From 720e114ace198134c3ec2a7ee51a3df13c263500 Mon Sep 17 00:00:00 2001 From: Christoph Stephan Date: Fri, 18 Nov 2016 13:08:23 +0100 Subject: [PATCH 01/11] update .gitignore --- .gitignore | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 275 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index f079a88c..709bd2ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,275 @@ -# ignore all obj and bin folders (even in subdirectories) -obj/ -bin/ -/Documentation/Help -/packages/AvalonEdit -/packages/AvalonEdit.Sample -/packages/NUnit.2.6.3 -/Documentation/IntelliSense +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ +Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +### VisualStudio Patch ### +build/ From 7cc1615d98ad4af328d7d86f9ad23eac3e0dd80a Mon Sep 17 00:00:00 2001 From: Christoph Stephan Date: Fri, 18 Nov 2016 13:09:20 +0100 Subject: [PATCH 02/11] only formatting --- ICSharpCode.AvalonEdit.Sample/Window1.xaml | 122 ++- ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs | 328 +++--- .../Search/FindTests.cs | 199 ++-- .../Search/DropDownButton.cs | 127 ++- .../Search/ISearchStrategy.cs | 145 ++- ICSharpCode.AvalonEdit/Search/Localization.cs | 112 +- .../Search/RegexSearchStrategy.cs | 105 +- .../Search/SearchCommands.cs | 232 ++--- ICSharpCode.AvalonEdit/Search/SearchPanel.cs | 955 +++++++++--------- .../Search/SearchPanel.xaml | 86 +- .../Search/SearchResultBackgroundRenderer.cs | 124 ++- .../Search/SearchStrategyFactory.cs | 112 +- 12 files changed, 1282 insertions(+), 1365 deletions(-) diff --git a/ICSharpCode.AvalonEdit.Sample/Window1.xaml b/ICSharpCode.AvalonEdit.Sample/Window1.xaml index 984a27ae..c802e00b 100644 --- a/ICSharpCode.AvalonEdit.Sample/Window1.xaml +++ b/ICSharpCode.AvalonEdit.Sample/Window1.xaml @@ -24,66 +24,82 @@ xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" Title="AvalonEdit.Sample" Height="500" Width="700" > - - - - - - - - - - - - - - - - - - - - - # - - - - - + + + + + + + + + + + + + + + + + + + + # + + + + + - - - - - - - + + + + + + Welcome to AvalonEdit! - - - - + + + - TextEditor - TextArea - Options - - - - - - - + TextEditor + TextArea + Options + + + + + + + \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs b/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs index 337cd8cd..6bc34435 100644 --- a/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs +++ b/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs @@ -31,174 +31,164 @@ using ICSharpCode.AvalonEdit.Highlighting; using Microsoft.Win32; -namespace ICSharpCode.AvalonEdit.Sample -{ - /// - /// Interaction logic for Window1.xaml - /// - public partial class Window1 : Window - { - public Window1() - { - // Load our custom highlighting definition - IHighlightingDefinition customHighlighting; - using (Stream s = typeof(Window1).Assembly.GetManifestResourceStream("AvalonEdit.Sample.CustomHighlighting.xshd")) { - if (s == null) - throw new InvalidOperationException("Could not find embedded resource"); - using (XmlReader reader = new XmlTextReader(s)) { - customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd. - HighlightingLoader.Load(reader, HighlightingManager.Instance); - } - } - // and register it in the HighlightingManager - HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".cool" }, customHighlighting); - - - InitializeComponent(); - #if DOTNET4 +namespace ICSharpCode.AvalonEdit.Sample { + /// + /// Interaction logic for Window1.xaml + /// + public partial class Window1 : Window { + public Window1() { + // Load our custom highlighting definition + IHighlightingDefinition customHighlighting; + using (Stream s = typeof(Window1).Assembly.GetManifestResourceStream("AvalonEdit.Sample.CustomHighlighting.xshd")) { + if (s == null) + throw new InvalidOperationException("Could not find embedded resource"); + using (XmlReader reader = new XmlTextReader(s)) { + customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd. + HighlightingLoader.Load(reader, HighlightingManager.Instance); + } + } + // and register it in the HighlightingManager + HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".cool" }, customHighlighting); + + + InitializeComponent(); +#if DOTNET4 this.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display); - #endif - propertyGridComboBox.SelectedIndex = 2; - - //textEditor.TextArea.SelectionBorder = null; - - //textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#"); - //textEditor.SyntaxHighlighting = customHighlighting; - // initial highlighting now set by XAML - - textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering; - textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered; - - DispatcherTimer foldingUpdateTimer = new DispatcherTimer(); - foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2); - foldingUpdateTimer.Tick += delegate { UpdateFoldings(); }; - foldingUpdateTimer.Start(); - } - - string currentFileName; - - void openFileClick(object sender, RoutedEventArgs e) - { - OpenFileDialog dlg = new OpenFileDialog(); - dlg.CheckFileExists = true; - if (dlg.ShowDialog() ?? false) { - currentFileName = dlg.FileName; - textEditor.Load(currentFileName); - textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(Path.GetExtension(currentFileName)); - } - } - - void saveFileClick(object sender, EventArgs e) - { - if (currentFileName == null) { - SaveFileDialog dlg = new SaveFileDialog(); - dlg.DefaultExt = ".txt"; - if (dlg.ShowDialog() ?? false) { - currentFileName = dlg.FileName; - } else { - return; - } - } - textEditor.Save(currentFileName); - } - - void propertyGridComboBoxSelectionChanged(object sender, RoutedEventArgs e) - { - if (propertyGrid == null) - return; - switch (propertyGridComboBox.SelectedIndex) { - case 0: - propertyGrid.SelectedObject = textEditor; - break; - case 1: - propertyGrid.SelectedObject = textEditor.TextArea; - break; - case 2: - propertyGrid.SelectedObject = textEditor.Options; - break; - } - } - - CompletionWindow completionWindow; - - void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e) - { - if (e.Text == ".") { - // open code completion after the user has pressed dot: - completionWindow = new CompletionWindow(textEditor.TextArea); - // provide AvalonEdit with the data: - IList data = completionWindow.CompletionList.CompletionData; - data.Add(new MyCompletionData("Item1")); - data.Add(new MyCompletionData("Item2")); - data.Add(new MyCompletionData("Item3")); - data.Add(new MyCompletionData("Another item")); - completionWindow.Show(); - completionWindow.Closed += delegate { - completionWindow = null; - }; - } - } - - void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e) - { - if (e.Text.Length > 0 && completionWindow != null) { - if (!char.IsLetterOrDigit(e.Text[0])) { - // Whenever a non-letter is typed while the completion window is open, - // insert the currently selected element. - completionWindow.CompletionList.RequestInsertion(e); - } - } - // do not set e.Handled=true - we still want to insert the character that was typed - } - - #region Folding - FoldingManager foldingManager; - object foldingStrategy; - - void HighlightingComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (textEditor.SyntaxHighlighting == null) { - foldingStrategy = null; - } else { - switch (textEditor.SyntaxHighlighting.Name) { - case "XML": - foldingStrategy = new XmlFoldingStrategy(); - textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); - break; - case "C#": - case "C++": - case "PHP": - case "Java": - textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.CSharp.CSharpIndentationStrategy(textEditor.Options); - foldingStrategy = new BraceFoldingStrategy(); - break; - default: - textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); - foldingStrategy = null; - break; - } - } - if (foldingStrategy != null) { - if (foldingManager == null) - foldingManager = FoldingManager.Install(textEditor.TextArea); - UpdateFoldings(); - } else { - if (foldingManager != null) { - FoldingManager.Uninstall(foldingManager); - foldingManager = null; - } - } - } - - void UpdateFoldings() - { - if (foldingStrategy is BraceFoldingStrategy) { - ((BraceFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); - } - if (foldingStrategy is XmlFoldingStrategy) { - ((XmlFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); - } - } - #endregion - } +#endif + propertyGridComboBox.SelectedIndex = 2; + + //textEditor.TextArea.SelectionBorder = null; + + //textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#"); + //textEditor.SyntaxHighlighting = customHighlighting; + // initial highlighting now set by XAML + + textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering; + textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered; + + DispatcherTimer foldingUpdateTimer = new DispatcherTimer(); + foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2); + foldingUpdateTimer.Tick += delegate { UpdateFoldings(); }; + foldingUpdateTimer.Start(); + } + + string currentFileName; + + void openFileClick(object sender, RoutedEventArgs e) { + OpenFileDialog dlg = new OpenFileDialog(); + dlg.CheckFileExists = true; + if (dlg.ShowDialog() ?? false) { + currentFileName = dlg.FileName; + textEditor.Load(currentFileName); + textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(Path.GetExtension(currentFileName)); + } + } + + void saveFileClick(object sender, EventArgs e) { + if (currentFileName == null) { + SaveFileDialog dlg = new SaveFileDialog(); + dlg.DefaultExt = ".txt"; + if (dlg.ShowDialog() ?? false) { + currentFileName = dlg.FileName; + } else { + return; + } + } + textEditor.Save(currentFileName); + } + + void propertyGridComboBoxSelectionChanged(object sender, RoutedEventArgs e) { + if (propertyGrid == null) + return; + switch (propertyGridComboBox.SelectedIndex) { + case 0: + propertyGrid.SelectedObject = textEditor; + break; + case 1: + propertyGrid.SelectedObject = textEditor.TextArea; + break; + case 2: + propertyGrid.SelectedObject = textEditor.Options; + break; + } + } + + CompletionWindow completionWindow; + + void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e) { + if (e.Text == ".") { + // open code completion after the user has pressed dot: + completionWindow = new CompletionWindow(textEditor.TextArea); + // provide AvalonEdit with the data: + IList data = completionWindow.CompletionList.CompletionData; + data.Add(new MyCompletionData("Item1")); + data.Add(new MyCompletionData("Item2")); + data.Add(new MyCompletionData("Item3")); + data.Add(new MyCompletionData("Another item")); + completionWindow.Show(); + completionWindow.Closed += delegate { + completionWindow = null; + }; + } + } + + void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e) { + if (e.Text.Length > 0 && completionWindow != null) { + if (!char.IsLetterOrDigit(e.Text[0])) { + // Whenever a non-letter is typed while the completion window is open, + // insert the currently selected element. + completionWindow.CompletionList.RequestInsertion(e); + } + } + // do not set e.Handled=true - we still want to insert the character that was typed + } + + #region Folding + FoldingManager foldingManager; + object foldingStrategy; + + void HighlightingComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { + if (textEditor.SyntaxHighlighting == null) { + foldingStrategy = null; + } else { + switch (textEditor.SyntaxHighlighting.Name) { + case "XML": + foldingStrategy = new XmlFoldingStrategy(); + textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); + break; + case "C#": + case "C++": + case "PHP": + case "Java": + textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.CSharp.CSharpIndentationStrategy(textEditor.Options); + foldingStrategy = new BraceFoldingStrategy(); + break; + default: + textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); + foldingStrategy = null; + break; + } + } + if (foldingStrategy != null) { + if (foldingManager == null) + foldingManager = FoldingManager.Install(textEditor.TextArea); + UpdateFoldings(); + } else { + if (foldingManager != null) { + FoldingManager.Uninstall(foldingManager); + foldingManager = null; + } + } + } + + void UpdateFoldings() { + if (foldingStrategy is BraceFoldingStrategy) { + ((BraceFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); + } + if (foldingStrategy is XmlFoldingStrategy) { + ((XmlFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); + } + } + #endregion + } } \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs b/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs index 958baffa..b7d82e4d 100644 --- a/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs +++ b/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs @@ -24,109 +24,98 @@ #endif using NUnit.Framework; -namespace ICSharpCode.AvalonEdit.Search -{ - [TestFixture] - public class FindTests - { - [Test] - public void SkipWordBorderSimple() - { - var strategy = SearchStrategyFactory.Create("All", false, true, SearchMode.Normal); - var text = new StringTextSource(" FindAllTests "); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void SkipWordBorder() - { - var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); - var text = new StringTextSource("name=\"{FindAllTests}\""); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void SkipWordBorder2() - { - var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); - var text = new StringTextSource("name=\"FindAllTests "); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void SkipWordBorder3() - { - var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); - var text = new StringTextSource(" // findtest"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void WordBorderTest() - { - var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); - var text = new StringTextSource(" // find me"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual(" ".Length, results[0].Offset); - Assert.AreEqual("// find".Length, results[0].Length); - } - - [Test] - public void ResultAtStart() - { - var strategy = SearchStrategyFactory.Create("result", false, true, SearchMode.Normal); - var text = new StringTextSource("result // find me"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual(0, results[0].Offset); - Assert.AreEqual("result".Length, results[0].Length); - } - - [Test] - public void ResultAtEnd() - { - var strategy = SearchStrategyFactory.Create("me", false, true, SearchMode.Normal); - var text = new StringTextSource("result // find me"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual("result // find ".Length, results[0].Offset); - Assert.AreEqual("me".Length, results[0].Length); - } - - [Test] - public void TextWithDots() - { - var strategy = SearchStrategyFactory.Create("Text", false, true, SearchMode.Normal); - var text = new StringTextSource(".Text."); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual(".".Length, results[0].Offset); - Assert.AreEqual("Text".Length, results[0].Length); - } - - [Test] - public void SimpleTest() - { - var strategy = SearchStrategyFactory.Create("AllTests", false, false, SearchMode.Normal); - var text = new StringTextSource("name=\"FindAllTests "); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual("name=\"Find".Length, results[0].Offset); - Assert.AreEqual("AllTests".Length, results[0].Length); - } - } +namespace ICSharpCode.AvalonEdit.Search { + [TestFixture] + public class FindTests { + [Test] + public void SkipWordBorderSimple() { + var strategy = SearchStrategyFactory.Create("All", false, true, SearchMode.Normal); + var text = new StringTextSource(" FindAllTests "); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void SkipWordBorder() { + var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); + var text = new StringTextSource("name=\"{FindAllTests}\""); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void SkipWordBorder2() { + var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); + var text = new StringTextSource("name=\"FindAllTests "); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void SkipWordBorder3() { + var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); + var text = new StringTextSource(" // findtest"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void WordBorderTest() { + var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); + var text = new StringTextSource(" // find me"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual(" ".Length, results[0].Offset); + Assert.AreEqual("// find".Length, results[0].Length); + } + + [Test] + public void ResultAtStart() { + var strategy = SearchStrategyFactory.Create("result", false, true, SearchMode.Normal); + var text = new StringTextSource("result // find me"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual(0, results[0].Offset); + Assert.AreEqual("result".Length, results[0].Length); + } + + [Test] + public void ResultAtEnd() { + var strategy = SearchStrategyFactory.Create("me", false, true, SearchMode.Normal); + var text = new StringTextSource("result // find me"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual("result // find ".Length, results[0].Offset); + Assert.AreEqual("me".Length, results[0].Length); + } + + [Test] + public void TextWithDots() { + var strategy = SearchStrategyFactory.Create("Text", false, true, SearchMode.Normal); + var text = new StringTextSource(".Text."); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual(".".Length, results[0].Offset); + Assert.AreEqual("Text".Length, results[0].Length); + } + + [Test] + public void SimpleTest() { + var strategy = SearchStrategyFactory.Create("AllTests", false, false, SearchMode.Normal); + var text = new StringTextSource("name=\"FindAllTests "); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual("name=\"Find".Length, results[0].Offset); + Assert.AreEqual("AllTests".Length, results[0].Length); + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/DropDownButton.cs b/ICSharpCode.AvalonEdit/Search/DropDownButton.cs index 31cb5f99..cd1802dc 100644 --- a/ICSharpCode.AvalonEdit/Search/DropDownButton.cs +++ b/ICSharpCode.AvalonEdit/Search/DropDownButton.cs @@ -20,70 +20,65 @@ using System.Windows; using System.Windows.Controls.Primitives; -namespace ICSharpCode.AvalonEdit.Search -{ - /// - /// A button that opens a drop-down menu when clicked. - /// - public class DropDownButton : ButtonBase - { - /// - /// Identifies the  dependency property. - /// - public static readonly DependencyProperty DropDownContentProperty - = DependencyProperty.Register("DropDownContent", typeof(Popup), - typeof(DropDownButton), new FrameworkPropertyMetadata(null)); - - /// - /// The key that identifies the  dependency property. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] - protected static readonly DependencyPropertyKey IsDropDownContentOpenPropertyKey - = DependencyProperty.RegisterReadOnly("IsDropDownContentOpen", typeof(bool), - typeof(DropDownButton), new FrameworkPropertyMetadata(false)); - - /// - /// Identifies the  dependency property. - /// - public static readonly DependencyProperty IsDropDownContentOpenProperty = IsDropDownContentOpenPropertyKey.DependencyProperty; - - static DropDownButton() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); - } - - /// - /// Gets/Sets the popup that is used as drop-down content. - /// - public Popup DropDownContent { - get { return (Popup)GetValue(DropDownContentProperty); } - set { SetValue(DropDownContentProperty, value); } - } - - /// - /// Gets whether the drop-down is opened. - /// - public bool IsDropDownContentOpen { - get { return (bool)GetValue(IsDropDownContentOpenProperty); } - protected set { SetValue(IsDropDownContentOpenPropertyKey, value); } - } - - /// - protected override void OnClick() - { - if (DropDownContent != null && !IsDropDownContentOpen) { - DropDownContent.Placement = PlacementMode.Bottom; - DropDownContent.PlacementTarget = this; - DropDownContent.IsOpen = true; - DropDownContent.Closed += DropDownContent_Closed; - this.IsDropDownContentOpen = true; - } - } - - void DropDownContent_Closed(object sender, EventArgs e) - { - ((Popup)sender).Closed -= DropDownContent_Closed; - this.IsDropDownContentOpen = false; - } - } +namespace ICSharpCode.AvalonEdit.Search { + /// + /// A button that opens a drop-down menu when clicked. + /// + public class DropDownButton : ButtonBase { + /// + /// Identifies the  dependency property. + /// + public static readonly DependencyProperty DropDownContentProperty + = DependencyProperty.Register("DropDownContent", typeof(Popup), + typeof(DropDownButton), new FrameworkPropertyMetadata(null)); + + /// + /// The key that identifies the  dependency property. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + protected static readonly DependencyPropertyKey IsDropDownContentOpenPropertyKey + = DependencyProperty.RegisterReadOnly("IsDropDownContentOpen", typeof(bool), + typeof(DropDownButton), new FrameworkPropertyMetadata(false)); + + /// + /// Identifies the  dependency property. + /// + public static readonly DependencyProperty IsDropDownContentOpenProperty = IsDropDownContentOpenPropertyKey.DependencyProperty; + + static DropDownButton() { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); + } + + /// + /// Gets/Sets the popup that is used as drop-down content. + /// + public Popup DropDownContent { + get { return (Popup)GetValue(DropDownContentProperty); } + set { SetValue(DropDownContentProperty, value); } + } + + /// + /// Gets whether the drop-down is opened. + /// + public bool IsDropDownContentOpen { + get { return (bool)GetValue(IsDropDownContentOpenProperty); } + protected set { SetValue(IsDropDownContentOpenPropertyKey, value); } + } + + /// + protected override void OnClick() { + if (DropDownContent != null && !IsDropDownContentOpen) { + DropDownContent.Placement = PlacementMode.Bottom; + DropDownContent.PlacementTarget = this; + DropDownContent.IsOpen = true; + DropDownContent.Closed += DropDownContent_Closed; + this.IsDropDownContentOpen = true; + } + } + + void DropDownContent_Closed(object sender, EventArgs e) { + ((Popup)sender).Closed -= DropDownContent_Closed; + this.IsDropDownContentOpen = false; + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs b/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs index 9121a454..71440ae5 100644 --- a/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs +++ b/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs @@ -22,82 +22,73 @@ using ICSharpCode.NRefactory.Editor; using ICSharpCode.AvalonEdit.Document; -namespace ICSharpCode.AvalonEdit.Search -{ - /// - /// Basic interface for search algorithms. - /// - public interface ISearchStrategy : IEquatable - { - /// - /// Finds all matches in the given ITextSource and the given range. - /// - /// - /// This method must be implemented thread-safe. - /// All segments in the result must be within the given range, and they must be returned in order - /// (e.g. if two results are returned, EndOffset of first result must be less than or equal StartOffset of second result). - /// - IEnumerable FindAll(ITextSource document, int offset, int length); - - /// - /// Finds the next match in the given ITextSource and the given range. - /// - /// This method must be implemented thread-safe. - ISearchResult FindNext(ITextSource document, int offset, int length); - } - - /// - /// Represents a search result. - /// - public interface ISearchResult : ISegment - { - /// - /// Replaces parts of the replacement string with parts from the match. (e.g. $1) - /// - string ReplaceWith(string replacement); - } - - /// - /// Defines supported search modes. - /// - public enum SearchMode - { - /// - /// Standard search - /// - Normal, - /// - /// RegEx search - /// - RegEx, - /// - /// Wildcard search - /// - Wildcard - } - - /// - public class SearchPatternException : Exception, ISerializable - { - /// - public SearchPatternException() - { - } - - /// - public SearchPatternException(string message) : base(message) - { - } - - /// - public SearchPatternException(string message, Exception innerException) : base(message, innerException) - { - } +namespace ICSharpCode.AvalonEdit.Search { + /// + /// Basic interface for search algorithms. + /// + public interface ISearchStrategy : IEquatable { + /// + /// Finds all matches in the given ITextSource and the given range. + /// + /// + /// This method must be implemented thread-safe. + /// All segments in the result must be within the given range, and they must be returned in order + /// (e.g. if two results are returned, EndOffset of first result must be less than or equal StartOffset of second result). + /// + IEnumerable FindAll(ITextSource document, int offset, int length); - // This constructor is needed for serialization. - /// - protected SearchPatternException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } + /// + /// Finds the next match in the given ITextSource and the given range. + /// + /// This method must be implemented thread-safe. + ISearchResult FindNext(ITextSource document, int offset, int length); + } + + /// + /// Represents a search result. + /// + public interface ISearchResult : ISegment { + /// + /// Replaces parts of the replacement string with parts from the match. (e.g. $1) + /// + string ReplaceWith(string replacement); + } + + /// + /// Defines supported search modes. + /// + public enum SearchMode { + /// + /// Standard search + /// + Normal, + /// + /// RegEx search + /// + RegEx, + /// + /// Wildcard search + /// + Wildcard + } + + /// + public class SearchPatternException : Exception, ISerializable { + /// + public SearchPatternException() { + } + + /// + public SearchPatternException(string message) : base(message) { + } + + /// + public SearchPatternException(string message, Exception innerException) : base(message, innerException) { + } + + // This constructor is needed for serialization. + /// + protected SearchPatternException(SerializationInfo info, StreamingContext context) : base(info, context) { + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/Localization.cs b/ICSharpCode.AvalonEdit/Search/Localization.cs index b0044d93..d965ddaf 100644 --- a/ICSharpCode.AvalonEdit/Search/Localization.cs +++ b/ICSharpCode.AvalonEdit/Search/Localization.cs @@ -19,61 +19,59 @@ using System; using System.ComponentModel; -namespace ICSharpCode.AvalonEdit.Search -{ - /// - /// Holds default texts for buttons and labels in the SearchPanel. Override properties to add other languages. - /// - public class Localization - { - /// - /// Default: 'Match case' - /// - public virtual string MatchCaseText { - get { return "Match case"; } - } - - /// - /// Default: 'Match whole words' - /// - public virtual string MatchWholeWordsText { - get { return "Match whole words"; } - } - - - /// - /// Default: 'Use regular expressions' - /// - public virtual string UseRegexText { - get { return "Use regular expressions"; } - } - - /// - /// Default: 'Find next (F3)' - /// - public virtual string FindNextText { - get { return "Find next (F3)"; } - } - - /// - /// Default: 'Find previous (Shift+F3)' - /// - public virtual string FindPreviousText { - get { return "Find previous (Shift+F3)"; } - } - - /// - /// Default: 'Error: ' - /// - public virtual string ErrorText { - get { return "Error: "; } - } - - /// - /// Default: 'No matches found!' - /// - public virtual string NoMatchesFoundText { - get { return "No matches found!"; } - } - } +namespace ICSharpCode.AvalonEdit.Search { + /// + /// Holds default texts for buttons and labels in the SearchPanel. Override properties to add other languages. + /// + public class Localization { + /// + /// Default: 'Match case' + /// + public virtual string MatchCaseText { + get { return "Match case"; } + } + + /// + /// Default: 'Match whole words' + /// + public virtual string MatchWholeWordsText { + get { return "Match whole words"; } + } + + + /// + /// Default: 'Use regular expressions' + /// + public virtual string UseRegexText { + get { return "Use regular expressions"; } + } + + /// + /// Default: 'Find next (F3)' + /// + public virtual string FindNextText { + get { return "Find next (F3)"; } + } + + /// + /// Default: 'Find previous (Shift+F3)' + /// + public virtual string FindPreviousText { + get { return "Find previous (Shift+F3)"; } + } + + /// + /// Default: 'Error: ' + /// + public virtual string ErrorText { + get { return "Error: "; } + } + + /// + /// Default: 'No matches found!' + /// + public virtual string NoMatchesFoundText { + get { return "No matches found!"; } + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs b/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs index 47bde5af..ada35ee5 100644 --- a/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs +++ b/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs @@ -25,61 +25,52 @@ using ICSharpCode.AvalonEdit.Document; using ICSharpCode.NRefactory.Editor; -namespace ICSharpCode.AvalonEdit.Search -{ - class RegexSearchStrategy : ISearchStrategy - { - readonly Regex searchPattern; - readonly bool matchWholeWords; - - public RegexSearchStrategy(Regex searchPattern, bool matchWholeWords) - { - if (searchPattern == null) - throw new ArgumentNullException("searchPattern"); - this.searchPattern = searchPattern; - this.matchWholeWords = matchWholeWords; - } - - public IEnumerable FindAll(ITextSource document, int offset, int length) - { - int endOffset = offset + length; - foreach (Match result in searchPattern.Matches(document.Text)) { - int resultEndOffset = result.Length + result.Index; - if (offset > result.Index || endOffset < resultEndOffset) - continue; - if (matchWholeWords && (!IsWordBorder(document, result.Index) || !IsWordBorder(document, resultEndOffset))) - continue; - yield return new SearchResult { StartOffset = result.Index, Length = result.Length, Data = result }; - } - } - - static bool IsWordBorder(ITextSource document, int offset) - { - return TextUtilities.GetNextCaretPosition(document, offset - 1, LogicalDirection.Forward, CaretPositioningMode.WordBorder) == offset; - } - - public ISearchResult FindNext(ITextSource document, int offset, int length) - { - return FindAll(document, offset, length).FirstOrDefault(); - } - - public bool Equals(ISearchStrategy other) - { - var strategy = other as RegexSearchStrategy; - return strategy != null && - strategy.searchPattern.ToString() == searchPattern.ToString() && - strategy.searchPattern.Options == searchPattern.Options && - strategy.searchPattern.RightToLeft == searchPattern.RightToLeft; - } - } - - class SearchResult : TextSegment, ISearchResult - { - public Match Data { get; set; } - - public string ReplaceWith(string replacement) - { - return Data.Result(replacement); - } - } +namespace ICSharpCode.AvalonEdit.Search { + class RegexSearchStrategy : ISearchStrategy { + readonly Regex searchPattern; + readonly bool matchWholeWords; + + public RegexSearchStrategy(Regex searchPattern, bool matchWholeWords) { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + this.searchPattern = searchPattern; + this.matchWholeWords = matchWholeWords; + } + + public IEnumerable FindAll(ITextSource document, int offset, int length) { + int endOffset = offset + length; + foreach (Match result in searchPattern.Matches(document.Text)) { + int resultEndOffset = result.Length + result.Index; + if (offset > result.Index || endOffset < resultEndOffset) + continue; + if (matchWholeWords && (!IsWordBorder(document, result.Index) || !IsWordBorder(document, resultEndOffset))) + continue; + yield return new SearchResult { StartOffset = result.Index, Length = result.Length, Data = result }; + } + } + + static bool IsWordBorder(ITextSource document, int offset) { + return TextUtilities.GetNextCaretPosition(document, offset - 1, LogicalDirection.Forward, CaretPositioningMode.WordBorder) == offset; + } + + public ISearchResult FindNext(ITextSource document, int offset, int length) { + return FindAll(document, offset, length).FirstOrDefault(); + } + + public bool Equals(ISearchStrategy other) { + var strategy = other as RegexSearchStrategy; + return strategy != null && + strategy.searchPattern.ToString() == searchPattern.ToString() && + strategy.searchPattern.Options == searchPattern.Options && + strategy.searchPattern.RightToLeft == searchPattern.RightToLeft; + } + } + + class SearchResult : TextSegment, ISearchResult { + public Match Data { get; set; } + + public string ReplaceWith(string replacement) { + return Data.Result(replacement); + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs index 6e16f38f..8a3ab735 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs @@ -28,128 +28,116 @@ using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; -namespace ICSharpCode.AvalonEdit.Search -{ - /// - /// Search commands for AvalonEdit. - /// - public static class SearchCommands - { - /// - /// Finds the next occurrence in the file. - /// - public static readonly RoutedCommand FindNext = new RoutedCommand( - "FindNext", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.F3) } - ); - - /// - /// Finds the previous occurrence in the file. - /// - public static readonly RoutedCommand FindPrevious = new RoutedCommand( - "FindPrevious", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) } - ); - - /// - /// Closes the SearchPanel. - /// - public static readonly RoutedCommand CloseSearchPanel = new RoutedCommand( - "CloseSearchPanel", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.Escape) } - ); - } - - /// - /// TextAreaInputHandler that registers all search-related commands. - /// - public class SearchInputHandler : TextAreaInputHandler - { - /// - /// Creates a new SearchInputHandler and registers the search-related commands. - /// - [Obsolete("Use SearchPanel.Install instead")] - public SearchInputHandler(TextArea textArea) - : base(textArea) - { - RegisterCommands(this.CommandBindings); - panel = SearchPanel.Install(textArea); - } - - internal SearchInputHandler(TextArea textArea, SearchPanel panel) - : base(textArea) - { - RegisterCommands(this.CommandBindings); - this.panel = panel; - } - - internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) - { - commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); - commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); - } +namespace ICSharpCode.AvalonEdit.Search { + /// + /// Search commands for AvalonEdit. + /// + public static class SearchCommands { + /// + /// Finds the next occurrence in the file. + /// + public static readonly RoutedCommand FindNext = new RoutedCommand( + "FindNext", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3) } + ); - void RegisterCommands(ICollection commandBindings) - { - commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); - commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel, CanExecuteWithOpenSearchPanel)); - } - - SearchPanel panel; - - void ExecuteFind(object sender, ExecutedRoutedEventArgs e) - { - panel.Open(); - if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) - panel.SearchPattern = TextArea.Selection.GetText(); - Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); - } + /// + /// Finds the previous occurrence in the file. + /// + public static readonly RoutedCommand FindPrevious = new RoutedCommand( + "FindPrevious", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) } + ); - void CanExecuteWithOpenSearchPanel(object sender, CanExecuteRoutedEventArgs e) - { - if (panel.IsClosed) { - e.CanExecute = false; - // Continue routing so that the key gesture can be consumed by another component. - e.ContinueRouting = true; - } else { - e.CanExecute = true; - e.Handled = true; - } - } - - void ExecuteFindNext(object sender, ExecutedRoutedEventArgs e) - { - if (!panel.IsClosed) { - panel.FindNext(); - e.Handled = true; - } - } - - void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) - { - if (!panel.IsClosed) { - panel.FindPrevious(); - e.Handled = true; - } - } - - void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) - { - if (!panel.IsClosed) { - panel.Close(); - e.Handled = true; - } - } - - /// - /// Fired when SearchOptions are modified inside the SearchPanel. - /// - public event EventHandler SearchOptionsChanged { - add { panel.SearchOptionsChanged += value; } - remove { panel.SearchOptionsChanged -= value; } - } - } + /// + /// Closes the SearchPanel. + /// + public static readonly RoutedCommand CloseSearchPanel = new RoutedCommand( + "CloseSearchPanel", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.Escape) } + ); + } + + /// + /// TextAreaInputHandler that registers all search-related commands. + /// + public class SearchInputHandler : TextAreaInputHandler { + /// + /// Creates a new SearchInputHandler and registers the search-related commands. + /// + [Obsolete("Use SearchPanel.Install instead")] + public SearchInputHandler(TextArea textArea) + : base(textArea) { + RegisterCommands(this.CommandBindings); + panel = SearchPanel.Install(textArea); + } + + internal SearchInputHandler(TextArea textArea, SearchPanel panel) + : base(textArea) { + RegisterCommands(this.CommandBindings); + this.panel = panel; + } + + internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) { + commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); + } + + void RegisterCommands(ICollection commandBindings) { + commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel, CanExecuteWithOpenSearchPanel)); + } + + SearchPanel panel; + + void ExecuteFind(object sender, ExecutedRoutedEventArgs e) { + panel.Open(); + if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) + panel.SearchPattern = TextArea.Selection.GetText(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); + } + + void CanExecuteWithOpenSearchPanel(object sender, CanExecuteRoutedEventArgs e) { + if (panel.IsClosed) { + e.CanExecute = false; + // Continue routing so that the key gesture can be consumed by another component. + e.ContinueRouting = true; + } else { + e.CanExecute = true; + e.Handled = true; + } + } + + void ExecuteFindNext(object sender, ExecutedRoutedEventArgs e) { + if (!panel.IsClosed) { + panel.FindNext(); + e.Handled = true; + } + } + + void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) { + if (!panel.IsClosed) { + panel.FindPrevious(); + e.Handled = true; + } + } + + void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) { + if (!panel.IsClosed) { + panel.Close(); + e.Handled = true; + } + } + + /// + /// Fired when SearchOptions are modified inside the SearchPanel. + /// + public event EventHandler SearchOptionsChanged { + add { panel.SearchOptionsChanged += value; } + remove { panel.SearchOptionsChanged -= value; } + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs index 9871b95f..89f6c01e 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs @@ -34,498 +34,465 @@ using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Rendering; -namespace ICSharpCode.AvalonEdit.Search -{ - /// - /// Provides search functionality for AvalonEdit. It is displayed in the top-right corner of the TextArea. - /// - public class SearchPanel : Control - { - TextArea textArea; - SearchInputHandler handler; - TextDocument currentDocument; - SearchResultBackgroundRenderer renderer; - TextBox searchTextBox; - SearchPanelAdorner adorner; - - #region DependencyProperties - /// - /// Dependency property for . - /// - public static readonly DependencyProperty UseRegexProperty = - DependencyProperty.Register("UseRegex", typeof(bool), typeof(SearchPanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should be interpreted as regular expression. - /// - public bool UseRegex { - get { return (bool)GetValue(UseRegexProperty); } - set { SetValue(UseRegexProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty MatchCaseProperty = - DependencyProperty.Register("MatchCase", typeof(bool), typeof(SearchPanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should be interpreted case-sensitive. - /// - public bool MatchCase { - get { return (bool)GetValue(MatchCaseProperty); } - set { SetValue(MatchCaseProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty WholeWordsProperty = - DependencyProperty.Register("WholeWords", typeof(bool), typeof(SearchPanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should only match whole words. - /// - public bool WholeWords { - get { return (bool)GetValue(WholeWordsProperty); } - set { SetValue(WholeWordsProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty SearchPatternProperty = - DependencyProperty.Register("SearchPattern", typeof(string), typeof(SearchPanel), - new FrameworkPropertyMetadata("", SearchPatternChangedCallback)); - - /// - /// Gets/sets the search pattern. - /// - public string SearchPattern { - get { return (string)GetValue(SearchPatternProperty); } - set { SetValue(SearchPatternProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty MarkerBrushProperty = - DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(SearchPanel), - new FrameworkPropertyMetadata(Brushes.LightGreen, MarkerBrushChangedCallback)); - - /// - /// Gets/sets the Brush used for marking search results in the TextView. - /// - public Brush MarkerBrush { - get { return (Brush)GetValue(MarkerBrushProperty); } - set { SetValue(MarkerBrushProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty LocalizationProperty = - DependencyProperty.Register("Localization", typeof(Localization), typeof(SearchPanel), - new FrameworkPropertyMetadata(new Localization())); - - /// - /// Gets/sets the localization for the SearchPanel. - /// - public Localization Localization { - get { return (Localization)GetValue(LocalizationProperty); } - set { SetValue(LocalizationProperty, value); } - } - #endregion - - static void MarkerBrushChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - SearchPanel panel = d as SearchPanel; - if (panel != null) { - panel.renderer.MarkerBrush = (Brush)e.NewValue; - } - } - - static SearchPanel() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchPanel), new FrameworkPropertyMetadata(typeof(SearchPanel))); - } - - ISearchStrategy strategy; - - static void SearchPatternChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - SearchPanel panel = d as SearchPanel; - if (panel != null) { - panel.ValidateSearchText(); - panel.UpdateSearch(); - } - } - - void UpdateSearch() - { - // only reset as long as there are results - // if no results are found, the "no matches found" message should not flicker. - // if results are found by the next run, the message will be hidden inside DoSearch ... - if (renderer.CurrentResults.Any()) - messageView.IsOpen = false; - strategy = SearchStrategyFactory.Create(SearchPattern ?? "", !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal); - OnSearchOptionsChanged(new SearchOptionsChangedEventArgs(SearchPattern, MatchCase, UseRegex, WholeWords)); - DoSearch(true); - } - - /// - /// Creates a new SearchPanel. - /// - SearchPanel() - { - } - - /// - /// Attaches this SearchPanel to a TextArea instance. - /// - [Obsolete("Use the Install method instead")] - public void Attach(TextArea textArea) - { - if (textArea == null) - throw new ArgumentNullException("textArea"); - AttachInternal(textArea); - } - - /// - /// Creates a SearchPanel and installs it to the TextEditor's TextArea. - /// - /// This is a convenience wrapper. - public static SearchPanel Install(TextEditor editor) - { - if (editor == null) - throw new ArgumentNullException("editor"); - return Install(editor.TextArea); - } - - /// - /// Creates a SearchPanel and installs it to the TextArea. - /// - public static SearchPanel Install(TextArea textArea) - { - if (textArea == null) - throw new ArgumentNullException("textArea"); - SearchPanel panel = new SearchPanel(); - panel.AttachInternal(textArea); - panel.handler = new SearchInputHandler(textArea, panel); - textArea.DefaultInputHandler.NestedInputHandlers.Add(panel.handler); - return panel; - } - - /// - /// Adds the commands used by SearchPanel to the given CommandBindingCollection. - /// - public void RegisterCommands(CommandBindingCollection commandBindings) - { - handler.RegisterGlobalCommands(commandBindings); - } - - /// - /// Removes the SearchPanel from the TextArea. - /// - public void Uninstall() - { - CloseAndRemove(); - textArea.DefaultInputHandler.NestedInputHandlers.Remove(handler); - } - - void AttachInternal(TextArea textArea) - { - this.textArea = textArea; - adorner = new SearchPanelAdorner(textArea, this); - DataContext = this; - - renderer = new SearchResultBackgroundRenderer(); - currentDocument = textArea.Document; - if (currentDocument != null) - currentDocument.TextChanged += textArea_Document_TextChanged; - textArea.DocumentChanged += textArea_DocumentChanged; - KeyDown += SearchLayerKeyDown; - - this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); - this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); - this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); - IsClosed = true; - } - - void textArea_DocumentChanged(object sender, EventArgs e) - { - if (currentDocument != null) - currentDocument.TextChanged -= textArea_Document_TextChanged; - currentDocument = textArea.Document; - if (currentDocument != null) { - currentDocument.TextChanged += textArea_Document_TextChanged; - DoSearch(false); - } - } - - void textArea_Document_TextChanged(object sender, EventArgs e) - { - DoSearch(false); - } - - /// - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; - } - - void ValidateSearchText() - { - if (searchTextBox == null) - return; - var be = searchTextBox.GetBindingExpression(TextBox.TextProperty); - try { - Validation.ClearInvalid(be); - UpdateSearch(); - } catch (SearchPatternException ex) { - var ve = new ValidationError(be.ParentBinding.ValidationRules[0], be, ex.Message, ex); - Validation.MarkInvalid(be, ve); - } - } - - /// - /// Reactivates the SearchPanel by setting the focus on the search box and selecting all text. - /// - public void Reactivate() - { - if (searchTextBox == null) - return; - searchTextBox.Focus(); - searchTextBox.SelectAll(); - } - - /// - /// Moves to the next occurrence in the file. - /// - public void FindNext() - { - SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); - if (result == null) - result = renderer.CurrentResults.FirstSegment; - if (result != null) { - SelectResult(result); - } - } - - /// - /// Moves to the previous occurrence in the file. - /// - public void FindPrevious() - { - SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); - if (result != null) - result = renderer.CurrentResults.GetPreviousSegment(result); - if (result == null) - result = renderer.CurrentResults.LastSegment; - if (result != null) { - SelectResult(result); - } - } - - ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = true, Focusable = false }; - - void DoSearch(bool changeSelection) - { - if (IsClosed) - return; - renderer.CurrentResults.Clear(); - - if (!string.IsNullOrEmpty(SearchPattern)) { - int offset = textArea.Caret.Offset; - if (changeSelection) { - textArea.ClearSelection(); - } - // We cast from ISearchResult to SearchResult; this is safe because we always use the built-in strategy - foreach (SearchResult result in strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength)) { - if (changeSelection && result.StartOffset >= offset) { - SelectResult(result); - changeSelection = false; - } - renderer.CurrentResults.Add(result); - } - if (!renderer.CurrentResults.Any()) { - messageView.IsOpen = true; - messageView.Content = Localization.NoMatchesFoundText; - messageView.PlacementTarget = searchTextBox; - } else - messageView.IsOpen = false; - } - textArea.TextView.InvalidateLayer(KnownLayer.Selection); - } - - void SelectResult(SearchResult result) - { - textArea.Caret.Offset = result.StartOffset; - textArea.Selection = Selection.Create(textArea, result.StartOffset, result.EndOffset); - textArea.Caret.BringCaretToView(); - // show caret even if the editor does not have the Keyboard Focus - textArea.Caret.Show(); - } - - void SearchLayerKeyDown(object sender, KeyEventArgs e) - { - switch (e.Key) { - case Key.Enter: - e.Handled = true; - if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) - FindPrevious(); - else - FindNext(); - if (searchTextBox != null) { - var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); - if (error != null) { - messageView.Content = Localization.ErrorText + " " + error.ErrorContent; - messageView.PlacementTarget = searchTextBox; - messageView.IsOpen = true; - } - } - break; - case Key.Escape: - e.Handled = true; - Close(); - break; - } - } - - /// - /// Gets whether the Panel is already closed. - /// - public bool IsClosed { get; private set; } - - /// - /// Closes the SearchPanel. - /// - public void Close() - { - bool hasFocus = this.IsKeyboardFocusWithin; - - var layer = AdornerLayer.GetAdornerLayer(textArea); - if (layer != null) - layer.Remove(adorner); - messageView.IsOpen = false; - textArea.TextView.BackgroundRenderers.Remove(renderer); - if (hasFocus) - textArea.Focus(); - IsClosed = true; - - // Clear existing search results so that the segments don't have to be maintained - renderer.CurrentResults.Clear(); - } - - /// - /// Closes the SearchPanel and removes it. - /// - [Obsolete("Use the Uninstall method instead!")] - public void CloseAndRemove() - { - Close(); - textArea.DocumentChanged -= textArea_DocumentChanged; - if (currentDocument != null) - currentDocument.TextChanged -= textArea_Document_TextChanged; - } - - /// - /// Opens the an existing search panel. - /// - public void Open() - { - if (!IsClosed) return; - var layer = AdornerLayer.GetAdornerLayer(textArea); - if (layer != null) - layer.Add(adorner); - textArea.TextView.BackgroundRenderers.Add(renderer); - IsClosed = false; - DoSearch(false); - } - - /// - /// Fired when SearchOptions are changed inside the SearchPanel. - /// - public event EventHandler SearchOptionsChanged; - - /// - /// Raises the event. - /// - protected virtual void OnSearchOptionsChanged(SearchOptionsChangedEventArgs e) - { - if (SearchOptionsChanged != null) { - SearchOptionsChanged(this, e); - } - } - } - - /// - /// EventArgs for event. - /// - public class SearchOptionsChangedEventArgs : EventArgs - { - /// - /// Gets the search pattern. - /// - public string SearchPattern { get; private set; } - - /// - /// Gets whether the search pattern should be interpreted case-sensitive. - /// - public bool MatchCase { get; private set; } - - /// - /// Gets whether the search pattern should be interpreted as regular expression. - /// - public bool UseRegex { get; private set; } - - /// - /// Gets whether the search pattern should only match whole words. - /// - public bool WholeWords { get; private set; } - - /// - /// Creates a new SearchOptionsChangedEventArgs instance. - /// - public SearchOptionsChangedEventArgs(string searchPattern, bool matchCase, bool useRegex, bool wholeWords) - { - this.SearchPattern = searchPattern; - this.MatchCase = matchCase; - this.UseRegex = useRegex; - this.WholeWords = wholeWords; - } - } - - class SearchPanelAdorner : Adorner - { - SearchPanel panel; - - public SearchPanelAdorner(TextArea textArea, SearchPanel panel) - : base(textArea) - { - this.panel = panel; - AddVisualChild(panel); - } - - protected override int VisualChildrenCount { - get { return 1; } - } - - protected override Visual GetVisualChild(int index) - { - if (index != 0) - throw new ArgumentOutOfRangeException(); - return panel; - } - - protected override Size ArrangeOverride(Size finalSize) - { - panel.Arrange(new Rect(new Point(0, 0), finalSize)); - return new Size(panel.ActualWidth, panel.ActualHeight); - } - } +namespace ICSharpCode.AvalonEdit.Search { + /// + /// Provides search functionality for AvalonEdit. It is displayed in the top-right corner of the TextArea. + /// + public class SearchPanel : Control { + TextArea textArea; + SearchInputHandler handler; + TextDocument currentDocument; + SearchResultBackgroundRenderer renderer; + TextBox searchTextBox; + SearchPanelAdorner adorner; + + #region DependencyProperties + /// + /// Dependency property for . + /// + public static readonly DependencyProperty UseRegexProperty = + DependencyProperty.Register("UseRegex", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should be interpreted as regular expression. + /// + public bool UseRegex { + get { return (bool)GetValue(UseRegexProperty); } + set { SetValue(UseRegexProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty MatchCaseProperty = + DependencyProperty.Register("MatchCase", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should be interpreted case-sensitive. + /// + public bool MatchCase { + get { return (bool)GetValue(MatchCaseProperty); } + set { SetValue(MatchCaseProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty WholeWordsProperty = + DependencyProperty.Register("WholeWords", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should only match whole words. + /// + public bool WholeWords { + get { return (bool)GetValue(WholeWordsProperty); } + set { SetValue(WholeWordsProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty SearchPatternProperty = + DependencyProperty.Register("SearchPattern", typeof(string), typeof(SearchPanel), + new FrameworkPropertyMetadata("", SearchPatternChangedCallback)); + + /// + /// Gets/sets the search pattern. + /// + public string SearchPattern { + get { return (string)GetValue(SearchPatternProperty); } + set { SetValue(SearchPatternProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty MarkerBrushProperty = + DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(SearchPanel), + new FrameworkPropertyMetadata(Brushes.LightGreen, MarkerBrushChangedCallback)); + + /// + /// Gets/sets the Brush used for marking search results in the TextView. + /// + public Brush MarkerBrush { + get { return (Brush)GetValue(MarkerBrushProperty); } + set { SetValue(MarkerBrushProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty LocalizationProperty = + DependencyProperty.Register("Localization", typeof(Localization), typeof(SearchPanel), + new FrameworkPropertyMetadata(new Localization())); + + /// + /// Gets/sets the localization for the SearchPanel. + /// + public Localization Localization { + get { return (Localization)GetValue(LocalizationProperty); } + set { SetValue(LocalizationProperty, value); } + } + #endregion + + static void MarkerBrushChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.renderer.MarkerBrush = (Brush)e.NewValue; + } + } + + static SearchPanel() { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchPanel), new FrameworkPropertyMetadata(typeof(SearchPanel))); + } + + ISearchStrategy strategy; + + static void SearchPatternChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.ValidateSearchText(); + panel.UpdateSearch(); + } + } + + void UpdateSearch() { + // only reset as long as there are results + // if no results are found, the "no matches found" message should not flicker. + // if results are found by the next run, the message will be hidden inside DoSearch ... + if (renderer.CurrentResults.Any()) + messageView.IsOpen = false; + strategy = SearchStrategyFactory.Create(SearchPattern ?? "", !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal); + OnSearchOptionsChanged(new SearchOptionsChangedEventArgs(SearchPattern, MatchCase, UseRegex, WholeWords)); + DoSearch(true); + } + + /// + /// Creates a new SearchPanel. + /// + SearchPanel() { + } + + /// + /// Attaches this SearchPanel to a TextArea instance. + /// + [Obsolete("Use the Install method instead")] + public void Attach(TextArea textArea) { + if (textArea == null) + throw new ArgumentNullException("textArea"); + AttachInternal(textArea); + } + + /// + /// Creates a SearchPanel and installs it to the TextEditor's TextArea. + /// + /// This is a convenience wrapper. + public static SearchPanel Install(TextEditor editor) { + if (editor == null) + throw new ArgumentNullException("editor"); + return Install(editor.TextArea); + } + + /// + /// Creates a SearchPanel and installs it to the TextArea. + /// + public static SearchPanel Install(TextArea textArea) { + if (textArea == null) + throw new ArgumentNullException("textArea"); + SearchPanel panel = new SearchPanel(); + panel.AttachInternal(textArea); + panel.handler = new SearchInputHandler(textArea, panel); + textArea.DefaultInputHandler.NestedInputHandlers.Add(panel.handler); + return panel; + } + + /// + /// Adds the commands used by SearchPanel to the given CommandBindingCollection. + /// + public void RegisterCommands(CommandBindingCollection commandBindings) { + handler.RegisterGlobalCommands(commandBindings); + } + + /// + /// Removes the SearchPanel from the TextArea. + /// + public void Uninstall() { + CloseAndRemove(); + textArea.DefaultInputHandler.NestedInputHandlers.Remove(handler); + } + + void AttachInternal(TextArea textArea) { + this.textArea = textArea; + adorner = new SearchPanelAdorner(textArea, this); + DataContext = this; + + renderer = new SearchResultBackgroundRenderer(); + currentDocument = textArea.Document; + if (currentDocument != null) + currentDocument.TextChanged += textArea_Document_TextChanged; + textArea.DocumentChanged += textArea_DocumentChanged; + KeyDown += SearchLayerKeyDown; + + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); + IsClosed = true; + } + + void textArea_DocumentChanged(object sender, EventArgs e) { + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + currentDocument = textArea.Document; + if (currentDocument != null) { + currentDocument.TextChanged += textArea_Document_TextChanged; + DoSearch(false); + } + } + + void textArea_Document_TextChanged(object sender, EventArgs e) { + DoSearch(false); + } + + /// + public override void OnApplyTemplate() { + base.OnApplyTemplate(); + searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; + } + + void ValidateSearchText() { + if (searchTextBox == null) + return; + var be = searchTextBox.GetBindingExpression(TextBox.TextProperty); + try { + Validation.ClearInvalid(be); + UpdateSearch(); + } catch (SearchPatternException ex) { + var ve = new ValidationError(be.ParentBinding.ValidationRules[0], be, ex.Message, ex); + Validation.MarkInvalid(be, ve); + } + } + + /// + /// Reactivates the SearchPanel by setting the focus on the search box and selecting all text. + /// + public void Reactivate() { + if (searchTextBox == null) + return; + searchTextBox.Focus(); + searchTextBox.SelectAll(); + } + + /// + /// Moves to the next occurrence in the file. + /// + public void FindNext() { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); + if (result == null) + result = renderer.CurrentResults.FirstSegment; + if (result != null) { + SelectResult(result); + } + } + + /// + /// Moves to the previous occurrence in the file. + /// + public void FindPrevious() { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); + if (result != null) + result = renderer.CurrentResults.GetPreviousSegment(result); + if (result == null) + result = renderer.CurrentResults.LastSegment; + if (result != null) { + SelectResult(result); + } + } + + ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = true, Focusable = false }; + + void DoSearch(bool changeSelection) { + if (IsClosed) + return; + renderer.CurrentResults.Clear(); + + if (!string.IsNullOrEmpty(SearchPattern)) { + int offset = textArea.Caret.Offset; + if (changeSelection) { + textArea.ClearSelection(); + } + // We cast from ISearchResult to SearchResult; this is safe because we always use the built-in strategy + foreach (SearchResult result in strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength)) { + if (changeSelection && result.StartOffset >= offset) { + SelectResult(result); + changeSelection = false; + } + renderer.CurrentResults.Add(result); + } + if (!renderer.CurrentResults.Any()) { + messageView.IsOpen = true; + messageView.Content = Localization.NoMatchesFoundText; + messageView.PlacementTarget = searchTextBox; + } else + messageView.IsOpen = false; + } + textArea.TextView.InvalidateLayer(KnownLayer.Selection); + } + + void SelectResult(SearchResult result) { + textArea.Caret.Offset = result.StartOffset; + textArea.Selection = Selection.Create(textArea, result.StartOffset, result.EndOffset); + textArea.Caret.BringCaretToView(); + // show caret even if the editor does not have the Keyboard Focus + textArea.Caret.Show(); + } + + void SearchLayerKeyDown(object sender, KeyEventArgs e) { + switch (e.Key) { + case Key.Enter: + e.Handled = true; + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) + FindPrevious(); + else + FindNext(); + if (searchTextBox != null) { + var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); + if (error != null) { + messageView.Content = Localization.ErrorText + " " + error.ErrorContent; + messageView.PlacementTarget = searchTextBox; + messageView.IsOpen = true; + } + } + break; + case Key.Escape: + e.Handled = true; + Close(); + break; + } + } + + /// + /// Gets whether the Panel is already closed. + /// + public bool IsClosed { get; private set; } + + /// + /// Closes the SearchPanel. + /// + public void Close() { + bool hasFocus = this.IsKeyboardFocusWithin; + + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Remove(adorner); + messageView.IsOpen = false; + textArea.TextView.BackgroundRenderers.Remove(renderer); + if (hasFocus) + textArea.Focus(); + IsClosed = true; + + // Clear existing search results so that the segments don't have to be maintained + renderer.CurrentResults.Clear(); + } + + /// + /// Closes the SearchPanel and removes it. + /// + [Obsolete("Use the Uninstall method instead!")] + public void CloseAndRemove() { + Close(); + textArea.DocumentChanged -= textArea_DocumentChanged; + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + } + + /// + /// Opens the an existing search panel. + /// + public void Open() { + if (!IsClosed) return; + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Add(adorner); + textArea.TextView.BackgroundRenderers.Add(renderer); + IsClosed = false; + DoSearch(false); + } + + /// + /// Fired when SearchOptions are changed inside the SearchPanel. + /// + public event EventHandler SearchOptionsChanged; + + /// + /// Raises the event. + /// + protected virtual void OnSearchOptionsChanged(SearchOptionsChangedEventArgs e) { + if (SearchOptionsChanged != null) { + SearchOptionsChanged(this, e); + } + } + } + + /// + /// EventArgs for event. + /// + public class SearchOptionsChangedEventArgs : EventArgs { + /// + /// Gets the search pattern. + /// + public string SearchPattern { get; private set; } + + /// + /// Gets whether the search pattern should be interpreted case-sensitive. + /// + public bool MatchCase { get; private set; } + + /// + /// Gets whether the search pattern should be interpreted as regular expression. + /// + public bool UseRegex { get; private set; } + + /// + /// Gets whether the search pattern should only match whole words. + /// + public bool WholeWords { get; private set; } + + /// + /// Creates a new SearchOptionsChangedEventArgs instance. + /// + public SearchOptionsChangedEventArgs(string searchPattern, bool matchCase, bool useRegex, bool wholeWords) { + this.SearchPattern = searchPattern; + this.MatchCase = matchCase; + this.UseRegex = useRegex; + this.WholeWords = wholeWords; + } + } + + class SearchPanelAdorner : Adorner { + SearchPanel panel; + + public SearchPanelAdorner(TextArea textArea, SearchPanel panel) + : base(textArea) { + this.panel = panel; + AddVisualChild(panel); + } + + protected override int VisualChildrenCount { + get { return 1; } + } + + protected override Visual GetVisualChild(int index) { + if (index != 0) + throw new ArgumentOutOfRangeException(); + return panel; + } + + protected override Size ArrangeOverride(Size finalSize) { + panel.Arrange(new Rect(new Point(0, 0), finalSize)); + return new Size(panel.ActualWidth, panel.ActualHeight); + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml index 3e7e19f8..0f3e1b37 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml @@ -1,47 +1,47 @@  - + + + + + + + + \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs b/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs index e9c8d215..479c7f14 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs @@ -27,68 +27,64 @@ using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; -namespace ICSharpCode.AvalonEdit.Search -{ - class SearchResultBackgroundRenderer : IBackgroundRenderer - { - TextSegmentCollection currentResults = new TextSegmentCollection(); - - public TextSegmentCollection CurrentResults { - get { return currentResults; } - } - - public KnownLayer Layer { - get { - // draw behind selection - return KnownLayer.Selection; - } - } - - public SearchResultBackgroundRenderer() - { - markerBrush = Brushes.LightGreen; - markerPen = new Pen(markerBrush, 1); - } - - Brush markerBrush; - Pen markerPen; - - public Brush MarkerBrush { - get { return markerBrush; } - set { - this.markerBrush = value; - markerPen = new Pen(markerBrush, 1); - } - } - - public void Draw(TextView textView, DrawingContext drawingContext) - { - if (textView == null) - throw new ArgumentNullException("textView"); - if (drawingContext == null) - throw new ArgumentNullException("drawingContext"); - - if (currentResults == null || !textView.VisualLinesValid) - return; - - var visualLines = textView.VisualLines; - if (visualLines.Count == 0) - return; - - int viewStart = visualLines.First().FirstDocumentLine.Offset; - int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; - - foreach (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { - BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); - geoBuilder.AlignToWholePixels = true; - geoBuilder.BorderThickness = markerPen != null ? markerPen.Thickness : 0; - geoBuilder.CornerRadius = 3; - geoBuilder.AddSegment(textView, result); - Geometry geometry = geoBuilder.CreateGeometry(); - if (geometry != null) { - drawingContext.DrawGeometry(markerBrush, markerPen, geometry); - } - } - } - } +namespace ICSharpCode.AvalonEdit.Search { + class SearchResultBackgroundRenderer : IBackgroundRenderer { + TextSegmentCollection currentResults = new TextSegmentCollection(); + + public TextSegmentCollection CurrentResults { + get { return currentResults; } + } + + public KnownLayer Layer { + get { + // draw behind selection + return KnownLayer.Selection; + } + } + + public SearchResultBackgroundRenderer() { + markerBrush = Brushes.LightGreen; + markerPen = new Pen(markerBrush, 1); + } + + Brush markerBrush; + Pen markerPen; + + public Brush MarkerBrush { + get { return markerBrush; } + set { + this.markerBrush = value; + markerPen = new Pen(markerBrush, 1); + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) { + if (textView == null) + throw new ArgumentNullException("textView"); + if (drawingContext == null) + throw new ArgumentNullException("drawingContext"); + + if (currentResults == null || !textView.VisualLinesValid) + return; + + var visualLines = textView.VisualLines; + if (visualLines.Count == 0) + return; + + int viewStart = visualLines.First().FirstDocumentLine.Offset; + int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; + + foreach (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToWholePixels = true; + geoBuilder.BorderThickness = markerPen != null ? markerPen.Thickness : 0; + geoBuilder.CornerRadius = 3; + geoBuilder.AddSegment(textView, result); + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(markerBrush, markerPen, geometry); + } + } + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs b/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs index 4c45a147..89e4dddf 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs @@ -22,62 +22,58 @@ using System.Windows.Controls; using ICSharpCode.AvalonEdit.Document; -namespace ICSharpCode.AvalonEdit.Search -{ - /// - /// Provides factory methods for ISearchStrategies. - /// - public static class SearchStrategyFactory - { - /// - /// Creates a default ISearchStrategy with the given parameters. - /// - public static ISearchStrategy Create(string searchPattern, bool ignoreCase, bool matchWholeWords, SearchMode mode) - { - if (searchPattern == null) - throw new ArgumentNullException("searchPattern"); - RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline; - if (ignoreCase) - options |= RegexOptions.IgnoreCase; - - switch (mode) { - case SearchMode.Normal: - searchPattern = Regex.Escape(searchPattern); - break; - case SearchMode.Wildcard: - searchPattern = ConvertWildcardsToRegex(searchPattern); - break; - } - try { - Regex pattern = new Regex(searchPattern, options); - return new RegexSearchStrategy(pattern, matchWholeWords); - } catch (ArgumentException ex) { - throw new SearchPatternException(ex.Message, ex); - } - } - - static string ConvertWildcardsToRegex(string searchPattern) - { - if (string.IsNullOrEmpty(searchPattern)) - return ""; - - StringBuilder builder = new StringBuilder(); - - foreach (char ch in searchPattern) { - switch (ch) { - case '?': - builder.Append("."); - break; - case '*': - builder.Append(".*"); - break; - default: - builder.Append(Regex.Escape(ch.ToString())); - break; - } - } - - return builder.ToString(); - } - } +namespace ICSharpCode.AvalonEdit.Search { + /// + /// Provides factory methods for ISearchStrategies. + /// + public static class SearchStrategyFactory { + /// + /// Creates a default ISearchStrategy with the given parameters. + /// + public static ISearchStrategy Create(string searchPattern, bool ignoreCase, bool matchWholeWords, SearchMode mode) { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline; + if (ignoreCase) + options |= RegexOptions.IgnoreCase; + + switch (mode) { + case SearchMode.Normal: + searchPattern = Regex.Escape(searchPattern); + break; + case SearchMode.Wildcard: + searchPattern = ConvertWildcardsToRegex(searchPattern); + break; + } + try { + Regex pattern = new Regex(searchPattern, options); + return new RegexSearchStrategy(pattern, matchWholeWords); + } catch (ArgumentException ex) { + throw new SearchPatternException(ex.Message, ex); + } + } + + static string ConvertWildcardsToRegex(string searchPattern) { + if (string.IsNullOrEmpty(searchPattern)) + return ""; + + StringBuilder builder = new StringBuilder(); + + foreach (char ch in searchPattern) { + switch (ch) { + case '?': + builder.Append("."); + break; + case '*': + builder.Append(".*"); + break; + default: + builder.Append(Regex.Escape(ch.ToString())); + break; + } + } + + return builder.ToString(); + } + } } From 1d609fceef5452f2e9aa249b3122b211b72d8cfc Mon Sep 17 00:00:00 2001 From: Christoph Stephan Date: Fri, 18 Nov 2016 13:09:46 +0100 Subject: [PATCH 03/11] extend search panel by replace posibility --- ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs | 2 + .../ICSharpCode.AvalonEdit.csproj | 6 ++ ICSharpCode.AvalonEdit/Search/Localization.cs | 14 +++ .../Search/SearchCommands.cs | 45 +++++++- ICSharpCode.AvalonEdit/Search/SearchPanel.cs | 97 ++++++++++++++++-- .../Search/SearchPanel.xaml | 88 ++++++++++------ ICSharpCode.AvalonEdit/Search/replaceall.png | Bin 0 -> 971 bytes ICSharpCode.AvalonEdit/Search/replacenext.png | Bin 0 -> 956 bytes 8 files changed, 207 insertions(+), 45 deletions(-) create mode 100644 ICSharpCode.AvalonEdit/Search/replaceall.png create mode 100644 ICSharpCode.AvalonEdit/Search/replacenext.png diff --git a/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs b/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs index 6bc34435..4f3f3327 100644 --- a/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs +++ b/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs @@ -70,6 +70,8 @@ public Window1() { foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2); foldingUpdateTimer.Tick += delegate { UpdateFoldings(); }; foldingUpdateTimer.Start(); + + Search.SearchPanel.Install(textEditor); } string currentFileName; diff --git a/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index f0b7c642..ddbd149c 100644 --- a/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -449,4 +449,10 @@ + + + + + + \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit/Search/Localization.cs b/ICSharpCode.AvalonEdit/Search/Localization.cs index d965ddaf..32885377 100644 --- a/ICSharpCode.AvalonEdit/Search/Localization.cs +++ b/ICSharpCode.AvalonEdit/Search/Localization.cs @@ -60,6 +60,20 @@ public virtual string FindPreviousText { get { return "Find previous (Shift+F3)"; } } + /// + /// Default: 'Replace next (ALT+R)' + /// + public virtual string ReplaceNextText { + get { return "Replace next (ALT+R)"; } + } + + /// + /// Default: 'Replace all (ALT+A)' + /// + public virtual string ReplaceAllText { + get { return "Replace all (ALT+A)"; } + } + /// /// Default: 'Error: ' /// diff --git a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs index 8a3ab735..43ef5855 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs @@ -49,6 +49,22 @@ public static class SearchCommands { new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) } ); + /// + /// Replaces the current occurrence and finds the next occurrence in the file. + /// + public static readonly RoutedCommand ReplaceNext = new RoutedCommand( + "ReplaceNext", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.R, ModifierKeys.Alt) } + ); + + /// + /// Replaces all occurrence in the file. + /// + public static readonly RoutedCommand ReplaceAll = new RoutedCommand( + "ReplaceAll", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.A, ModifierKeys.Alt) } + ); + /// /// Closes the SearchPanel. /// @@ -80,21 +96,34 @@ internal SearchInputHandler(TextArea textArea, SearchPanel panel) internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) { commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel)); } void RegisterCommands(ICollection commandBindings) { commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel)); commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel, CanExecuteWithOpenSearchPanel)); } SearchPanel panel; void ExecuteFind(object sender, ExecutedRoutedEventArgs e) { - panel.Open(); + panel.Open(false); + if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) + panel.SearchPattern = TextArea.Selection.GetText(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); + } + + void ExecuteReplace(object sender, ExecutedRoutedEventArgs e) { + panel.Open(true); if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) panel.SearchPattern = TextArea.Selection.GetText(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); @@ -125,6 +154,20 @@ void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) { } } + void ExecuteReplaceNext(object sender, ExecutedRoutedEventArgs e) { + if (!panel.IsClosed) { + panel.ReplaceNext(); + e.Handled = true; + } + } + + void ExecuteReplaceAll(object sender, ExecutedRoutedEventArgs e) { + if (!panel.IsClosed) { + panel.ReplaceAll(); + e.Handled = true; + } + } + void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) { if (!panel.IsClosed) { panel.Close(); diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs index 89f6c01e..5c821780 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs @@ -44,6 +44,7 @@ public class SearchPanel : Control { TextDocument currentDocument; SearchResultBackgroundRenderer renderer; TextBox searchTextBox; + TextBox replaceTextBox; SearchPanelAdorner adorner; #region DependencyProperties @@ -107,6 +108,36 @@ public string SearchPattern { set { SetValue(SearchPatternProperty, value); } } + /// + /// Dependency property for . + /// + public static readonly DependencyProperty ReplacementProperty = + DependencyProperty.Register("Replacement", typeof(string), typeof(SearchPanel), + new FrameworkPropertyMetadata("")); + + /// + /// Gets/sets the replacement. + /// + public string Replacement { + get { return (string)GetValue(ReplacementProperty); } + set { SetValue(ReplacementProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty ShowReplaceProperty = + DependencyProperty.Register("ShowReplace", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false)); + + /// + /// Gets/sets whether the replace is shown. + /// + public bool ShowReplace { + get { return (bool)GetValue(ShowReplaceProperty); } + set { SetValue(ShowReplaceProperty, value); } + } + /// /// Dependency property for . /// @@ -238,6 +269,8 @@ void AttachInternal(TextArea textArea) { this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, (sender, e) => ReplaceNext())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, (sender, e) => ReplaceAll())); this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); IsClosed = true; } @@ -260,6 +293,7 @@ void textArea_Document_TextChanged(object sender, EventArgs e) { public override void OnApplyTemplate() { base.OnApplyTemplate(); searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; + replaceTextBox = Template.FindName("PART_replaceTextBox", this) as TextBox; } void ValidateSearchText() { @@ -311,6 +345,41 @@ public void FindPrevious() { } } + /// + /// Replaces current result if any and moves to the next occurrence in the file. + /// + public int ReplaceNext() { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); + var count = renderer.CurrentResults.Count; + if (result != null + && textArea.Document.GetOffset(textArea.Selection.StartPosition.Location) == result.StartOffset + && textArea.Document.GetOffset(textArea.Selection.EndPosition.Location) == result.EndOffset) { + Replace(result); + --count; + } + result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + textArea.Selection.Length); + if (result == null) + result = renderer.CurrentResults.FirstSegment; + if (result != null) { + SelectResult(result); + return count; + } + return 0; + } + + /// + /// Replaces all occurrences in the file. + /// + public void ReplaceAll() { + var count = ReplaceNext(); + while (count-- > 0) + ReplaceNext(); + } + + void Replace(SearchResult result) { + currentDocument.Replace(textArea.Selection.Segments.FirstOrDefault(), result.ReplaceWith(Replacement)); + } + ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = true, Focusable = false }; void DoSearch(bool changeSelection) { @@ -353,16 +422,21 @@ void SearchLayerKeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.Enter: e.Handled = true; - if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) - FindPrevious(); - else - FindNext(); - if (searchTextBox != null) { - var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); - if (error != null) { - messageView.Content = Localization.ErrorText + " " + error.ErrorContent; - messageView.PlacementTarget = searchTextBox; - messageView.IsOpen = true; + if (replaceTextBox != null + && replaceTextBox.IsFocused) { + ReplaceNext(); + } else { + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) + FindPrevious(); + else + FindNext(); + if (searchTextBox != null) { + var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); + if (error != null) { + messageView.Content = Localization.ErrorText + " " + error.ErrorContent; + messageView.PlacementTarget = searchTextBox; + messageView.IsOpen = true; + } } } break; @@ -411,7 +485,8 @@ public void CloseAndRemove() { /// /// Opens the an existing search panel. /// - public void Open() { + public void Open(bool showReplace) { + ShowReplace = showReplace; if (!IsClosed) return; var layer = AdornerLayer.GetAdornerLayer(textArea); if (layer != null) diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml index 0f3e1b37..e037c263 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml @@ -4,40 +4,62 @@ + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.AvalonEdit/Search/replaceall.png b/ICSharpCode.AvalonEdit/Search/replaceall.png new file mode 100644 index 0000000000000000000000000000000000000000..9d4446f127a854bbdf1c1549a0e4ffeb57ab8489 GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl4m>B|mLR^7dGp`f|hX1NQGc|*j z>xC{djag}vyu~hMqrJx&+p;G@xOK5zs?PRr|21sKj0SP5aeOj zQCtEPa`AL=43W58+jCNoL4k+qB|mLR^7dGp`f|hX1NQGc|*j z>xC{djag}vyu~hMqrJx&+p;G@xOK5zs?PRr|21sKj0SP;FDc) zjQ<2s$kfxtF+}2WazX>Mn?OQ>gIGg?4pWRm!;)6+P}UGBPwt-nm8|Lx#k@26AKI}6 fv0gaiu$`5mub1JJqRx9spaBe?u6{1-oD!M Date: Fri, 18 Nov 2016 13:11:04 +0100 Subject: [PATCH 04/11] allow switch between both modes --- .../Search/SearchCommands.cs | 20 +++++++++++++++++++ ICSharpCode.AvalonEdit/Search/SearchPanel.cs | 2 ++ 2 files changed, 22 insertions(+) diff --git a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs index 43ef5855..bb78fd10 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs @@ -33,6 +33,22 @@ namespace ICSharpCode.AvalonEdit.Search { /// Search commands for AvalonEdit. /// public static class SearchCommands { + /// + /// Opens the Find panel + /// + public static readonly RoutedCommand Find = new RoutedCommand( + "Find", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F, ModifierKeys.Control) } + ); + + /// + /// Opens the Replace panel + /// + public static readonly RoutedCommand Replace = new RoutedCommand( + "Replace", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.H, ModifierKeys.Control) } + ); + /// /// Finds the next occurrence in the file. /// @@ -97,6 +113,8 @@ internal SearchInputHandler(TextArea textArea, SearchPanel panel) internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) { commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); + commandBindings.Add(new CommandBinding(SearchCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.Replace, ExecuteReplace)); commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); @@ -106,6 +124,8 @@ internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) { void RegisterCommands(ICollection commandBindings) { commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); + commandBindings.Add(new CommandBinding(SearchCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.Replace, ExecuteReplace)); commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs index 5c821780..4c3a069f 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs @@ -267,6 +267,8 @@ void AttachInternal(TextArea textArea) { textArea.DocumentChanged += textArea_DocumentChanged; KeyDown += SearchLayerKeyDown; + this.CommandBindings.Add(new CommandBinding(SearchCommands.Find, (sender, e) => Open(false))); + this.CommandBindings.Add(new CommandBinding(SearchCommands.Replace, (sender, e) => Open(true))); this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, (sender, e) => ReplaceNext())); From 71c5b35eb17667e22ce59463a0cb03bd01773bd9 Mon Sep 17 00:00:00 2001 From: Christoph Stephan Date: Fri, 18 Nov 2016 13:12:32 +0100 Subject: [PATCH 05/11] Fix: tooltip in replace mode --- ICSharpCode.AvalonEdit/Search/SearchPanel.cs | 6 ++++-- ICSharpCode.AvalonEdit/Search/SearchPanel.xaml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs index 4c3a069f..45a2bfa7 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs @@ -45,6 +45,7 @@ public class SearchPanel : Control { SearchResultBackgroundRenderer renderer; TextBox searchTextBox; TextBox replaceTextBox; + Border searchPanel; SearchPanelAdorner adorner; #region DependencyProperties @@ -294,6 +295,7 @@ void textArea_Document_TextChanged(object sender, EventArgs e) { /// public override void OnApplyTemplate() { base.OnApplyTemplate(); + searchPanel = Template.FindName("PART_searchPanel", this) as Border; searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; replaceTextBox = Template.FindName("PART_replaceTextBox", this) as TextBox; } @@ -405,7 +407,7 @@ void DoSearch(bool changeSelection) { if (!renderer.CurrentResults.Any()) { messageView.IsOpen = true; messageView.Content = Localization.NoMatchesFoundText; - messageView.PlacementTarget = searchTextBox; + messageView.PlacementTarget = searchPanel; } else messageView.IsOpen = false; } @@ -436,7 +438,7 @@ void SearchLayerKeyDown(object sender, KeyEventArgs e) { var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); if (error != null) { messageView.Content = Localization.ErrorText + " " + error.ErrorContent; - messageView.PlacementTarget = searchTextBox; + messageView.PlacementTarget = searchPanel; messageView.IsOpen = true; } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml index e037c263..d2a641da 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml @@ -7,7 +7,7 @@ - + From 0509e01758fe6da49cb3454bc9fc81e428cd2d42 Mon Sep 17 00:00:00 2001 From: Christoph Stephan Date: Fri, 18 Nov 2016 14:13:39 +0100 Subject: [PATCH 06/11] adjust formatting to fit the CI tests --- .gitignore | 283 +---- ICSharpCode.AvalonEdit.Sample/Window1.xaml | 122 +- ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs | 332 ++--- .../Search/FindTests.cs | 199 +-- .../Search/DropDownButton.cs | 127 +- .../Search/ISearchStrategy.cs | 145 ++- ICSharpCode.AvalonEdit/Search/Localization.cs | 136 +- .../Search/RegexSearchStrategy.cs | 105 +- .../Search/SearchCommands.cs | 362 +++--- ICSharpCode.AvalonEdit/Search/SearchPanel.cs | 1113 +++++++++-------- .../Search/SearchPanel.xaml | 132 +- .../Search/SearchResultBackgroundRenderer.cs | 124 +- .../Search/SearchStrategyFactory.cs | 112 +- 13 files changed, 1554 insertions(+), 1738 deletions(-) diff --git a/.gitignore b/.gitignore index 709bd2ab..f079a88c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,275 +1,8 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -project.fragment.lock.json -artifacts/ -Properties/launchSettings.json - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/ - -### VisualStudio Patch ### -build/ +# ignore all obj and bin folders (even in subdirectories) +obj/ +bin/ +/Documentation/Help +/packages/AvalonEdit +/packages/AvalonEdit.Sample +/packages/NUnit.2.6.3 +/Documentation/IntelliSense diff --git a/ICSharpCode.AvalonEdit.Sample/Window1.xaml b/ICSharpCode.AvalonEdit.Sample/Window1.xaml index c802e00b..984a27ae 100644 --- a/ICSharpCode.AvalonEdit.Sample/Window1.xaml +++ b/ICSharpCode.AvalonEdit.Sample/Window1.xaml @@ -24,82 +24,66 @@ xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" Title="AvalonEdit.Sample" Height="500" Width="700" > - - - - - - - - - - - - - - - - - - - - - # - - - - - + + + + + + + + + + + + + + + + + + + + # + + + + + - - - - - - - + + + + + + Welcome to AvalonEdit! - - - - + + + - TextEditor - TextArea - Options - - - - - - - + TextEditor + TextArea + Options + + + + + + + \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs b/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs index 4f3f3327..1468feff 100644 --- a/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs +++ b/ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs @@ -31,166 +31,176 @@ using ICSharpCode.AvalonEdit.Highlighting; using Microsoft.Win32; -namespace ICSharpCode.AvalonEdit.Sample { - /// - /// Interaction logic for Window1.xaml - /// - public partial class Window1 : Window { - public Window1() { - // Load our custom highlighting definition - IHighlightingDefinition customHighlighting; - using (Stream s = typeof(Window1).Assembly.GetManifestResourceStream("AvalonEdit.Sample.CustomHighlighting.xshd")) { - if (s == null) - throw new InvalidOperationException("Could not find embedded resource"); - using (XmlReader reader = new XmlTextReader(s)) { - customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd. - HighlightingLoader.Load(reader, HighlightingManager.Instance); - } - } - // and register it in the HighlightingManager - HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".cool" }, customHighlighting); - - - InitializeComponent(); -#if DOTNET4 +namespace ICSharpCode.AvalonEdit.Sample +{ + /// + /// Interaction logic for Window1.xaml + /// + public partial class Window1 : Window + { + public Window1() + { + // Load our custom highlighting definition + IHighlightingDefinition customHighlighting; + using (Stream s = typeof(Window1).Assembly.GetManifestResourceStream("AvalonEdit.Sample.CustomHighlighting.xshd")) { + if (s == null) + throw new InvalidOperationException("Could not find embedded resource"); + using (XmlReader reader = new XmlTextReader(s)) { + customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd. + HighlightingLoader.Load(reader, HighlightingManager.Instance); + } + } + // and register it in the HighlightingManager + HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".cool" }, customHighlighting); + + + InitializeComponent(); + #if DOTNET4 this.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display); -#endif - propertyGridComboBox.SelectedIndex = 2; - - //textEditor.TextArea.SelectionBorder = null; - - //textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#"); - //textEditor.SyntaxHighlighting = customHighlighting; - // initial highlighting now set by XAML - - textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering; - textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered; - - DispatcherTimer foldingUpdateTimer = new DispatcherTimer(); - foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2); - foldingUpdateTimer.Tick += delegate { UpdateFoldings(); }; - foldingUpdateTimer.Start(); - - Search.SearchPanel.Install(textEditor); - } - - string currentFileName; - - void openFileClick(object sender, RoutedEventArgs e) { - OpenFileDialog dlg = new OpenFileDialog(); - dlg.CheckFileExists = true; - if (dlg.ShowDialog() ?? false) { - currentFileName = dlg.FileName; - textEditor.Load(currentFileName); - textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(Path.GetExtension(currentFileName)); - } - } - - void saveFileClick(object sender, EventArgs e) { - if (currentFileName == null) { - SaveFileDialog dlg = new SaveFileDialog(); - dlg.DefaultExt = ".txt"; - if (dlg.ShowDialog() ?? false) { - currentFileName = dlg.FileName; - } else { - return; - } - } - textEditor.Save(currentFileName); - } - - void propertyGridComboBoxSelectionChanged(object sender, RoutedEventArgs e) { - if (propertyGrid == null) - return; - switch (propertyGridComboBox.SelectedIndex) { - case 0: - propertyGrid.SelectedObject = textEditor; - break; - case 1: - propertyGrid.SelectedObject = textEditor.TextArea; - break; - case 2: - propertyGrid.SelectedObject = textEditor.Options; - break; - } - } - - CompletionWindow completionWindow; - - void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e) { - if (e.Text == ".") { - // open code completion after the user has pressed dot: - completionWindow = new CompletionWindow(textEditor.TextArea); - // provide AvalonEdit with the data: - IList data = completionWindow.CompletionList.CompletionData; - data.Add(new MyCompletionData("Item1")); - data.Add(new MyCompletionData("Item2")); - data.Add(new MyCompletionData("Item3")); - data.Add(new MyCompletionData("Another item")); - completionWindow.Show(); - completionWindow.Closed += delegate { - completionWindow = null; - }; - } - } - - void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e) { - if (e.Text.Length > 0 && completionWindow != null) { - if (!char.IsLetterOrDigit(e.Text[0])) { - // Whenever a non-letter is typed while the completion window is open, - // insert the currently selected element. - completionWindow.CompletionList.RequestInsertion(e); - } - } - // do not set e.Handled=true - we still want to insert the character that was typed - } - - #region Folding - FoldingManager foldingManager; - object foldingStrategy; - - void HighlightingComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (textEditor.SyntaxHighlighting == null) { - foldingStrategy = null; - } else { - switch (textEditor.SyntaxHighlighting.Name) { - case "XML": - foldingStrategy = new XmlFoldingStrategy(); - textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); - break; - case "C#": - case "C++": - case "PHP": - case "Java": - textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.CSharp.CSharpIndentationStrategy(textEditor.Options); - foldingStrategy = new BraceFoldingStrategy(); - break; - default: - textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); - foldingStrategy = null; - break; - } - } - if (foldingStrategy != null) { - if (foldingManager == null) - foldingManager = FoldingManager.Install(textEditor.TextArea); - UpdateFoldings(); - } else { - if (foldingManager != null) { - FoldingManager.Uninstall(foldingManager); - foldingManager = null; - } - } - } - - void UpdateFoldings() { - if (foldingStrategy is BraceFoldingStrategy) { - ((BraceFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); - } - if (foldingStrategy is XmlFoldingStrategy) { - ((XmlFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); - } - } - #endregion - } + #endif + propertyGridComboBox.SelectedIndex = 2; + + //textEditor.TextArea.SelectionBorder = null; + + //textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#"); + //textEditor.SyntaxHighlighting = customHighlighting; + // initial highlighting now set by XAML + + textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering; + textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered; + + DispatcherTimer foldingUpdateTimer = new DispatcherTimer(); + foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2); + foldingUpdateTimer.Tick += delegate { UpdateFoldings(); }; + foldingUpdateTimer.Start(); + + Search.SearchPanel.Install(textEditor); + } + + string currentFileName; + + void openFileClick(object sender, RoutedEventArgs e) + { + OpenFileDialog dlg = new OpenFileDialog(); + dlg.CheckFileExists = true; + if (dlg.ShowDialog() ?? false) { + currentFileName = dlg.FileName; + textEditor.Load(currentFileName); + textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(Path.GetExtension(currentFileName)); + } + } + + void saveFileClick(object sender, EventArgs e) + { + if (currentFileName == null) { + SaveFileDialog dlg = new SaveFileDialog(); + dlg.DefaultExt = ".txt"; + if (dlg.ShowDialog() ?? false) { + currentFileName = dlg.FileName; + } else { + return; + } + } + textEditor.Save(currentFileName); + } + + void propertyGridComboBoxSelectionChanged(object sender, RoutedEventArgs e) + { + if (propertyGrid == null) + return; + switch (propertyGridComboBox.SelectedIndex) { + case 0: + propertyGrid.SelectedObject = textEditor; + break; + case 1: + propertyGrid.SelectedObject = textEditor.TextArea; + break; + case 2: + propertyGrid.SelectedObject = textEditor.Options; + break; + } + } + + CompletionWindow completionWindow; + + void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e) + { + if (e.Text == ".") { + // open code completion after the user has pressed dot: + completionWindow = new CompletionWindow(textEditor.TextArea); + // provide AvalonEdit with the data: + IList data = completionWindow.CompletionList.CompletionData; + data.Add(new MyCompletionData("Item1")); + data.Add(new MyCompletionData("Item2")); + data.Add(new MyCompletionData("Item3")); + data.Add(new MyCompletionData("Another item")); + completionWindow.Show(); + completionWindow.Closed += delegate { + completionWindow = null; + }; + } + } + + void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e) + { + if (e.Text.Length > 0 && completionWindow != null) { + if (!char.IsLetterOrDigit(e.Text[0])) { + // Whenever a non-letter is typed while the completion window is open, + // insert the currently selected element. + completionWindow.CompletionList.RequestInsertion(e); + } + } + // do not set e.Handled=true - we still want to insert the character that was typed + } + + #region Folding + FoldingManager foldingManager; + object foldingStrategy; + + void HighlightingComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (textEditor.SyntaxHighlighting == null) { + foldingStrategy = null; + } else { + switch (textEditor.SyntaxHighlighting.Name) { + case "XML": + foldingStrategy = new XmlFoldingStrategy(); + textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); + break; + case "C#": + case "C++": + case "PHP": + case "Java": + textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.CSharp.CSharpIndentationStrategy(textEditor.Options); + foldingStrategy = new BraceFoldingStrategy(); + break; + default: + textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy(); + foldingStrategy = null; + break; + } + } + if (foldingStrategy != null) { + if (foldingManager == null) + foldingManager = FoldingManager.Install(textEditor.TextArea); + UpdateFoldings(); + } else { + if (foldingManager != null) { + FoldingManager.Uninstall(foldingManager); + foldingManager = null; + } + } + } + + void UpdateFoldings() + { + if (foldingStrategy is BraceFoldingStrategy) { + ((BraceFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); + } + if (foldingStrategy is XmlFoldingStrategy) { + ((XmlFoldingStrategy)foldingStrategy).UpdateFoldings(foldingManager, textEditor.Document); + } + } + #endregion + } } \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs b/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs index b7d82e4d..958baffa 100644 --- a/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs +++ b/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs @@ -24,98 +24,109 @@ #endif using NUnit.Framework; -namespace ICSharpCode.AvalonEdit.Search { - [TestFixture] - public class FindTests { - [Test] - public void SkipWordBorderSimple() { - var strategy = SearchStrategyFactory.Create("All", false, true, SearchMode.Normal); - var text = new StringTextSource(" FindAllTests "); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void SkipWordBorder() { - var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); - var text = new StringTextSource("name=\"{FindAllTests}\""); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void SkipWordBorder2() { - var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); - var text = new StringTextSource("name=\"FindAllTests "); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void SkipWordBorder3() { - var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); - var text = new StringTextSource(" // findtest"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.IsEmpty(results, "No results should be found!"); - } - - [Test] - public void WordBorderTest() { - var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); - var text = new StringTextSource(" // find me"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual(" ".Length, results[0].Offset); - Assert.AreEqual("// find".Length, results[0].Length); - } - - [Test] - public void ResultAtStart() { - var strategy = SearchStrategyFactory.Create("result", false, true, SearchMode.Normal); - var text = new StringTextSource("result // find me"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual(0, results[0].Offset); - Assert.AreEqual("result".Length, results[0].Length); - } - - [Test] - public void ResultAtEnd() { - var strategy = SearchStrategyFactory.Create("me", false, true, SearchMode.Normal); - var text = new StringTextSource("result // find me"); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual("result // find ".Length, results[0].Offset); - Assert.AreEqual("me".Length, results[0].Length); - } - - [Test] - public void TextWithDots() { - var strategy = SearchStrategyFactory.Create("Text", false, true, SearchMode.Normal); - var text = new StringTextSource(".Text."); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual(".".Length, results[0].Offset); - Assert.AreEqual("Text".Length, results[0].Length); - } - - [Test] - public void SimpleTest() { - var strategy = SearchStrategyFactory.Create("AllTests", false, false, SearchMode.Normal); - var text = new StringTextSource("name=\"FindAllTests "); - var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); - - Assert.AreEqual(1, results.Length, "One result should be found!"); - Assert.AreEqual("name=\"Find".Length, results[0].Offset); - Assert.AreEqual("AllTests".Length, results[0].Length); - } - } +namespace ICSharpCode.AvalonEdit.Search +{ + [TestFixture] + public class FindTests + { + [Test] + public void SkipWordBorderSimple() + { + var strategy = SearchStrategyFactory.Create("All", false, true, SearchMode.Normal); + var text = new StringTextSource(" FindAllTests "); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void SkipWordBorder() + { + var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); + var text = new StringTextSource("name=\"{FindAllTests}\""); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void SkipWordBorder2() + { + var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal); + var text = new StringTextSource("name=\"FindAllTests "); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void SkipWordBorder3() + { + var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); + var text = new StringTextSource(" // findtest"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.IsEmpty(results, "No results should be found!"); + } + + [Test] + public void WordBorderTest() + { + var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal); + var text = new StringTextSource(" // find me"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual(" ".Length, results[0].Offset); + Assert.AreEqual("// find".Length, results[0].Length); + } + + [Test] + public void ResultAtStart() + { + var strategy = SearchStrategyFactory.Create("result", false, true, SearchMode.Normal); + var text = new StringTextSource("result // find me"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual(0, results[0].Offset); + Assert.AreEqual("result".Length, results[0].Length); + } + + [Test] + public void ResultAtEnd() + { + var strategy = SearchStrategyFactory.Create("me", false, true, SearchMode.Normal); + var text = new StringTextSource("result // find me"); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual("result // find ".Length, results[0].Offset); + Assert.AreEqual("me".Length, results[0].Length); + } + + [Test] + public void TextWithDots() + { + var strategy = SearchStrategyFactory.Create("Text", false, true, SearchMode.Normal); + var text = new StringTextSource(".Text."); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual(".".Length, results[0].Offset); + Assert.AreEqual("Text".Length, results[0].Length); + } + + [Test] + public void SimpleTest() + { + var strategy = SearchStrategyFactory.Create("AllTests", false, false, SearchMode.Normal); + var text = new StringTextSource("name=\"FindAllTests "); + var results = strategy.FindAll(text, 0, text.TextLength).ToArray(); + + Assert.AreEqual(1, results.Length, "One result should be found!"); + Assert.AreEqual("name=\"Find".Length, results[0].Offset); + Assert.AreEqual("AllTests".Length, results[0].Length); + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/DropDownButton.cs b/ICSharpCode.AvalonEdit/Search/DropDownButton.cs index cd1802dc..31cb5f99 100644 --- a/ICSharpCode.AvalonEdit/Search/DropDownButton.cs +++ b/ICSharpCode.AvalonEdit/Search/DropDownButton.cs @@ -20,65 +20,70 @@ using System.Windows; using System.Windows.Controls.Primitives; -namespace ICSharpCode.AvalonEdit.Search { - /// - /// A button that opens a drop-down menu when clicked. - /// - public class DropDownButton : ButtonBase { - /// - /// Identifies the  dependency property. - /// - public static readonly DependencyProperty DropDownContentProperty - = DependencyProperty.Register("DropDownContent", typeof(Popup), - typeof(DropDownButton), new FrameworkPropertyMetadata(null)); - - /// - /// The key that identifies the  dependency property. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] - protected static readonly DependencyPropertyKey IsDropDownContentOpenPropertyKey - = DependencyProperty.RegisterReadOnly("IsDropDownContentOpen", typeof(bool), - typeof(DropDownButton), new FrameworkPropertyMetadata(false)); - - /// - /// Identifies the  dependency property. - /// - public static readonly DependencyProperty IsDropDownContentOpenProperty = IsDropDownContentOpenPropertyKey.DependencyProperty; - - static DropDownButton() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); - } - - /// - /// Gets/Sets the popup that is used as drop-down content. - /// - public Popup DropDownContent { - get { return (Popup)GetValue(DropDownContentProperty); } - set { SetValue(DropDownContentProperty, value); } - } - - /// - /// Gets whether the drop-down is opened. - /// - public bool IsDropDownContentOpen { - get { return (bool)GetValue(IsDropDownContentOpenProperty); } - protected set { SetValue(IsDropDownContentOpenPropertyKey, value); } - } - - /// - protected override void OnClick() { - if (DropDownContent != null && !IsDropDownContentOpen) { - DropDownContent.Placement = PlacementMode.Bottom; - DropDownContent.PlacementTarget = this; - DropDownContent.IsOpen = true; - DropDownContent.Closed += DropDownContent_Closed; - this.IsDropDownContentOpen = true; - } - } - - void DropDownContent_Closed(object sender, EventArgs e) { - ((Popup)sender).Closed -= DropDownContent_Closed; - this.IsDropDownContentOpen = false; - } - } +namespace ICSharpCode.AvalonEdit.Search +{ + /// + /// A button that opens a drop-down menu when clicked. + /// + public class DropDownButton : ButtonBase + { + /// + /// Identifies the  dependency property. + /// + public static readonly DependencyProperty DropDownContentProperty + = DependencyProperty.Register("DropDownContent", typeof(Popup), + typeof(DropDownButton), new FrameworkPropertyMetadata(null)); + + /// + /// The key that identifies the  dependency property. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + protected static readonly DependencyPropertyKey IsDropDownContentOpenPropertyKey + = DependencyProperty.RegisterReadOnly("IsDropDownContentOpen", typeof(bool), + typeof(DropDownButton), new FrameworkPropertyMetadata(false)); + + /// + /// Identifies the  dependency property. + /// + public static readonly DependencyProperty IsDropDownContentOpenProperty = IsDropDownContentOpenPropertyKey.DependencyProperty; + + static DropDownButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); + } + + /// + /// Gets/Sets the popup that is used as drop-down content. + /// + public Popup DropDownContent { + get { return (Popup)GetValue(DropDownContentProperty); } + set { SetValue(DropDownContentProperty, value); } + } + + /// + /// Gets whether the drop-down is opened. + /// + public bool IsDropDownContentOpen { + get { return (bool)GetValue(IsDropDownContentOpenProperty); } + protected set { SetValue(IsDropDownContentOpenPropertyKey, value); } + } + + /// + protected override void OnClick() + { + if (DropDownContent != null && !IsDropDownContentOpen) { + DropDownContent.Placement = PlacementMode.Bottom; + DropDownContent.PlacementTarget = this; + DropDownContent.IsOpen = true; + DropDownContent.Closed += DropDownContent_Closed; + this.IsDropDownContentOpen = true; + } + } + + void DropDownContent_Closed(object sender, EventArgs e) + { + ((Popup)sender).Closed -= DropDownContent_Closed; + this.IsDropDownContentOpen = false; + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs b/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs index 71440ae5..9121a454 100644 --- a/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs +++ b/ICSharpCode.AvalonEdit/Search/ISearchStrategy.cs @@ -22,73 +22,82 @@ using ICSharpCode.NRefactory.Editor; using ICSharpCode.AvalonEdit.Document; -namespace ICSharpCode.AvalonEdit.Search { - /// - /// Basic interface for search algorithms. - /// - public interface ISearchStrategy : IEquatable { - /// - /// Finds all matches in the given ITextSource and the given range. - /// - /// - /// This method must be implemented thread-safe. - /// All segments in the result must be within the given range, and they must be returned in order - /// (e.g. if two results are returned, EndOffset of first result must be less than or equal StartOffset of second result). - /// - IEnumerable FindAll(ITextSource document, int offset, int length); +namespace ICSharpCode.AvalonEdit.Search +{ + /// + /// Basic interface for search algorithms. + /// + public interface ISearchStrategy : IEquatable + { + /// + /// Finds all matches in the given ITextSource and the given range. + /// + /// + /// This method must be implemented thread-safe. + /// All segments in the result must be within the given range, and they must be returned in order + /// (e.g. if two results are returned, EndOffset of first result must be less than or equal StartOffset of second result). + /// + IEnumerable FindAll(ITextSource document, int offset, int length); + + /// + /// Finds the next match in the given ITextSource and the given range. + /// + /// This method must be implemented thread-safe. + ISearchResult FindNext(ITextSource document, int offset, int length); + } + + /// + /// Represents a search result. + /// + public interface ISearchResult : ISegment + { + /// + /// Replaces parts of the replacement string with parts from the match. (e.g. $1) + /// + string ReplaceWith(string replacement); + } + + /// + /// Defines supported search modes. + /// + public enum SearchMode + { + /// + /// Standard search + /// + Normal, + /// + /// RegEx search + /// + RegEx, + /// + /// Wildcard search + /// + Wildcard + } + + /// + public class SearchPatternException : Exception, ISerializable + { + /// + public SearchPatternException() + { + } + + /// + public SearchPatternException(string message) : base(message) + { + } + + /// + public SearchPatternException(string message, Exception innerException) : base(message, innerException) + { + } - /// - /// Finds the next match in the given ITextSource and the given range. - /// - /// This method must be implemented thread-safe. - ISearchResult FindNext(ITextSource document, int offset, int length); - } - - /// - /// Represents a search result. - /// - public interface ISearchResult : ISegment { - /// - /// Replaces parts of the replacement string with parts from the match. (e.g. $1) - /// - string ReplaceWith(string replacement); - } - - /// - /// Defines supported search modes. - /// - public enum SearchMode { - /// - /// Standard search - /// - Normal, - /// - /// RegEx search - /// - RegEx, - /// - /// Wildcard search - /// - Wildcard - } - - /// - public class SearchPatternException : Exception, ISerializable { - /// - public SearchPatternException() { - } - - /// - public SearchPatternException(string message) : base(message) { - } - - /// - public SearchPatternException(string message, Exception innerException) : base(message, innerException) { - } - - // This constructor is needed for serialization. - /// - protected SearchPatternException(SerializationInfo info, StreamingContext context) : base(info, context) { - } - } + // This constructor is needed for serialization. + /// + protected SearchPatternException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/Localization.cs b/ICSharpCode.AvalonEdit/Search/Localization.cs index 32885377..f7801b0a 100644 --- a/ICSharpCode.AvalonEdit/Search/Localization.cs +++ b/ICSharpCode.AvalonEdit/Search/Localization.cs @@ -19,73 +19,75 @@ using System; using System.ComponentModel; -namespace ICSharpCode.AvalonEdit.Search { - /// - /// Holds default texts for buttons and labels in the SearchPanel. Override properties to add other languages. - /// - public class Localization { - /// - /// Default: 'Match case' - /// - public virtual string MatchCaseText { - get { return "Match case"; } - } +namespace ICSharpCode.AvalonEdit.Search +{ + /// + /// Holds default texts for buttons and labels in the SearchPanel. Override properties to add other languages. + /// + public class Localization + { + /// + /// Default: 'Match case' + /// + public virtual string MatchCaseText { + get { return "Match case"; } + } + + /// + /// Default: 'Match whole words' + /// + public virtual string MatchWholeWordsText { + get { return "Match whole words"; } + } + + + /// + /// Default: 'Use regular expressions' + /// + public virtual string UseRegexText { + get { return "Use regular expressions"; } + } + + /// + /// Default: 'Find next (F3)' + /// + public virtual string FindNextText { + get { return "Find next (F3)"; } + } + + /// + /// Default: 'Find previous (Shift+F3)' + /// + public virtual string FindPreviousText { + get { return "Find previous (Shift+F3)"; } + } + + /// + /// Default: 'Replace next (ALT+R)' + /// + public virtual string ReplaceNextText { + get { return "Replace next (ALT+R)"; } + } - /// - /// Default: 'Match whole words' - /// - public virtual string MatchWholeWordsText { - get { return "Match whole words"; } - } + /// + /// Default: 'Replace all (ALT+A)' + /// + public virtual string ReplaceAllText { + get { return "Replace all (ALT+A)"; } + } - - /// - /// Default: 'Use regular expressions' - /// - public virtual string UseRegexText { - get { return "Use regular expressions"; } - } - - /// - /// Default: 'Find next (F3)' - /// - public virtual string FindNextText { - get { return "Find next (F3)"; } - } - - /// - /// Default: 'Find previous (Shift+F3)' - /// - public virtual string FindPreviousText { - get { return "Find previous (Shift+F3)"; } - } - - /// - /// Default: 'Replace next (ALT+R)' - /// - public virtual string ReplaceNextText { - get { return "Replace next (ALT+R)"; } - } - - /// - /// Default: 'Replace all (ALT+A)' - /// - public virtual string ReplaceAllText { - get { return "Replace all (ALT+A)"; } - } - - /// - /// Default: 'Error: ' - /// - public virtual string ErrorText { - get { return "Error: "; } - } - - /// - /// Default: 'No matches found!' - /// - public virtual string NoMatchesFoundText { - get { return "No matches found!"; } - } - } + /// + /// Default: 'Error: ' + /// + public virtual string ErrorText { + get { return "Error: "; } + } + + /// + /// Default: 'No matches found!' + /// + public virtual string NoMatchesFoundText { + get { return "No matches found!"; } + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs b/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs index ada35ee5..47bde5af 100644 --- a/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs +++ b/ICSharpCode.AvalonEdit/Search/RegexSearchStrategy.cs @@ -25,52 +25,61 @@ using ICSharpCode.AvalonEdit.Document; using ICSharpCode.NRefactory.Editor; -namespace ICSharpCode.AvalonEdit.Search { - class RegexSearchStrategy : ISearchStrategy { - readonly Regex searchPattern; - readonly bool matchWholeWords; - - public RegexSearchStrategy(Regex searchPattern, bool matchWholeWords) { - if (searchPattern == null) - throw new ArgumentNullException("searchPattern"); - this.searchPattern = searchPattern; - this.matchWholeWords = matchWholeWords; - } - - public IEnumerable FindAll(ITextSource document, int offset, int length) { - int endOffset = offset + length; - foreach (Match result in searchPattern.Matches(document.Text)) { - int resultEndOffset = result.Length + result.Index; - if (offset > result.Index || endOffset < resultEndOffset) - continue; - if (matchWholeWords && (!IsWordBorder(document, result.Index) || !IsWordBorder(document, resultEndOffset))) - continue; - yield return new SearchResult { StartOffset = result.Index, Length = result.Length, Data = result }; - } - } - - static bool IsWordBorder(ITextSource document, int offset) { - return TextUtilities.GetNextCaretPosition(document, offset - 1, LogicalDirection.Forward, CaretPositioningMode.WordBorder) == offset; - } - - public ISearchResult FindNext(ITextSource document, int offset, int length) { - return FindAll(document, offset, length).FirstOrDefault(); - } - - public bool Equals(ISearchStrategy other) { - var strategy = other as RegexSearchStrategy; - return strategy != null && - strategy.searchPattern.ToString() == searchPattern.ToString() && - strategy.searchPattern.Options == searchPattern.Options && - strategy.searchPattern.RightToLeft == searchPattern.RightToLeft; - } - } - - class SearchResult : TextSegment, ISearchResult { - public Match Data { get; set; } - - public string ReplaceWith(string replacement) { - return Data.Result(replacement); - } - } +namespace ICSharpCode.AvalonEdit.Search +{ + class RegexSearchStrategy : ISearchStrategy + { + readonly Regex searchPattern; + readonly bool matchWholeWords; + + public RegexSearchStrategy(Regex searchPattern, bool matchWholeWords) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + this.searchPattern = searchPattern; + this.matchWholeWords = matchWholeWords; + } + + public IEnumerable FindAll(ITextSource document, int offset, int length) + { + int endOffset = offset + length; + foreach (Match result in searchPattern.Matches(document.Text)) { + int resultEndOffset = result.Length + result.Index; + if (offset > result.Index || endOffset < resultEndOffset) + continue; + if (matchWholeWords && (!IsWordBorder(document, result.Index) || !IsWordBorder(document, resultEndOffset))) + continue; + yield return new SearchResult { StartOffset = result.Index, Length = result.Length, Data = result }; + } + } + + static bool IsWordBorder(ITextSource document, int offset) + { + return TextUtilities.GetNextCaretPosition(document, offset - 1, LogicalDirection.Forward, CaretPositioningMode.WordBorder) == offset; + } + + public ISearchResult FindNext(ITextSource document, int offset, int length) + { + return FindAll(document, offset, length).FirstOrDefault(); + } + + public bool Equals(ISearchStrategy other) + { + var strategy = other as RegexSearchStrategy; + return strategy != null && + strategy.searchPattern.ToString() == searchPattern.ToString() && + strategy.searchPattern.Options == searchPattern.Options && + strategy.searchPattern.RightToLeft == searchPattern.RightToLeft; + } + } + + class SearchResult : TextSegment, ISearchResult + { + public Match Data { get; set; } + + public string ReplaceWith(string replacement) + { + return Data.Result(replacement); + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs index bb78fd10..18cbec19 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchCommands.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchCommands.cs @@ -28,179 +28,191 @@ using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; -namespace ICSharpCode.AvalonEdit.Search { - /// - /// Search commands for AvalonEdit. - /// - public static class SearchCommands { - /// - /// Opens the Find panel - /// - public static readonly RoutedCommand Find = new RoutedCommand( - "Find", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.F, ModifierKeys.Control) } - ); - - /// - /// Opens the Replace panel - /// - public static readonly RoutedCommand Replace = new RoutedCommand( - "Replace", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.H, ModifierKeys.Control) } - ); - - /// - /// Finds the next occurrence in the file. - /// - public static readonly RoutedCommand FindNext = new RoutedCommand( - "FindNext", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.F3) } - ); - - /// - /// Finds the previous occurrence in the file. - /// - public static readonly RoutedCommand FindPrevious = new RoutedCommand( - "FindPrevious", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) } - ); - - /// - /// Replaces the current occurrence and finds the next occurrence in the file. - /// - public static readonly RoutedCommand ReplaceNext = new RoutedCommand( - "ReplaceNext", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.R, ModifierKeys.Alt) } - ); - - /// - /// Replaces all occurrence in the file. - /// - public static readonly RoutedCommand ReplaceAll = new RoutedCommand( - "ReplaceAll", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.A, ModifierKeys.Alt) } - ); - - /// - /// Closes the SearchPanel. - /// - public static readonly RoutedCommand CloseSearchPanel = new RoutedCommand( - "CloseSearchPanel", typeof(SearchPanel), - new InputGestureCollection { new KeyGesture(Key.Escape) } - ); - } - - /// - /// TextAreaInputHandler that registers all search-related commands. - /// - public class SearchInputHandler : TextAreaInputHandler { - /// - /// Creates a new SearchInputHandler and registers the search-related commands. - /// - [Obsolete("Use SearchPanel.Install instead")] - public SearchInputHandler(TextArea textArea) - : base(textArea) { - RegisterCommands(this.CommandBindings); - panel = SearchPanel.Install(textArea); - } - - internal SearchInputHandler(TextArea textArea, SearchPanel panel) - : base(textArea) { - RegisterCommands(this.CommandBindings); - this.panel = panel; - } - - internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) { - commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); - commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); - commandBindings.Add(new CommandBinding(SearchCommands.Find, ExecuteFind)); - commandBindings.Add(new CommandBinding(SearchCommands.Replace, ExecuteReplace)); - commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel)); - } - - void RegisterCommands(ICollection commandBindings) { - commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); - commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); - commandBindings.Add(new CommandBinding(SearchCommands.Find, ExecuteFind)); - commandBindings.Add(new CommandBinding(SearchCommands.Replace, ExecuteReplace)); - commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel)); - commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel, CanExecuteWithOpenSearchPanel)); - } - - SearchPanel panel; - - void ExecuteFind(object sender, ExecutedRoutedEventArgs e) { - panel.Open(false); - if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) - panel.SearchPattern = TextArea.Selection.GetText(); - Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); - } - - void ExecuteReplace(object sender, ExecutedRoutedEventArgs e) { - panel.Open(true); - if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) - panel.SearchPattern = TextArea.Selection.GetText(); - Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); - } - - void CanExecuteWithOpenSearchPanel(object sender, CanExecuteRoutedEventArgs e) { - if (panel.IsClosed) { - e.CanExecute = false; - // Continue routing so that the key gesture can be consumed by another component. - e.ContinueRouting = true; - } else { - e.CanExecute = true; - e.Handled = true; - } - } - - void ExecuteFindNext(object sender, ExecutedRoutedEventArgs e) { - if (!panel.IsClosed) { - panel.FindNext(); - e.Handled = true; - } - } - - void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) { - if (!panel.IsClosed) { - panel.FindPrevious(); - e.Handled = true; - } - } - - void ExecuteReplaceNext(object sender, ExecutedRoutedEventArgs e) { - if (!panel.IsClosed) { - panel.ReplaceNext(); - e.Handled = true; - } - } - - void ExecuteReplaceAll(object sender, ExecutedRoutedEventArgs e) { - if (!panel.IsClosed) { - panel.ReplaceAll(); - e.Handled = true; - } - } - - void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) { - if (!panel.IsClosed) { - panel.Close(); - e.Handled = true; - } - } - - /// - /// Fired when SearchOptions are modified inside the SearchPanel. - /// - public event EventHandler SearchOptionsChanged { - add { panel.SearchOptionsChanged += value; } - remove { panel.SearchOptionsChanged -= value; } - } - } +namespace ICSharpCode.AvalonEdit.Search +{ + /// + /// Search commands for AvalonEdit. + /// + public static class SearchCommands + { + /// + /// Opens the Find panel + /// + public static readonly RoutedCommand Find = new RoutedCommand( + "Find", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F, ModifierKeys.Control) } + ); + + /// + /// Opens the Replace panel + /// + public static readonly RoutedCommand Replace = new RoutedCommand( + "Replace", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.H, ModifierKeys.Control) } + ); + + /// + /// Finds the next occurrence in the file. + /// + public static readonly RoutedCommand FindNext = new RoutedCommand( + "FindNext", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3) } + ); + + /// + /// Finds the previous occurrence in the file. + /// + public static readonly RoutedCommand FindPrevious = new RoutedCommand( + "FindPrevious", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) } + ); + + /// + /// Replaces the current occurrence and finds the next occurrence in the file. + /// + public static readonly RoutedCommand ReplaceNext = new RoutedCommand( + "ReplaceNext", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.R, ModifierKeys.Alt) } + ); + + /// + /// Replaces all occurrence in the file. + /// + public static readonly RoutedCommand ReplaceAll = new RoutedCommand( + "ReplaceAll", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.A, ModifierKeys.Alt) } + ); + + /// + /// Closes the SearchPanel. + /// + public static readonly RoutedCommand CloseSearchPanel = new RoutedCommand( + "CloseSearchPanel", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.Escape) } + ); + } + + /// + /// TextAreaInputHandler that registers all search-related commands. + /// + public class SearchInputHandler : TextAreaInputHandler + { + /// + /// Creates a new SearchInputHandler and registers the search-related commands. + /// + [Obsolete("Use SearchPanel.Install instead")] + public SearchInputHandler(TextArea textArea) + : base(textArea) + { + RegisterCommands(this.CommandBindings); + panel = SearchPanel.Install(textArea); + } + + internal SearchInputHandler(TextArea textArea, SearchPanel panel) + : base(textArea) + { + RegisterCommands(this.CommandBindings); + this.panel = panel; + } + + internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) + { + commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); + commandBindings.Add(new CommandBinding(SearchCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.Replace, ExecuteReplace)); + commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel)); + } + + void RegisterCommands(ICollection commandBindings) + { + commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace)); + commandBindings.Add(new CommandBinding(SearchCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.Replace, ExecuteReplace)); + commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel)); + commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel, CanExecuteWithOpenSearchPanel)); + } + + SearchPanel panel; + + void ExecuteFind(object sender, ExecutedRoutedEventArgs e) + { + panel.Open(false); + if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) + panel.SearchPattern = TextArea.Selection.GetText(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); + } + + void ExecuteReplace(object sender, ExecutedRoutedEventArgs e) { + panel.Open(true); + if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) + panel.SearchPattern = TextArea.Selection.GetText(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); + } + + void CanExecuteWithOpenSearchPanel(object sender, CanExecuteRoutedEventArgs e) + { + if (panel.IsClosed) { + e.CanExecute = false; + // Continue routing so that the key gesture can be consumed by another component. + e.ContinueRouting = true; + } else { + e.CanExecute = true; + e.Handled = true; + } + } + + void ExecuteFindNext(object sender, ExecutedRoutedEventArgs e) + { + if (!panel.IsClosed) { + panel.FindNext(); + e.Handled = true; + } + } + + void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) + { + if (!panel.IsClosed) { + panel.FindPrevious(); + e.Handled = true; + } + } + + void ExecuteReplaceNext(object sender, ExecutedRoutedEventArgs e) { + if (!panel.IsClosed) { + panel.ReplaceNext(); + e.Handled = true; + } + } + + void ExecuteReplaceAll(object sender, ExecutedRoutedEventArgs e) { + if (!panel.IsClosed) { + panel.ReplaceAll(); + e.Handled = true; + } + } + + void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) + { + if (!panel.IsClosed) { + panel.Close(); + e.Handled = true; + } + } + + /// + /// Fired when SearchOptions are modified inside the SearchPanel. + /// + public event EventHandler SearchOptionsChanged { + add { panel.SearchOptionsChanged += value; } + remove { panel.SearchOptionsChanged -= value; } + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs index 45a2bfa7..f96c918f 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs @@ -34,544 +34,577 @@ using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Rendering; -namespace ICSharpCode.AvalonEdit.Search { - /// - /// Provides search functionality for AvalonEdit. It is displayed in the top-right corner of the TextArea. - /// - public class SearchPanel : Control { - TextArea textArea; - SearchInputHandler handler; - TextDocument currentDocument; - SearchResultBackgroundRenderer renderer; - TextBox searchTextBox; - TextBox replaceTextBox; - Border searchPanel; - SearchPanelAdorner adorner; - - #region DependencyProperties - /// - /// Dependency property for . - /// - public static readonly DependencyProperty UseRegexProperty = - DependencyProperty.Register("UseRegex", typeof(bool), typeof(SearchPanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should be interpreted as regular expression. - /// - public bool UseRegex { - get { return (bool)GetValue(UseRegexProperty); } - set { SetValue(UseRegexProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty MatchCaseProperty = - DependencyProperty.Register("MatchCase", typeof(bool), typeof(SearchPanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should be interpreted case-sensitive. - /// - public bool MatchCase { - get { return (bool)GetValue(MatchCaseProperty); } - set { SetValue(MatchCaseProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty WholeWordsProperty = - DependencyProperty.Register("WholeWords", typeof(bool), typeof(SearchPanel), - new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); - - /// - /// Gets/sets whether the search pattern should only match whole words. - /// - public bool WholeWords { - get { return (bool)GetValue(WholeWordsProperty); } - set { SetValue(WholeWordsProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty SearchPatternProperty = - DependencyProperty.Register("SearchPattern", typeof(string), typeof(SearchPanel), - new FrameworkPropertyMetadata("", SearchPatternChangedCallback)); - - /// - /// Gets/sets the search pattern. - /// - public string SearchPattern { - get { return (string)GetValue(SearchPatternProperty); } - set { SetValue(SearchPatternProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty ReplacementProperty = - DependencyProperty.Register("Replacement", typeof(string), typeof(SearchPanel), - new FrameworkPropertyMetadata("")); - - /// - /// Gets/sets the replacement. - /// - public string Replacement { - get { return (string)GetValue(ReplacementProperty); } - set { SetValue(ReplacementProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty ShowReplaceProperty = - DependencyProperty.Register("ShowReplace", typeof(bool), typeof(SearchPanel), - new FrameworkPropertyMetadata(false)); - - /// - /// Gets/sets whether the replace is shown. - /// - public bool ShowReplace { - get { return (bool)GetValue(ShowReplaceProperty); } - set { SetValue(ShowReplaceProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty MarkerBrushProperty = - DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(SearchPanel), - new FrameworkPropertyMetadata(Brushes.LightGreen, MarkerBrushChangedCallback)); - - /// - /// Gets/sets the Brush used for marking search results in the TextView. - /// - public Brush MarkerBrush { - get { return (Brush)GetValue(MarkerBrushProperty); } - set { SetValue(MarkerBrushProperty, value); } - } - - /// - /// Dependency property for . - /// - public static readonly DependencyProperty LocalizationProperty = - DependencyProperty.Register("Localization", typeof(Localization), typeof(SearchPanel), - new FrameworkPropertyMetadata(new Localization())); - - /// - /// Gets/sets the localization for the SearchPanel. - /// - public Localization Localization { - get { return (Localization)GetValue(LocalizationProperty); } - set { SetValue(LocalizationProperty, value); } - } - #endregion - - static void MarkerBrushChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { - SearchPanel panel = d as SearchPanel; - if (panel != null) { - panel.renderer.MarkerBrush = (Brush)e.NewValue; - } - } - - static SearchPanel() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchPanel), new FrameworkPropertyMetadata(typeof(SearchPanel))); - } - - ISearchStrategy strategy; - - static void SearchPatternChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { - SearchPanel panel = d as SearchPanel; - if (panel != null) { - panel.ValidateSearchText(); - panel.UpdateSearch(); - } - } - - void UpdateSearch() { - // only reset as long as there are results - // if no results are found, the "no matches found" message should not flicker. - // if results are found by the next run, the message will be hidden inside DoSearch ... - if (renderer.CurrentResults.Any()) - messageView.IsOpen = false; - strategy = SearchStrategyFactory.Create(SearchPattern ?? "", !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal); - OnSearchOptionsChanged(new SearchOptionsChangedEventArgs(SearchPattern, MatchCase, UseRegex, WholeWords)); - DoSearch(true); - } - - /// - /// Creates a new SearchPanel. - /// - SearchPanel() { - } - - /// - /// Attaches this SearchPanel to a TextArea instance. - /// - [Obsolete("Use the Install method instead")] - public void Attach(TextArea textArea) { - if (textArea == null) - throw new ArgumentNullException("textArea"); - AttachInternal(textArea); - } - - /// - /// Creates a SearchPanel and installs it to the TextEditor's TextArea. - /// - /// This is a convenience wrapper. - public static SearchPanel Install(TextEditor editor) { - if (editor == null) - throw new ArgumentNullException("editor"); - return Install(editor.TextArea); - } - - /// - /// Creates a SearchPanel and installs it to the TextArea. - /// - public static SearchPanel Install(TextArea textArea) { - if (textArea == null) - throw new ArgumentNullException("textArea"); - SearchPanel panel = new SearchPanel(); - panel.AttachInternal(textArea); - panel.handler = new SearchInputHandler(textArea, panel); - textArea.DefaultInputHandler.NestedInputHandlers.Add(panel.handler); - return panel; - } - - /// - /// Adds the commands used by SearchPanel to the given CommandBindingCollection. - /// - public void RegisterCommands(CommandBindingCollection commandBindings) { - handler.RegisterGlobalCommands(commandBindings); - } - - /// - /// Removes the SearchPanel from the TextArea. - /// - public void Uninstall() { - CloseAndRemove(); - textArea.DefaultInputHandler.NestedInputHandlers.Remove(handler); - } - - void AttachInternal(TextArea textArea) { - this.textArea = textArea; - adorner = new SearchPanelAdorner(textArea, this); - DataContext = this; - - renderer = new SearchResultBackgroundRenderer(); - currentDocument = textArea.Document; - if (currentDocument != null) - currentDocument.TextChanged += textArea_Document_TextChanged; - textArea.DocumentChanged += textArea_DocumentChanged; - KeyDown += SearchLayerKeyDown; - - this.CommandBindings.Add(new CommandBinding(SearchCommands.Find, (sender, e) => Open(false))); - this.CommandBindings.Add(new CommandBinding(SearchCommands.Replace, (sender, e) => Open(true))); - this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); - this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); - this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, (sender, e) => ReplaceNext())); - this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, (sender, e) => ReplaceAll())); - this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); - IsClosed = true; - } - - void textArea_DocumentChanged(object sender, EventArgs e) { - if (currentDocument != null) - currentDocument.TextChanged -= textArea_Document_TextChanged; - currentDocument = textArea.Document; - if (currentDocument != null) { - currentDocument.TextChanged += textArea_Document_TextChanged; - DoSearch(false); - } - } - - void textArea_Document_TextChanged(object sender, EventArgs e) { - DoSearch(false); - } - - /// - public override void OnApplyTemplate() { - base.OnApplyTemplate(); - searchPanel = Template.FindName("PART_searchPanel", this) as Border; - searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; - replaceTextBox = Template.FindName("PART_replaceTextBox", this) as TextBox; - } - - void ValidateSearchText() { - if (searchTextBox == null) - return; - var be = searchTextBox.GetBindingExpression(TextBox.TextProperty); - try { - Validation.ClearInvalid(be); - UpdateSearch(); - } catch (SearchPatternException ex) { - var ve = new ValidationError(be.ParentBinding.ValidationRules[0], be, ex.Message, ex); - Validation.MarkInvalid(be, ve); - } - } - - /// - /// Reactivates the SearchPanel by setting the focus on the search box and selecting all text. - /// - public void Reactivate() { - if (searchTextBox == null) - return; - searchTextBox.Focus(); - searchTextBox.SelectAll(); - } - - /// - /// Moves to the next occurrence in the file. - /// - public void FindNext() { - SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); - if (result == null) - result = renderer.CurrentResults.FirstSegment; - if (result != null) { - SelectResult(result); - } - } - - /// - /// Moves to the previous occurrence in the file. - /// - public void FindPrevious() { - SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); - if (result != null) - result = renderer.CurrentResults.GetPreviousSegment(result); - if (result == null) - result = renderer.CurrentResults.LastSegment; - if (result != null) { - SelectResult(result); - } - } - - /// - /// Replaces current result if any and moves to the next occurrence in the file. - /// - public int ReplaceNext() { - SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); - var count = renderer.CurrentResults.Count; - if (result != null - && textArea.Document.GetOffset(textArea.Selection.StartPosition.Location) == result.StartOffset - && textArea.Document.GetOffset(textArea.Selection.EndPosition.Location) == result.EndOffset) { - Replace(result); - --count; - } - result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + textArea.Selection.Length); - if (result == null) - result = renderer.CurrentResults.FirstSegment; - if (result != null) { - SelectResult(result); - return count; - } - return 0; - } - - /// - /// Replaces all occurrences in the file. - /// - public void ReplaceAll() { - var count = ReplaceNext(); - while (count-- > 0) - ReplaceNext(); - } - - void Replace(SearchResult result) { - currentDocument.Replace(textArea.Selection.Segments.FirstOrDefault(), result.ReplaceWith(Replacement)); - } - - ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = true, Focusable = false }; - - void DoSearch(bool changeSelection) { - if (IsClosed) - return; - renderer.CurrentResults.Clear(); - - if (!string.IsNullOrEmpty(SearchPattern)) { - int offset = textArea.Caret.Offset; - if (changeSelection) { - textArea.ClearSelection(); - } - // We cast from ISearchResult to SearchResult; this is safe because we always use the built-in strategy - foreach (SearchResult result in strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength)) { - if (changeSelection && result.StartOffset >= offset) { - SelectResult(result); - changeSelection = false; - } - renderer.CurrentResults.Add(result); - } - if (!renderer.CurrentResults.Any()) { - messageView.IsOpen = true; - messageView.Content = Localization.NoMatchesFoundText; - messageView.PlacementTarget = searchPanel; - } else - messageView.IsOpen = false; - } - textArea.TextView.InvalidateLayer(KnownLayer.Selection); - } - - void SelectResult(SearchResult result) { - textArea.Caret.Offset = result.StartOffset; - textArea.Selection = Selection.Create(textArea, result.StartOffset, result.EndOffset); - textArea.Caret.BringCaretToView(); - // show caret even if the editor does not have the Keyboard Focus - textArea.Caret.Show(); - } - - void SearchLayerKeyDown(object sender, KeyEventArgs e) { - switch (e.Key) { - case Key.Enter: - e.Handled = true; - if (replaceTextBox != null - && replaceTextBox.IsFocused) { - ReplaceNext(); - } else { - if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) - FindPrevious(); - else - FindNext(); - if (searchTextBox != null) { - var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); - if (error != null) { - messageView.Content = Localization.ErrorText + " " + error.ErrorContent; - messageView.PlacementTarget = searchPanel; - messageView.IsOpen = true; - } - } - } - break; - case Key.Escape: - e.Handled = true; - Close(); - break; - } - } - - /// - /// Gets whether the Panel is already closed. - /// - public bool IsClosed { get; private set; } - - /// - /// Closes the SearchPanel. - /// - public void Close() { - bool hasFocus = this.IsKeyboardFocusWithin; - - var layer = AdornerLayer.GetAdornerLayer(textArea); - if (layer != null) - layer.Remove(adorner); - messageView.IsOpen = false; - textArea.TextView.BackgroundRenderers.Remove(renderer); - if (hasFocus) - textArea.Focus(); - IsClosed = true; - - // Clear existing search results so that the segments don't have to be maintained - renderer.CurrentResults.Clear(); - } - - /// - /// Closes the SearchPanel and removes it. - /// - [Obsolete("Use the Uninstall method instead!")] - public void CloseAndRemove() { - Close(); - textArea.DocumentChanged -= textArea_DocumentChanged; - if (currentDocument != null) - currentDocument.TextChanged -= textArea_Document_TextChanged; - } - - /// - /// Opens the an existing search panel. - /// - public void Open(bool showReplace) { - ShowReplace = showReplace; - if (!IsClosed) return; - var layer = AdornerLayer.GetAdornerLayer(textArea); - if (layer != null) - layer.Add(adorner); - textArea.TextView.BackgroundRenderers.Add(renderer); - IsClosed = false; - DoSearch(false); - } - - /// - /// Fired when SearchOptions are changed inside the SearchPanel. - /// - public event EventHandler SearchOptionsChanged; - - /// - /// Raises the event. - /// - protected virtual void OnSearchOptionsChanged(SearchOptionsChangedEventArgs e) { - if (SearchOptionsChanged != null) { - SearchOptionsChanged(this, e); - } - } - } - - /// - /// EventArgs for event. - /// - public class SearchOptionsChangedEventArgs : EventArgs { - /// - /// Gets the search pattern. - /// - public string SearchPattern { get; private set; } - - /// - /// Gets whether the search pattern should be interpreted case-sensitive. - /// - public bool MatchCase { get; private set; } - - /// - /// Gets whether the search pattern should be interpreted as regular expression. - /// - public bool UseRegex { get; private set; } - - /// - /// Gets whether the search pattern should only match whole words. - /// - public bool WholeWords { get; private set; } - - /// - /// Creates a new SearchOptionsChangedEventArgs instance. - /// - public SearchOptionsChangedEventArgs(string searchPattern, bool matchCase, bool useRegex, bool wholeWords) { - this.SearchPattern = searchPattern; - this.MatchCase = matchCase; - this.UseRegex = useRegex; - this.WholeWords = wholeWords; - } - } - - class SearchPanelAdorner : Adorner { - SearchPanel panel; - - public SearchPanelAdorner(TextArea textArea, SearchPanel panel) - : base(textArea) { - this.panel = panel; - AddVisualChild(panel); - } - - protected override int VisualChildrenCount { - get { return 1; } - } - - protected override Visual GetVisualChild(int index) { - if (index != 0) - throw new ArgumentOutOfRangeException(); - return panel; - } - - protected override Size ArrangeOverride(Size finalSize) { - panel.Arrange(new Rect(new Point(0, 0), finalSize)); - return new Size(panel.ActualWidth, panel.ActualHeight); - } - } +namespace ICSharpCode.AvalonEdit.Search +{ + /// + /// Provides search functionality for AvalonEdit. It is displayed in the top-right corner of the TextArea. + /// + public class SearchPanel : Control + { + TextArea textArea; + SearchInputHandler handler; + TextDocument currentDocument; + SearchResultBackgroundRenderer renderer; + TextBox searchTextBox; + TextBox replaceTextBox; + Border searchPanel; + SearchPanelAdorner adorner; + + #region DependencyProperties + /// + /// Dependency property for . + /// + public static readonly DependencyProperty UseRegexProperty = + DependencyProperty.Register("UseRegex", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should be interpreted as regular expression. + /// + public bool UseRegex { + get { return (bool)GetValue(UseRegexProperty); } + set { SetValue(UseRegexProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty MatchCaseProperty = + DependencyProperty.Register("MatchCase", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should be interpreted case-sensitive. + /// + public bool MatchCase { + get { return (bool)GetValue(MatchCaseProperty); } + set { SetValue(MatchCaseProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty WholeWordsProperty = + DependencyProperty.Register("WholeWords", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should only match whole words. + /// + public bool WholeWords { + get { return (bool)GetValue(WholeWordsProperty); } + set { SetValue(WholeWordsProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty SearchPatternProperty = + DependencyProperty.Register("SearchPattern", typeof(string), typeof(SearchPanel), + new FrameworkPropertyMetadata("", SearchPatternChangedCallback)); + + /// + /// Gets/sets the search pattern. + /// + public string SearchPattern { + get { return (string)GetValue(SearchPatternProperty); } + set { SetValue(SearchPatternProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty ReplacementProperty = + DependencyProperty.Register("Replacement", typeof(string), typeof(SearchPanel), + new FrameworkPropertyMetadata("")); + + /// + /// Gets/sets the replacement. + /// + public string Replacement { + get { return (string)GetValue(ReplacementProperty); } + set { SetValue(ReplacementProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty ShowReplaceProperty = + DependencyProperty.Register("ShowReplace", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false)); + + /// + /// Gets/sets whether the replace is shown. + /// + public bool ShowReplace { + get { return (bool)GetValue(ShowReplaceProperty); } + set { SetValue(ShowReplaceProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty MarkerBrushProperty = + DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(SearchPanel), + new FrameworkPropertyMetadata(Brushes.LightGreen, MarkerBrushChangedCallback)); + + /// + /// Gets/sets the Brush used for marking search results in the TextView. + /// + public Brush MarkerBrush { + get { return (Brush)GetValue(MarkerBrushProperty); } + set { SetValue(MarkerBrushProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty LocalizationProperty = + DependencyProperty.Register("Localization", typeof(Localization), typeof(SearchPanel), + new FrameworkPropertyMetadata(new Localization())); + + /// + /// Gets/sets the localization for the SearchPanel. + /// + public Localization Localization { + get { return (Localization)GetValue(LocalizationProperty); } + set { SetValue(LocalizationProperty, value); } + } + #endregion + + static void MarkerBrushChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.renderer.MarkerBrush = (Brush)e.NewValue; + } + } + + static SearchPanel() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchPanel), new FrameworkPropertyMetadata(typeof(SearchPanel))); + } + + ISearchStrategy strategy; + + static void SearchPatternChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.ValidateSearchText(); + panel.UpdateSearch(); + } + } + + void UpdateSearch() + { + // only reset as long as there are results + // if no results are found, the "no matches found" message should not flicker. + // if results are found by the next run, the message will be hidden inside DoSearch ... + if (renderer.CurrentResults.Any()) + messageView.IsOpen = false; + strategy = SearchStrategyFactory.Create(SearchPattern ?? "", !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal); + OnSearchOptionsChanged(new SearchOptionsChangedEventArgs(SearchPattern, MatchCase, UseRegex, WholeWords)); + DoSearch(true); + } + + /// + /// Creates a new SearchPanel. + /// + SearchPanel() + { + } + + /// + /// Attaches this SearchPanel to a TextArea instance. + /// + [Obsolete("Use the Install method instead")] + public void Attach(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + AttachInternal(textArea); + } + + /// + /// Creates a SearchPanel and installs it to the TextEditor's TextArea. + /// + /// This is a convenience wrapper. + public static SearchPanel Install(TextEditor editor) + { + if (editor == null) + throw new ArgumentNullException("editor"); + return Install(editor.TextArea); + } + + /// + /// Creates a SearchPanel and installs it to the TextArea. + /// + public static SearchPanel Install(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + SearchPanel panel = new SearchPanel(); + panel.AttachInternal(textArea); + panel.handler = new SearchInputHandler(textArea, panel); + textArea.DefaultInputHandler.NestedInputHandlers.Add(panel.handler); + return panel; + } + + /// + /// Adds the commands used by SearchPanel to the given CommandBindingCollection. + /// + public void RegisterCommands(CommandBindingCollection commandBindings) + { + handler.RegisterGlobalCommands(commandBindings); + } + + /// + /// Removes the SearchPanel from the TextArea. + /// + public void Uninstall() + { + CloseAndRemove(); + textArea.DefaultInputHandler.NestedInputHandlers.Remove(handler); + } + + void AttachInternal(TextArea textArea) + { + this.textArea = textArea; + adorner = new SearchPanelAdorner(textArea, this); + DataContext = this; + + renderer = new SearchResultBackgroundRenderer(); + currentDocument = textArea.Document; + if (currentDocument != null) + currentDocument.TextChanged += textArea_Document_TextChanged; + textArea.DocumentChanged += textArea_DocumentChanged; + KeyDown += SearchLayerKeyDown; + + this.CommandBindings.Add(new CommandBinding(SearchCommands.Find, (sender, e) => Open(false))); + this.CommandBindings.Add(new CommandBinding(SearchCommands.Replace, (sender, e) => Open(true))); + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, (sender, e) => ReplaceNext())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, (sender, e) => ReplaceAll())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); + IsClosed = true; + } + + void textArea_DocumentChanged(object sender, EventArgs e) + { + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + currentDocument = textArea.Document; + if (currentDocument != null) { + currentDocument.TextChanged += textArea_Document_TextChanged; + DoSearch(false); + } + } + + void textArea_Document_TextChanged(object sender, EventArgs e) + { + DoSearch(false); + } + + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + searchPanel = Template.FindName("PART_searchPanel", this) as Border; + searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; + replaceTextBox = Template.FindName("PART_replaceTextBox", this) as TextBox; + } + + void ValidateSearchText() + { + if (searchTextBox == null) + return; + var be = searchTextBox.GetBindingExpression(TextBox.TextProperty); + try { + Validation.ClearInvalid(be); + UpdateSearch(); + } catch (SearchPatternException ex) { + var ve = new ValidationError(be.ParentBinding.ValidationRules[0], be, ex.Message, ex); + Validation.MarkInvalid(be, ve); + } + } + + /// + /// Reactivates the SearchPanel by setting the focus on the search box and selecting all text. + /// + public void Reactivate() + { + if (searchTextBox == null) + return; + searchTextBox.Focus(); + searchTextBox.SelectAll(); + } + + /// + /// Moves to the next occurrence in the file. + /// + public void FindNext() + { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); + if (result == null) + result = renderer.CurrentResults.FirstSegment; + if (result != null) { + SelectResult(result); + } + } + + /// + /// Moves to the previous occurrence in the file. + /// + public void FindPrevious() + { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); + if (result != null) + result = renderer.CurrentResults.GetPreviousSegment(result); + if (result == null) + result = renderer.CurrentResults.LastSegment; + if (result != null) { + SelectResult(result); + } + } + + /// + /// Replaces current result if any and moves to the next occurrence in the file. + /// + public int ReplaceNext() { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); + var count = renderer.CurrentResults.Count; + if (result != null + && textArea.Document.GetOffset(textArea.Selection.StartPosition.Location) == result.StartOffset + && textArea.Document.GetOffset(textArea.Selection.EndPosition.Location) == result.EndOffset) { + Replace(result); + --count; + } + result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + textArea.Selection.Length); + if (result == null) + result = renderer.CurrentResults.FirstSegment; + if (result != null) { + SelectResult(result); + return count; + } + return 0; + } + + /// + /// Replaces all occurrences in the file. + /// + public void ReplaceAll() { + var count = ReplaceNext(); + while (count-- > 0) + ReplaceNext(); + } + + void Replace(SearchResult result) { + currentDocument.Replace(textArea.Selection.Segments.FirstOrDefault(), result.ReplaceWith(Replacement)); + } + + ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = true, Focusable = false }; + + void DoSearch(bool changeSelection) + { + if (IsClosed) + return; + renderer.CurrentResults.Clear(); + + if (!string.IsNullOrEmpty(SearchPattern)) { + int offset = textArea.Caret.Offset; + if (changeSelection) { + textArea.ClearSelection(); + } + // We cast from ISearchResult to SearchResult; this is safe because we always use the built-in strategy + foreach (SearchResult result in strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength)) { + if (changeSelection && result.StartOffset >= offset) { + SelectResult(result); + changeSelection = false; + } + renderer.CurrentResults.Add(result); + } + if (!renderer.CurrentResults.Any()) { + messageView.IsOpen = true; + messageView.Content = Localization.NoMatchesFoundText; + messageView.PlacementTarget = searchPanel; + } else + messageView.IsOpen = false; + } + textArea.TextView.InvalidateLayer(KnownLayer.Selection); + } + + void SelectResult(SearchResult result) + { + textArea.Caret.Offset = result.StartOffset; + textArea.Selection = Selection.Create(textArea, result.StartOffset, result.EndOffset); + textArea.Caret.BringCaretToView(); + // show caret even if the editor does not have the Keyboard Focus + textArea.Caret.Show(); + } + + void SearchLayerKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) { + case Key.Enter: + e.Handled = true; + if (replaceTextBox != null + && replaceTextBox.IsFocused) { + ReplaceNext(); + } else { + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) + FindPrevious(); + else + FindNext(); + if (searchTextBox != null) { + var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); + if (error != null) { + messageView.Content = Localization.ErrorText + " " + error.ErrorContent; + messageView.PlacementTarget = searchPanel; + messageView.IsOpen = true; + } + } + } + break; + case Key.Escape: + e.Handled = true; + Close(); + break; + } + } + + /// + /// Gets whether the Panel is already closed. + /// + public bool IsClosed { get; private set; } + + /// + /// Closes the SearchPanel. + /// + public void Close() + { + bool hasFocus = this.IsKeyboardFocusWithin; + + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Remove(adorner); + messageView.IsOpen = false; + textArea.TextView.BackgroundRenderers.Remove(renderer); + if (hasFocus) + textArea.Focus(); + IsClosed = true; + + // Clear existing search results so that the segments don't have to be maintained + renderer.CurrentResults.Clear(); + } + + /// + /// Closes the SearchPanel and removes it. + /// + [Obsolete("Use the Uninstall method instead!")] + public void CloseAndRemove() + { + Close(); + textArea.DocumentChanged -= textArea_DocumentChanged; + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + } + + /// + /// Opens the an existing search panel. + /// + public void Open(bool showReplace) + { + ShowReplace = showReplace; + if (!IsClosed) return; + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Add(adorner); + textArea.TextView.BackgroundRenderers.Add(renderer); + IsClosed = false; + DoSearch(false); + } + + /// + /// Fired when SearchOptions are changed inside the SearchPanel. + /// + public event EventHandler SearchOptionsChanged; + + /// + /// Raises the event. + /// + protected virtual void OnSearchOptionsChanged(SearchOptionsChangedEventArgs e) + { + if (SearchOptionsChanged != null) { + SearchOptionsChanged(this, e); + } + } + } + + /// + /// EventArgs for event. + /// + public class SearchOptionsChangedEventArgs : EventArgs + { + /// + /// Gets the search pattern. + /// + public string SearchPattern { get; private set; } + + /// + /// Gets whether the search pattern should be interpreted case-sensitive. + /// + public bool MatchCase { get; private set; } + + /// + /// Gets whether the search pattern should be interpreted as regular expression. + /// + public bool UseRegex { get; private set; } + + /// + /// Gets whether the search pattern should only match whole words. + /// + public bool WholeWords { get; private set; } + + /// + /// Creates a new SearchOptionsChangedEventArgs instance. + /// + public SearchOptionsChangedEventArgs(string searchPattern, bool matchCase, bool useRegex, bool wholeWords) + { + this.SearchPattern = searchPattern; + this.MatchCase = matchCase; + this.UseRegex = useRegex; + this.WholeWords = wholeWords; + } + } + + class SearchPanelAdorner : Adorner + { + SearchPanel panel; + + public SearchPanelAdorner(TextArea textArea, SearchPanel panel) + : base(textArea) + { + this.panel = panel; + AddVisualChild(panel); + } + + protected override int VisualChildrenCount { + get { return 1; } + } + + protected override Visual GetVisualChild(int index) + { + if (index != 0) + throw new ArgumentOutOfRangeException(); + return panel; + } + + protected override Size ArrangeOverride(Size finalSize) + { + panel.Arrange(new Rect(new Point(0, 0), finalSize)); + return new Size(panel.ActualWidth, panel.ActualHeight); + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml index d2a641da..97595307 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.xaml @@ -1,69 +1,69 @@  - + \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs b/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs index 479c7f14..e9c8d215 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs @@ -27,64 +27,68 @@ using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; -namespace ICSharpCode.AvalonEdit.Search { - class SearchResultBackgroundRenderer : IBackgroundRenderer { - TextSegmentCollection currentResults = new TextSegmentCollection(); - - public TextSegmentCollection CurrentResults { - get { return currentResults; } - } - - public KnownLayer Layer { - get { - // draw behind selection - return KnownLayer.Selection; - } - } - - public SearchResultBackgroundRenderer() { - markerBrush = Brushes.LightGreen; - markerPen = new Pen(markerBrush, 1); - } - - Brush markerBrush; - Pen markerPen; - - public Brush MarkerBrush { - get { return markerBrush; } - set { - this.markerBrush = value; - markerPen = new Pen(markerBrush, 1); - } - } - - public void Draw(TextView textView, DrawingContext drawingContext) { - if (textView == null) - throw new ArgumentNullException("textView"); - if (drawingContext == null) - throw new ArgumentNullException("drawingContext"); - - if (currentResults == null || !textView.VisualLinesValid) - return; - - var visualLines = textView.VisualLines; - if (visualLines.Count == 0) - return; - - int viewStart = visualLines.First().FirstDocumentLine.Offset; - int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; - - foreach (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { - BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); - geoBuilder.AlignToWholePixels = true; - geoBuilder.BorderThickness = markerPen != null ? markerPen.Thickness : 0; - geoBuilder.CornerRadius = 3; - geoBuilder.AddSegment(textView, result); - Geometry geometry = geoBuilder.CreateGeometry(); - if (geometry != null) { - drawingContext.DrawGeometry(markerBrush, markerPen, geometry); - } - } - } - } +namespace ICSharpCode.AvalonEdit.Search +{ + class SearchResultBackgroundRenderer : IBackgroundRenderer + { + TextSegmentCollection currentResults = new TextSegmentCollection(); + + public TextSegmentCollection CurrentResults { + get { return currentResults; } + } + + public KnownLayer Layer { + get { + // draw behind selection + return KnownLayer.Selection; + } + } + + public SearchResultBackgroundRenderer() + { + markerBrush = Brushes.LightGreen; + markerPen = new Pen(markerBrush, 1); + } + + Brush markerBrush; + Pen markerPen; + + public Brush MarkerBrush { + get { return markerBrush; } + set { + this.markerBrush = value; + markerPen = new Pen(markerBrush, 1); + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (drawingContext == null) + throw new ArgumentNullException("drawingContext"); + + if (currentResults == null || !textView.VisualLinesValid) + return; + + var visualLines = textView.VisualLines; + if (visualLines.Count == 0) + return; + + int viewStart = visualLines.First().FirstDocumentLine.Offset; + int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; + + foreach (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToWholePixels = true; + geoBuilder.BorderThickness = markerPen != null ? markerPen.Thickness : 0; + geoBuilder.CornerRadius = 3; + geoBuilder.AddSegment(textView, result); + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(markerBrush, markerPen, geometry); + } + } + } + } } diff --git a/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs b/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs index 89e4dddf..4c45a147 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchStrategyFactory.cs @@ -22,58 +22,62 @@ using System.Windows.Controls; using ICSharpCode.AvalonEdit.Document; -namespace ICSharpCode.AvalonEdit.Search { - /// - /// Provides factory methods for ISearchStrategies. - /// - public static class SearchStrategyFactory { - /// - /// Creates a default ISearchStrategy with the given parameters. - /// - public static ISearchStrategy Create(string searchPattern, bool ignoreCase, bool matchWholeWords, SearchMode mode) { - if (searchPattern == null) - throw new ArgumentNullException("searchPattern"); - RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline; - if (ignoreCase) - options |= RegexOptions.IgnoreCase; - - switch (mode) { - case SearchMode.Normal: - searchPattern = Regex.Escape(searchPattern); - break; - case SearchMode.Wildcard: - searchPattern = ConvertWildcardsToRegex(searchPattern); - break; - } - try { - Regex pattern = new Regex(searchPattern, options); - return new RegexSearchStrategy(pattern, matchWholeWords); - } catch (ArgumentException ex) { - throw new SearchPatternException(ex.Message, ex); - } - } - - static string ConvertWildcardsToRegex(string searchPattern) { - if (string.IsNullOrEmpty(searchPattern)) - return ""; - - StringBuilder builder = new StringBuilder(); - - foreach (char ch in searchPattern) { - switch (ch) { - case '?': - builder.Append("."); - break; - case '*': - builder.Append(".*"); - break; - default: - builder.Append(Regex.Escape(ch.ToString())); - break; - } - } - - return builder.ToString(); - } - } +namespace ICSharpCode.AvalonEdit.Search +{ + /// + /// Provides factory methods for ISearchStrategies. + /// + public static class SearchStrategyFactory + { + /// + /// Creates a default ISearchStrategy with the given parameters. + /// + public static ISearchStrategy Create(string searchPattern, bool ignoreCase, bool matchWholeWords, SearchMode mode) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline; + if (ignoreCase) + options |= RegexOptions.IgnoreCase; + + switch (mode) { + case SearchMode.Normal: + searchPattern = Regex.Escape(searchPattern); + break; + case SearchMode.Wildcard: + searchPattern = ConvertWildcardsToRegex(searchPattern); + break; + } + try { + Regex pattern = new Regex(searchPattern, options); + return new RegexSearchStrategy(pattern, matchWholeWords); + } catch (ArgumentException ex) { + throw new SearchPatternException(ex.Message, ex); + } + } + + static string ConvertWildcardsToRegex(string searchPattern) + { + if (string.IsNullOrEmpty(searchPattern)) + return ""; + + StringBuilder builder = new StringBuilder(); + + foreach (char ch in searchPattern) { + switch (ch) { + case '?': + builder.Append("."); + break; + case '*': + builder.Append(".*"); + break; + default: + builder.Append(Regex.Escape(ch.ToString())); + break; + } + } + + return builder.ToString(); + } + } } From 8589f76bd9bd3c2268c928037aa8e05b9138514b Mon Sep 17 00:00:00 2001 From: Christoph Stephan Date: Wed, 23 Nov 2016 10:23:04 +0100 Subject: [PATCH 07/11] BugFix: exception if nothing is selected --- ICSharpCode.AvalonEdit/Search/SearchPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs index f96c918f..9921aa84 100644 --- a/ICSharpCode.AvalonEdit/Search/SearchPanel.cs +++ b/ICSharpCode.AvalonEdit/Search/SearchPanel.cs @@ -376,6 +376,7 @@ public int ReplaceNext() { SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); var count = renderer.CurrentResults.Count; if (result != null + && !textArea.Selection.IsEmpty && textArea.Document.GetOffset(textArea.Selection.StartPosition.Location) == result.StartOffset && textArea.Document.GetOffset(textArea.Selection.EndPosition.Location) == result.EndOffset) { Replace(result); From 8777bd599c7f7e63510767f296fa81a4847b3cbc Mon Sep 17 00:00:00 2001 From: STC Date: Thu, 1 Oct 2020 18:37:51 +0200 Subject: [PATCH 08/11] Migrate to PackageReferences --- .../ICSharpCode.AvalonEdit.Tests.csproj | 12 ++++-------- ICSharpCode.AvalonEdit.Tests/packages.config | 4 ---- 2 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 ICSharpCode.AvalonEdit.Tests/packages.config diff --git a/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj b/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj index d63f3032..0f85f990 100644 --- a/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj +++ b/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj @@ -55,7 +55,7 @@ True False obj\ - True + True @@ -63,9 +63,6 @@ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371} ICSharpCode.NRefactory - - $(SolutionDir)\packages\NUnit.2.6.3\lib\nunit.framework.dll - 3.0 @@ -118,9 +115,8 @@ - - - - + + 2.6.3 + \ No newline at end of file diff --git a/ICSharpCode.AvalonEdit.Tests/packages.config b/ICSharpCode.AvalonEdit.Tests/packages.config deleted file mode 100644 index ad37a528..00000000 --- a/ICSharpCode.AvalonEdit.Tests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 5a988311ec8aecf035a7db63f4f34549cd8b2892 Mon Sep 17 00:00:00 2001 From: STC Date: Thu, 1 Oct 2020 18:38:10 +0200 Subject: [PATCH 09/11] Cleanup --- .gitattributes | 66 +++++++++++- .gitignore | 269 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 324 insertions(+), 11 deletions(-) diff --git a/.gitattributes b/.gitattributes index fbfb41be..1ff0c423 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,63 @@ -*.cs text diff=csharp -*.sln text eol=crlf -*.csproj text eol=crlf +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index f079a88c..3c4efe20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,261 @@ -# ignore all obj and bin folders (even in subdirectories) -obj/ -bin/ -/Documentation/Help -/packages/AvalonEdit -/packages/AvalonEdit.Sample -/packages/NUnit.2.6.3 -/Documentation/IntelliSense +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file From cf3f145da4a0ef6876e06cc9061169540059fd64 Mon Sep 17 00:00:00 2001 From: STC Date: Thu, 1 Oct 2020 20:58:38 +0200 Subject: [PATCH 10/11] Ignore --- ICSharpCode.AvalonEdit/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 ICSharpCode.AvalonEdit/.gitignore diff --git a/ICSharpCode.AvalonEdit/.gitignore b/ICSharpCode.AvalonEdit/.gitignore new file mode 100644 index 00000000..e3eab68c --- /dev/null +++ b/ICSharpCode.AvalonEdit/.gitignore @@ -0,0 +1 @@ +/ICSharpCode.AvalonEdit.xml From 96b7f9ef40d7b97a7bd33ab896c4de01f403fc23 Mon Sep 17 00:00:00 2001 From: Christoph Stephan Date: Fri, 9 Dec 2022 10:23:37 +0100 Subject: [PATCH 11/11] Upgrade .NET framework --- ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index 0fdbc3b2..5a747d5a 100644 --- a/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -1,7 +1,7 @@  Library - netcoreapp3.0;net40;net45 + netcoreapp3.0;net472 true true TRACE