├── images
├── Icon1.png
├── image1.png
├── image2.png
├── Banner
│ ├── banner.jpg
│ ├── banner.psd
│ └── banner_notext.jpg
└── Icon1.svg
├── TextControlBox-TestApp
├── Assets
│ ├── StoreLogo.png
│ ├── SplashScreen.scale-200.png
│ ├── LockScreenLogo.scale-200.png
│ ├── Square44x44Logo.scale-200.png
│ ├── Wide310x150Logo.scale-200.png
│ ├── Square150x150Logo.scale-200.png
│ └── Square44x44Logo.targetsize-24_altform-unplated.png
├── App.xaml
├── MainPage.xaml
├── Properties
│ ├── AssemblyInfo.cs
│ └── Default.rd.xml
├── Package.appxmanifest
├── App.xaml.cs
├── MainPage.xaml.cs
└── TextControlBox-TestApp.csproj
├── TextControlBox
├── Enums
│ ├── LineMoveDirection.cs
│ ├── LineEnding.cs
│ └── SearchResult.cs
├── Extensions
│ ├── IntExtension.cs
│ ├── ColorExtension.cs
│ ├── PointExtension.cs
│ ├── StringExtension.cs
│ └── ListExtension.cs
├── Models
│ ├── JsonCodeLanguage.cs
│ ├── UndoRedoItem.cs
│ ├── SelectionChangedEventHandler.cs
│ ├── TextSelectionPosition.cs
│ ├── JsonLoadResult.cs
│ ├── CodeLanguage.cs
│ ├── ScrollBarPosition.cs
│ ├── CursorSize.cs
│ ├── CodeFontStyle.cs
│ ├── AutoPairingPair.cs
│ ├── TextSelection.cs
│ ├── CursorPosition.cs
│ ├── SyntaxHighlights.cs
│ └── TextControlBoxDesign.cs
├── Text
│ ├── StringManager.cs
│ ├── LineEndings.cs
│ ├── MoveLine.cs
│ ├── AutoPairing.cs
│ ├── TabKey.cs
│ ├── UndoRedo.cs
│ ├── Cursor.cs
│ └── Selection.cs
├── Renderer
│ ├── LineHighlighterRenderer.cs
│ ├── SearchHighlightsRenderer.cs
│ ├── CursorRenderer.cs
│ ├── SyntaxHighlightingRenderer.cs
│ └── SelectionRenderer.cs
├── Properties
│ ├── AssemblyInfo.cs
│ └── TextControlBox.rd.xml
├── Helper
│ ├── CanvasHelper.cs
│ ├── FlyoutHelper.cs
│ ├── ListHelper.cs
│ ├── TabSpaceHelper.cs
│ ├── TextLayoutHelper.cs
│ ├── Utils.cs
│ └── SearchHelper.cs
├── TextControlBox.xaml
└── TextControlBox.csproj
├── PrivacyPolicy.md
├── Disclaimer.md
├── LICENSE
├── .gitattributes
├── TextControlBox-UWP.sln
├── README.md
└── .gitignore
/images/Icon1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/images/Icon1.png
--------------------------------------------------------------------------------
/images/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/images/image1.png
--------------------------------------------------------------------------------
/images/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/images/image2.png
--------------------------------------------------------------------------------
/images/Banner/banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/images/Banner/banner.jpg
--------------------------------------------------------------------------------
/images/Banner/banner.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/images/Banner/banner.psd
--------------------------------------------------------------------------------
/images/Banner/banner_notext.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/images/Banner/banner_notext.jpg
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Assets/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/TextControlBox-TestApp/Assets/StoreLogo.png
--------------------------------------------------------------------------------
/TextControlBox/Enums/LineMoveDirection.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | internal enum LineMoveDirection
4 | {
5 | Up, Down
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Assets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/TextControlBox-TestApp/Assets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Assets/LockScreenLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/TextControlBox-TestApp/Assets/LockScreenLogo.scale-200.png
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Assets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/TextControlBox-TestApp/Assets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Assets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/TextControlBox-TestApp/Assets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Assets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/TextControlBox-TestApp/Assets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenAssassine/TextControlBox-UWP/HEAD/TextControlBox-TestApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
--------------------------------------------------------------------------------
/TextControlBox/Extensions/IntExtension.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox.Extensions
2 | {
3 | internal static class IntExtension
4 | {
5 | public static bool IsInRange(this int value, int start, int count)
6 | {
7 | return value >= start && value <= start + count;
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/TextControlBox/Models/JsonCodeLanguage.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | internal class JsonCodeLanguage
4 | {
5 | public string Name { get; set; }
6 | public string Description { get; set; }
7 | public string Filter { get; set; }
8 | public string Author { get; set; }
9 | public SyntaxHighlights[] Highlights;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TextControlBox-TestApp/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/TextControlBox/Models/UndoRedoItem.cs:
--------------------------------------------------------------------------------
1 | using TextControlBox.Text;
2 |
3 | namespace TextControlBox.Models
4 | {
5 | internal struct UndoRedoItem
6 | {
7 | public int StartLine { get; set; }
8 | public string UndoText { get; set; }
9 | public string RedoText { get; set; }
10 | public int UndoCount { get; set; }
11 | public int RedoCount { get; set; }
12 | public TextSelection Selection { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/TextControlBox/Extensions/ColorExtension.cs:
--------------------------------------------------------------------------------
1 | using Windows.UI;
2 |
3 | namespace TextControlBox.Extensions
4 | {
5 | internal static class ColorExtension
6 | {
7 | public static Windows.UI.Color ToMediaColor(this System.Drawing.Color drawingColor)
8 | {
9 | return Windows.UI.Color.FromArgb(drawingColor.A, drawingColor.R, drawingColor.G, drawingColor.B);
10 | }
11 | public static string ToHex(this Color c) => $"#{c.R:X2}{c.G:X2}{c.B:X2}";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/TextControlBox/Extensions/PointExtension.cs:
--------------------------------------------------------------------------------
1 | using Windows.Foundation;
2 |
3 | namespace TextControlBox.Extensions
4 | {
5 | internal static class PointExtension
6 | {
7 | public static Point Subtract(this Point point, double subtractX, double subtractY)
8 | {
9 | return new Point(point.X - subtractX, point.Y - subtractY);
10 | }
11 | public static Point Subtract(this Point point, Point subtract)
12 | {
13 | return new Point(point.X - subtract.X, point.Y - subtract.Y);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TextControlBox/Text/StringManager.cs:
--------------------------------------------------------------------------------
1 | using TextControlBox.Helper;
2 |
3 | namespace TextControlBox.Text
4 | {
5 | internal class StringManager
6 | {
7 | private TabSpaceHelper tabSpaceHelper;
8 | public LineEnding lineEnding;
9 |
10 | public StringManager(TabSpaceHelper tabSpaceHelper)
11 | {
12 | this.tabSpaceHelper = tabSpaceHelper;
13 | }
14 | public string CleanUpString(string input)
15 | {
16 | //Fix tabs and lineendings
17 | return tabSpaceHelper.UpdateTabs(LineEndings.CleanLineEndings(input, lineEnding));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TextControlBox/Renderer/LineHighlighterRenderer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graphics.Canvas.Brushes;
2 | using Microsoft.Graphics.Canvas.Text;
3 | using Microsoft.Graphics.Canvas.UI.Xaml;
4 |
5 | namespace TextControlBox.Renderer
6 | {
7 | internal class LineHighlighterRenderer
8 | {
9 | public static void Render(float canvasWidth, CanvasTextLayout textLayout, float y, float fontSize, CanvasDrawEventArgs args, CanvasSolidColorBrush backgroundBrush)
10 | {
11 | if (textLayout == null)
12 | return;
13 |
14 | args.DrawingSession.FillRectangle(0, y, canvasWidth, fontSize, backgroundBrush);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TextControlBox-TestApp/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/PrivacyPolicy.md:
--------------------------------------------------------------------------------
1 | # TCB Editor Privacy Policy
2 | By using the app you are consenting to our policies regarding the collection, use and disclosure of personal information set out in this privacy policy.
3 |
4 | ### Collection of Personal Information
5 | We do not collect, store, use or share any information, personal or otherwise.
6 |
7 | ### Email
8 | If you email the developer for support or other feedback, the emails with email addresses will be retained for quality assurance purposes. The email addresses will be used only to reply to the concerns or suggestions raised and will never be used for any marketing purpose.
9 |
10 | ### Disclosure of Personal Information
11 | We will not disclose your information to any third party except if you expressly consent or where required by law.
12 |
13 | ### Contacting us
14 | If you have any questions regarding this privacy policy, you can email **contact@frozenassassine.de**
15 |
--------------------------------------------------------------------------------
/Disclaimer.md:
--------------------------------------------------------------------------------
1 |
Disclaimer for TCB Editor
2 |
3 | We are doing our best to prepare the content of this app. However, TCB Editor cannot warranty the expressions and suggestions of the contents, as well as its accuracy. In addition, to the extent permitted by the law, TCB Editor shall not be responsible for any losses and/or damages due to the usage of the information on our app. Our Disclaimer was generated with the help of the App Disclaimer Generator from App-Privacy-Policy.com
4 |
5 | By using our app, you hereby consent to our disclaimer and agree to its terms.
6 |
7 | Any links contained in our app may lead to external sites are provided for convenience only. Any information or statements that appeared in these sites or app are not sponsored, endorsed, or otherwise approved by TCB Editor. For these external sites, TCB Editor cannot be held liable for the availability of, or the content located on or through it. Plus, any losses or damages occurred from using these contents or the internet generally.
8 |
--------------------------------------------------------------------------------
/TextControlBox/Models/SelectionChangedEventHandler.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the class that will be returned by the SelectionChanged event of the TextControlBox.
5 | ///
6 | public class SelectionChangedEventHandler
7 | {
8 | ///
9 | /// Represents the position of the cursor within the current line.
10 | ///
11 | public int CharacterPositionInLine { get; set; }
12 |
13 | ///
14 | /// Represents the line number where the cursor is currently located.
15 | ///
16 | public int LineNumber { get; set; }
17 |
18 | ///
19 | /// Represents the starting index of the selection.
20 | ///
21 | public int SelectionStartIndex { get; set; }
22 |
23 | ///
24 | /// Represents the length of the selection.
25 | ///
26 | public int SelectionLength { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TextControlBox/Models/TextSelectionPosition.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the position and length of a text selection in the textbox.
5 | ///
6 | public class TextSelectionPosition
7 | {
8 | ///
9 | /// Initializes a new instance of the TextSelectionPosition class with the specified index and length.
10 | ///
11 | /// The start index of the text selection.
12 | /// The length of the text selection.
13 | public TextSelectionPosition(int index = 0, int length = 0)
14 | {
15 | this.Index = index;
16 | this.Length = length;
17 | }
18 |
19 | ///
20 | /// Gets or sets the start index of the text selection.
21 | ///
22 | public int Index { get; set; }
23 |
24 | ///
25 | /// Gets or sets the length of the text selection.
26 | ///
27 | public int Length { get; set; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Julius Kirsch
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/TextControlBox/Text/LineEndings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.RegularExpressions;
3 |
4 | namespace TextControlBox.Text
5 | {
6 | internal class LineEndings
7 | {
8 | public static string LineEndingToString(LineEnding lineEnding)
9 | {
10 | return
11 | lineEnding == LineEnding.LF ? "\n" :
12 | lineEnding == LineEnding.CRLF ? "\r\n" :
13 | "\r";
14 | }
15 | public static LineEnding FindLineEnding(string text)
16 | {
17 | if (text.IndexOf("\r\n", StringComparison.Ordinal) > -1)
18 | return LineEnding.CRLF;
19 | else if (text.IndexOf("\n", StringComparison.Ordinal) > -1)
20 | return LineEnding.LF;
21 | else if (text.IndexOf("\r", StringComparison.Ordinal) > -1)
22 | return LineEnding.CR;
23 | return LineEnding.CRLF;
24 | }
25 | public static string CleanLineEndings(string text, LineEnding lineEnding)
26 | {
27 | return Regex.Replace(text, "(\r\n|\r|\n)", LineEndingToString(lineEnding));
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/TextControlBox/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // Allgemeine Informationen über eine Assembly werden über die folgenden
5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
6 | // die einer Assembly zugeordnet sind.
7 | [assembly: AssemblyTitle("TextControlBox")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("TextControlBox")]
12 | [assembly: AssemblyCopyright("Copyright © 2023")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
17 | //
18 | // Hauptversion
19 | // Nebenversion
20 | // Buildnummer
21 | // Revision
22 | //
23 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
24 | // indem Sie "*" wie unten gezeigt eingeben:
25 | // [Assembly: AssemblyVersion("1.0.*")]
26 | [assembly: AssemblyVersion("1.8.0.0")]
27 | [assembly: AssemblyFileVersion("1.8.0.0")]
28 | [assembly: ComVisible(false)]
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // Allgemeine Informationen über eine Assembly werden über die folgenden
5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
6 | // die einer Assembly zugeordnet sind.
7 | [assembly: AssemblyTitle("TextControlBox-TestApp")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("TextControlBox-TestApp")]
12 | [assembly: AssemblyCopyright("Copyright © 2022")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
17 | //
18 | // Hauptversion
19 | // Nebenversion
20 | // Buildnummer
21 | // Revision
22 | //
23 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
24 | // indem Sie "*" wie unten gezeigt eingeben:
25 | // [Assembly: AssemblyVersion("1.0.*")]
26 | [assembly: AssemblyVersion("1.0.0.0")]
27 | [assembly: AssemblyFileVersion("1.0.0.0")]
28 | [assembly: ComVisible(false)]
--------------------------------------------------------------------------------
/TextControlBox/Models/JsonLoadResult.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the result of a JSON load operation for a code language in the textbox.
5 | ///
6 | public class JsonLoadResult
7 | {
8 | ///
9 | /// Initializes a new instance of the JsonLoadResult class with the specified loading status and CodeLanguage.
10 | ///
11 | /// true if the loading operation succeeded; otherwise, false.
12 | /// The CodeLanguage loaded from the JSON data.
13 | public JsonLoadResult(bool succeed, CodeLanguage codeLanguage)
14 | {
15 | this.Succeed = succeed;
16 | this.CodeLanguage = codeLanguage;
17 | }
18 |
19 | ///
20 | /// Gets or sets a value indicating whether the loading operation succeeded.
21 | ///
22 | public bool Succeed { get; set; }
23 |
24 | ///
25 | /// Gets or sets the CodeLanguage that was loaded from the JSON data.
26 | ///
27 | public CodeLanguage CodeLanguage { get; set; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/TextControlBox/Enums/LineEnding.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the three default line endings: LF (Line Feed), CRLF (Carriage Return + Line Feed), and CR (Carriage Return).
5 | ///
6 | ///
7 | /// The LineEnding enum represents the different line ending formats that can be used in the textbox.
8 | /// The enum defines three values: LF, CRLF, and CR, corresponding to the different line ending formats.
9 | /// - LF: Represents the Line Feed character '\n' used for line breaks in Unix-based systems.
10 | /// - CRLF: Represents the Carriage Return + Line Feed sequence '\r\n' used for line breaks in Windows-based systems.
11 | /// - CR: Represents the Carriage Return character '\r' used for line breaks in older Macintosh systems.
12 | ///
13 | public enum LineEnding
14 | {
15 | ///
16 | /// Line Feed ('\n')
17 | ///
18 | LF,
19 | ///
20 | /// Carriage Return + Line Feed ('\r\n')
21 | ///
22 | CRLF,
23 | ///
24 | /// Carriage Return ('\r')
25 | ///
26 | CR
27 | }
28 | }
--------------------------------------------------------------------------------
/TextControlBox/Enums/SearchResult.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the result of a search operation in the textbox.
5 | ///
6 | public enum SearchResult
7 | {
8 | ///
9 | /// The target text was found during the search operation.
10 | ///
11 | Found,
12 |
13 | ///
14 | /// The target text was not found during the search operation.
15 | ///
16 | NotFound,
17 |
18 | ///
19 | /// The search input provided for the search operation was invalid or empty.
20 | ///
21 | InvalidInput,
22 |
23 | ///
24 | /// The search operation reached the beginning of the text without finding the target text.
25 | ///
26 | ReachedBegin,
27 |
28 | ///
29 | /// The search operation reached the end of the text without finding the target text.
30 | ///
31 | ReachedEnd,
32 |
33 | ///
34 | /// The search operation was attempted, but the search was not started.
35 | ///
36 | SearchNotOpened
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/TextControlBox/Text/MoveLine.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 |
3 | namespace TextControlBox.Text
4 | {
5 | internal class MoveLine
6 | {
7 | public static void Move(PooledList totalLines, TextSelection selection, CursorPosition cursorposition, UndoRedo undoredo, string newLineCharacter, LineMoveDirection direction)
8 | {
9 | if (selection != null)
10 | return;
11 |
12 | //move down:
13 | if (direction == LineMoveDirection.Down)
14 | {
15 | if (cursorposition.LineNumber >= totalLines.Count - 1)
16 | return;
17 |
18 | undoredo.RecordUndoAction(() =>
19 | {
20 | Selection.MoveLinesDown(totalLines, selection, cursorposition);
21 | }, totalLines, cursorposition.LineNumber, 2, 2, newLineCharacter, cursorposition);
22 | return;
23 | }
24 |
25 | //move up:
26 | if (cursorposition.LineNumber <= 0)
27 | return;
28 |
29 | undoredo.RecordUndoAction(() =>
30 | {
31 | Selection.MoveLinesUp(totalLines, selection, cursorposition);
32 | }, totalLines, cursorposition.LineNumber - 1, 2, 2, newLineCharacter, cursorposition);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/TextControlBox/Helper/CanvasHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graphics.Canvas.UI.Xaml;
2 | using Windows.UI.Core;
3 |
4 | namespace TextControlBox.Helper
5 | {
6 | internal class CanvasHelper
7 | {
8 | public CanvasControl Canvas_Selection = null;
9 | public CanvasControl Canvas_Linenumber = null;
10 | public CanvasControl Canvas_Text = null;
11 | public CanvasControl Canvas_Cursor = null;
12 |
13 | public CanvasHelper(CanvasControl canvas_selection, CanvasControl canvas_linenumber, CanvasControl canvas_text, CanvasControl canvas_cursor)
14 | {
15 | this.Canvas_Text = canvas_text;
16 | this.Canvas_Linenumber = canvas_linenumber;
17 | this.Canvas_Cursor = canvas_cursor;
18 | this.Canvas_Selection = canvas_selection;
19 | }
20 |
21 | public void UpdateCursor()
22 | {
23 | Canvas_Cursor.Invalidate();
24 | }
25 | public void UpdateText()
26 | {
27 | Utils.ChangeCursor(CoreCursorType.IBeam);
28 | Canvas_Text.Invalidate();
29 | }
30 | public void UpdateSelection()
31 | {
32 | Canvas_Selection.Invalidate();
33 | }
34 | public void UpdateAll()
35 | {
36 | UpdateText();
37 | UpdateSelection();
38 | UpdateCursor();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/TextControlBox/Models/CodeLanguage.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents a code language configuration used for syntax highlighting and auto-pairing in the text content.
5 | ///
6 | public class CodeLanguage
7 | {
8 | ///
9 | /// Gets or sets the name of the code language.
10 | ///
11 | public string Name { get; set; }
12 |
13 | ///
14 | /// Gets or sets the description of the code language.
15 | ///
16 | public string Description { get; set; }
17 |
18 | ///
19 | /// Gets or sets an array of file filters for the code language.
20 | ///
21 | public string[] Filter { get; set; }
22 |
23 | ///
24 | /// Gets or sets the author of the code language definition.
25 | ///
26 | public string Author { get; set; }
27 |
28 | ///
29 | /// Gets or sets an array of syntax highlights for the code language.
30 | ///
31 | public SyntaxHighlights[] Highlights { get; set; }
32 |
33 | ///
34 | /// Gets or sets an array of auto-pairing pairs for the code language.
35 | ///
36 | public AutoPairingPair[] AutoPairingPair { get; set; }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Properties/Default.rd.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/TextControlBox/Models/ScrollBarPosition.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the position of a scrollbar, containing the horizontal and vertical scroll positions.
5 | ///
6 | public class ScrollBarPosition
7 | {
8 | ///
9 | /// Initializes a new instance of the ScrollBarPosition class with the provided values.
10 | ///
11 | /// The existing ScrollBarPosition object from which to copy the values.
12 |
13 | public ScrollBarPosition(ScrollBarPosition scrollBarPosition)
14 | {
15 | this.ValueX = scrollBarPosition.ValueX;
16 | this.ValueY = scrollBarPosition.ValueY;
17 | }
18 |
19 | ///
20 | /// Creates a new instance of the ScrollBarPosition class
21 | ///
22 | /// The horizontal amount scrolled
23 | /// The vertical amount scrolled
24 | public ScrollBarPosition(double valueX = 0, double valueY = 0)
25 | {
26 | this.ValueX = valueX;
27 | this.ValueY = valueY;
28 | }
29 |
30 | ///
31 | /// The amount scrolled horizontally
32 | ///
33 | public double ValueX { get; set; }
34 |
35 | ///
36 | /// The amount scrolled vertically
37 | ///
38 | public double ValueY { get; set; }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/TextControlBox/Helper/FlyoutHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Windows.UI.Xaml.Controls;
3 |
4 | namespace TextControlBox.Helper
5 | {
6 | internal class FlyoutHelper
7 | {
8 | public MenuFlyout MenuFlyout;
9 |
10 | public FlyoutHelper(TextControlBox sender)
11 | {
12 | CreateFlyout(sender);
13 | }
14 |
15 | public void CreateFlyout(TextControlBox sender)
16 | {
17 | MenuFlyout = new MenuFlyout();
18 | MenuFlyout.Items.Add(CreateItem(() => { sender.Copy(); }, "Copy", Symbol.Copy, "Ctrl + C"));
19 | MenuFlyout.Items.Add(CreateItem(() => { sender.Paste(); }, "Paste", Symbol.Paste, "Ctrl + V"));
20 | MenuFlyout.Items.Add(CreateItem(() => { sender.Cut(); }, "Cut", Symbol.Cut, "Ctrl + X"));
21 | MenuFlyout.Items.Add(new MenuFlyoutSeparator());
22 | MenuFlyout.Items.Add(CreateItem(() => { sender.Undo(); }, "Undo", Symbol.Undo, "Ctrl + Z"));
23 | MenuFlyout.Items.Add(CreateItem(() => { sender.Redo(); }, "Redo", Symbol.Redo, "Ctrl + Y"));
24 | }
25 |
26 | public MenuFlyoutItem CreateItem(Action action, string text, Symbol icon, string key)
27 | {
28 | var item = new MenuFlyoutItem
29 | {
30 | Text = text,
31 | KeyboardAcceleratorTextOverride = key,
32 | Icon = new SymbolIcon { Symbol = icon }
33 | };
34 | item.Click += delegate
35 | {
36 | action();
37 | };
38 | return item;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/TextControlBox/Text/AutoPairing.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace TextControlBox.Text
4 | {
5 | internal class AutoPairing
6 | {
7 | public static (string text, int length) AutoPair(TextControlBox textbox, string inputtext)
8 | {
9 | if (!textbox.DoAutoPairing || inputtext.Length != 1 || textbox.CodeLanguage == null || textbox.CodeLanguage.AutoPairingPair == null)
10 | return (inputtext, inputtext.Length);
11 |
12 | var res = textbox.CodeLanguage.AutoPairingPair.Where(x => x.Matches(inputtext));
13 | if (res.Count() == 0)
14 | return (inputtext, inputtext.Length);
15 |
16 | if (res.ElementAt(0) is AutoPairingPair pair)
17 | return (inputtext + pair.Pair, inputtext.Length);
18 | return (inputtext, inputtext.Length);
19 | }
20 |
21 | public static string AutoPairSelection(TextControlBox textbox, string inputtext)
22 | {
23 | if (!textbox.DoAutoPairing || inputtext.Length != 1 || textbox.CodeLanguage == null || textbox.CodeLanguage.AutoPairingPair == null)
24 | return inputtext;
25 |
26 | var res = textbox.CodeLanguage.AutoPairingPair.Where(x => x.Value.Equals(inputtext));
27 | if (res.Count() == 0)
28 | return inputtext;
29 |
30 | if (res.ElementAt(0) is AutoPairingPair pair)
31 | {
32 | textbox.SurroundSelectionWith(inputtext, pair.Pair);
33 | return null;
34 | }
35 | return inputtext;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TextControlBox/Models/CursorSize.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the size of the cursor in the textbox.
5 | ///
6 | public class CursorSize
7 | {
8 | ///
9 | /// Creates a new instance of the CursorSize class
10 | ///
11 | /// The width of the cursor
12 | /// The height of the cursor
13 | /// The x-offset from the actual cursor position
14 | /// The y-offset from the actual cursor position
15 | public CursorSize(float width = 0, float height = 0, float offsetX = 0, float offsetY = 0)
16 | {
17 | this.Width = width;
18 | this.Height = height;
19 | this.OffsetX = offsetX;
20 | this.OffsetY = offsetY;
21 | }
22 |
23 | ///
24 | /// The width of the cursor
25 | ///
26 | public float Width { get; private set; }
27 |
28 | ///
29 | /// The height of the cursor
30 | ///
31 | public float Height { get; private set; }
32 |
33 | ///
34 | /// The left/right offset from the actual cursor position
35 | ///
36 | public float OffsetX { get; private set; }
37 |
38 | ///
39 | /// The top/bottom offset from the actual cursor position
40 | ///
41 | public float OffsetY { get; private set; }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TextControlBox/Models/CodeFontStyle.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the font style settings for a code element in the text.
5 | ///
6 | public class CodeFontStyle
7 | {
8 | ///
9 | /// Initializes a new instance of the CodeFontStyle class with the specified font style settings.
10 | ///
11 | /// true if the code element should be displayed with an underline; otherwise, false.
12 | /// true if the code element should be displayed in italic font; otherwise, false.
13 | /// true if the code element should be displayed in bold font; otherwise, false.
14 | public CodeFontStyle(bool underlined, bool italic, bool bold)
15 | {
16 | this.Italic = italic;
17 | this.Bold = bold;
18 | this.Underlined = underlined;
19 | }
20 |
21 | ///
22 | /// Gets or sets a value indicating whether the code element should be displayed with an underline.
23 | ///
24 | public bool Underlined { get; set; }
25 |
26 | ///
27 | /// Gets or sets a value indicating whether the code element should be displayed in bold font.
28 | ///
29 | public bool Bold { get; set; }
30 |
31 | ///
32 | /// Gets or sets a value indicating whether the code element should be displayed in italic font.
33 | ///
34 | public bool Italic { get; set; }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/TextControlBox/Renderer/SearchHighlightsRenderer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graphics.Canvas.Text;
2 | using Microsoft.Graphics.Canvas.UI.Xaml;
3 | using System;
4 | using System.Text.RegularExpressions;
5 | using TextControlBox.Helper;
6 | using Windows.UI;
7 |
8 | namespace TextControlBox.Renderer
9 | {
10 | internal class SearchHighlightsRenderer
11 | {
12 | public static void RenderHighlights(
13 | CanvasDrawEventArgs args,
14 | CanvasTextLayout drawnTextLayout,
15 | string renderedText,
16 | int[] possibleLines,
17 | string searchRegex,
18 | float scrollOffsetX,
19 | float offsetTop,
20 | Color searchHighlightColor)
21 | {
22 | if (searchRegex == null || possibleLines == null || possibleLines.Length == 0)
23 | return;
24 |
25 | MatchCollection matches;
26 | try
27 | {
28 | matches = Regex.Matches(renderedText, searchRegex);
29 | }
30 | catch (ArgumentException)
31 | {
32 | return;
33 | }
34 | for (int j = 0; j < matches.Count; j++)
35 | {
36 | var match = matches[j];
37 |
38 | var layoutRegion = drawnTextLayout.GetCharacterRegions(match.Index, match.Length);
39 | if (layoutRegion.Length > 0)
40 | {
41 | args.DrawingSession.FillRectangle(Utils.CreateRect(layoutRegion[0].LayoutBounds, scrollOffsetX, offsetTop), searchHighlightColor);
42 | }
43 | }
44 | return;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/TextControlBox-TestApp/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
13 |
14 |
15 |
16 |
17 | TextControlBox-TestApp
18 | juliu
19 | Assets\StoreLogo.png
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/TextControlBox/Properties/TextControlBox.rd.xml:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/TextControlBox/Models/AutoPairingPair.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents a pair of characters used for auto-pairing in text.
5 | ///
6 | public class AutoPairingPair
7 | {
8 | ///
9 | /// Initializes a new instance of the AutoPairingPair class with the same value for both the opening and closing characters.
10 | ///
11 | /// The character value to be used for both opening and closing.
12 | public AutoPairingPair(string value)
13 | {
14 | this.Value = this.Pair = value;
15 | }
16 |
17 | ///
18 | /// Initializes a new instance of the AutoPairingPair class with different values for the opening and closing characters.
19 | ///
20 | /// The character value to be used as the opening character.
21 | /// The character value to be used as the closing character.
22 | public AutoPairingPair(string value, string pair)
23 | {
24 | this.Value = value;
25 | this.Pair = pair;
26 | }
27 |
28 | ///
29 | /// Checks if the provided input contains the opening character of the pair.
30 | ///
31 | /// The input string to check for the opening character.
32 | /// True if the input contains the opening character; otherwise, false.
33 | public bool Matches(string input) => input.Contains(this.Value);
34 |
35 | ///
36 | /// Gets or sets the character value used for the opening part of the auto-pairing pair.
37 | ///
38 | public string Value { get; set; }
39 |
40 | ///
41 | /// Gets or sets the character value used for the closing part of the auto-pairing pair.
42 | ///
43 | public string Pair { get; set; }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/TextControlBox/Models/TextSelection.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox.Text
2 | {
3 | internal class TextSelection
4 | {
5 | public TextSelection()
6 | {
7 | Index = 0;
8 | Length = 0;
9 | StartPosition = null;
10 | EndPosition = null;
11 | }
12 | public TextSelection(int index = 0, int length = 0, CursorPosition startPosition = null, CursorPosition endPosition = null)
13 | {
14 | Index = index;
15 | Length = length;
16 | StartPosition = startPosition;
17 | EndPosition = endPosition;
18 | }
19 | public TextSelection(CursorPosition startPosition = null, CursorPosition endPosition = null)
20 | {
21 | StartPosition = startPosition;
22 | EndPosition = endPosition;
23 | }
24 | public TextSelection(TextSelection textSelection)
25 | {
26 | StartPosition = new CursorPosition(textSelection.StartPosition);
27 | EndPosition = new CursorPosition(textSelection.EndPosition);
28 | Index = textSelection.Index;
29 | Length = textSelection.Length;
30 | }
31 |
32 | public int Index { get; set; }
33 | public int Length { get; set; }
34 |
35 | public CursorPosition StartPosition { get; set; }
36 | public CursorPosition EndPosition { get; set; }
37 |
38 | public bool IsLineInSelection(int line)
39 | {
40 | if (this.StartPosition != null && this.EndPosition != null)
41 | {
42 | if (this.StartPosition.LineNumber > this.EndPosition.LineNumber)
43 | return this.StartPosition.LineNumber < line && this.EndPosition.LineNumber > line;
44 | else if (this.StartPosition.LineNumber == this.EndPosition.LineNumber)
45 | return this.StartPosition.LineNumber != line;
46 | else
47 | return this.StartPosition.LineNumber > line && this.EndPosition.LineNumber < line;
48 | }
49 | return false;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/TextControlBox/Models/CursorPosition.cs:
--------------------------------------------------------------------------------
1 | namespace TextControlBox
2 | {
3 | ///
4 | /// Represents the position of the cursor in the textbox.
5 | ///
6 | ///
7 | /// The CursorPosition class stores the position of the cursor within the textbox.
8 | /// It consists of two properties: CharacterPosition and LineNumber.
9 | /// The CharacterPosition property indicates the index of the cursor within the current line (zero-based index).
10 | /// The LineNumber property represents the line number on which the cursor is currently positioned (zero-based index).
11 | ///
12 | public class CursorPosition
13 | {
14 | internal CursorPosition(int characterPosition = 0, int lineNumber = 0)
15 | {
16 | this.CharacterPosition = characterPosition;
17 | this.LineNumber = lineNumber;
18 | }
19 | internal CursorPosition(CursorPosition currentCursorPosition)
20 | {
21 | if (currentCursorPosition == null)
22 | return;
23 |
24 | this.CharacterPosition = currentCursorPosition.CharacterPosition;
25 | this.LineNumber = currentCursorPosition.LineNumber;
26 | }
27 | ///
28 | /// Gets the character position of the cursor within the current line.
29 | ///
30 | public int CharacterPosition { get; internal set; } = 0;
31 | ///
32 | /// Gets the line number in which the cursor is currently positioned.
33 | ///
34 | public int LineNumber { get; internal set; } = 0;
35 |
36 | internal void AddToCharacterPos(int add)
37 | {
38 | CharacterPosition += add;
39 | }
40 | internal void SubtractFromCharacterPos(int subtract)
41 | {
42 | CharacterPosition -= subtract;
43 | if (CharacterPosition < 0)
44 | CharacterPosition = 0;
45 | }
46 | internal static CursorPosition ChangeLineNumber(CursorPosition currentCursorPosition, int lineNumber)
47 | {
48 | return new CursorPosition(currentCursorPosition.CharacterPosition, lineNumber);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/TextControlBox/Extensions/StringExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.RegularExpressions;
3 | using TextControlBox.Helper;
4 |
5 | namespace TextControlBox.Extensions
6 | {
7 | internal static class StringExtension
8 | {
9 | public static string RemoveFirstOccurence(this string value, string removeString)
10 | {
11 | int index = value.IndexOf(removeString, StringComparison.Ordinal);
12 | return index < 0 ? value : value.Remove(index, removeString.Length);
13 | }
14 |
15 | public static string AddToEnd(this string text, string add)
16 | {
17 | return text + add;
18 | }
19 | public static string AddToStart(this string text, string add)
20 | {
21 | return add + text;
22 | }
23 | public static string AddText(this string text, string add, int position)
24 | {
25 | if (position < 0)
26 | position = 0;
27 |
28 | if (position >= text.Length || text.Length <= 0)
29 | return text + add;
30 | else
31 | return text.Insert(position, add);
32 | }
33 | public static string SafeRemove(this string text, int start, int count = -1)
34 | {
35 | if (start >= text.Length || start < 0)
36 | return text;
37 |
38 | if (count <= -1)
39 | return text.Remove(start);
40 | else
41 | return text.Remove(start, count);
42 | }
43 | public static bool Contains(this string text, SearchParameter parameter)
44 | {
45 | if (parameter.WholeWord)
46 | return Regex.IsMatch(text, parameter.SearchExpression, RegexOptions.Compiled);
47 |
48 | if (parameter.MatchCase)
49 | return text.Contains(parameter.Word, StringComparison.Ordinal);
50 | else
51 | return text.Contains(parameter.Word, StringComparison.OrdinalIgnoreCase);
52 | }
53 | public static string Safe_Substring(this string text, int index, int count = -1)
54 | {
55 | if (index >= text.Length)
56 | return "";
57 | else if (count == -1)
58 | return text.Substring(index);
59 | else
60 | return text.Substring(index, count);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/TextControlBox/Helper/ListHelper.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using System;
3 | using System.Linq;
4 |
5 | namespace TextControlBox.Helper
6 | {
7 | internal class ListHelper
8 | {
9 | public struct ValueResult
10 | {
11 | public ValueResult(int index, int count)
12 | {
13 | this.Index = index;
14 | this.Count = count;
15 | }
16 | public int Index;
17 | public int Count;
18 | }
19 | public static ValueResult CheckValues(PooledList totalLines, int index, int count)
20 | {
21 | if (index >= totalLines.Count)
22 | {
23 | index = totalLines.Count - 1 < 0 ? 0 : totalLines.Count - 1;
24 | count = 0;
25 | }
26 | if (index + count >= totalLines.Count)
27 | {
28 | int difference = totalLines.Count - index;
29 | if (difference >= 0)
30 | count = difference;
31 | }
32 |
33 | if (count < 0)
34 | count = 0;
35 | if (index < 0)
36 | index = 0;
37 |
38 | return new ValueResult(index, count);
39 | }
40 |
41 | public static void GCList(PooledList totalLines)
42 | {
43 | int id = GC.GetGeneration(totalLines);
44 | GC.Collect(id, GCCollectionMode.Forced);
45 | }
46 |
47 | public static void Clear(PooledList totalLines, bool addNewLine = false)
48 | {
49 | totalLines.Clear();
50 | GCList(totalLines);
51 |
52 | if (addNewLine)
53 | totalLines.Add("");
54 | }
55 |
56 | public static string GetLinesAsString(PooledList lines, string newLineCharacter)
57 | {
58 | return string.Join(newLineCharacter, lines);
59 | }
60 | public static string[] GetLinesFromString(string content, string newLineCharacter)
61 | {
62 | return content.Split(newLineCharacter);
63 | }
64 | public static string[] CreateLines(string[] lines, int start, string beginning, string end)
65 | {
66 | if (start > 0)
67 | lines = lines.Skip(start).ToArray();
68 |
69 | lines[0] = beginning + lines[0];
70 | if (lines.Length - 1 > 0)
71 | lines[lines.Length - 1] = lines[lines.Length - 1] + end;
72 | return lines;
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/TextControlBox/Helper/TabSpaceHelper.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using System;
3 |
4 | namespace TextControlBox.Helper
5 | {
6 | internal class TabSpaceHelper
7 | {
8 | private int _NumberOfSpaces = 4;
9 | private string OldSpaces = " ";
10 |
11 | public int NumberOfSpaces
12 | {
13 | get => _NumberOfSpaces;
14 | set
15 | {
16 | if (value != _NumberOfSpaces)
17 | {
18 | OldSpaces = Spaces;
19 | _NumberOfSpaces = value;
20 | Spaces = new string(' ', _NumberOfSpaces);
21 | }
22 | }
23 | }
24 | public bool UseSpacesInsteadTabs = false;
25 | public string TabCharacter { get => UseSpacesInsteadTabs ? Spaces : Tab; }
26 | private string Spaces = " ";
27 | private string Tab = "\t";
28 |
29 | public void UpdateNumberOfSpaces(PooledList totalLines)
30 | {
31 | ReplaceSpacesToSpaces(totalLines);
32 | }
33 |
34 | public void UpdateTabs(PooledList totalLines)
35 | {
36 | if (UseSpacesInsteadTabs)
37 | ReplaceTabsToSpaces(totalLines);
38 | else
39 | ReplaceSpacesToTabs(totalLines);
40 | }
41 | public string UpdateTabs(string input)
42 | {
43 | if (UseSpacesInsteadTabs)
44 | return Replace(input, Tab, Spaces);
45 | return Replace(input, Spaces, Tab);
46 | }
47 |
48 | private void ReplaceSpacesToSpaces(PooledList totalLines)
49 | {
50 | for (int i = 0; i < totalLines.Count; i++)
51 | {
52 | totalLines[i] = Replace(totalLines[i], OldSpaces, Spaces);
53 | }
54 | }
55 | private void ReplaceSpacesToTabs(PooledList TotalLines)
56 | {
57 | for (int i = 0; i < TotalLines.Count; i++)
58 | {
59 | TotalLines[i] = Replace(TotalLines[i], Spaces, Tab);
60 | }
61 | }
62 | private void ReplaceTabsToSpaces(PooledList totalLines)
63 | {
64 | for (int i = 0; i < totalLines.Count; i++)
65 | {
66 | totalLines[i] = Replace(totalLines[i], "\t", Spaces);
67 | }
68 | }
69 | public string Replace(string input, string find, string replace)
70 | {
71 | return input.Replace(find, replace, StringComparison.OrdinalIgnoreCase);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/TextControlBox/Renderer/CursorRenderer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graphics.Canvas.Brushes;
2 | using Microsoft.Graphics.Canvas.Text;
3 | using Microsoft.Graphics.Canvas.UI.Xaml;
4 | using System;
5 | using System.Numerics;
6 | using Windows.Foundation;
7 |
8 | namespace TextControlBox.Renderer
9 | {
10 | internal class CursorRenderer
11 | {
12 | public static int GetCursorLineFromPoint(Point point, float singleLineHeight, int numberOfRenderedLines, int numberOfStartLine)
13 | {
14 | //Calculate the relative linenumber, where the pointer was pressed at
15 | int linenumber = (int)(point.Y / singleLineHeight);
16 | linenumber += numberOfStartLine;
17 | return Math.Clamp(linenumber, 0, numberOfStartLine + numberOfRenderedLines - 1);
18 | }
19 | public static int GetCharacterPositionFromPoint(string currentLine, CanvasTextLayout textLayout, Point cursorPosition, float marginLeft)
20 | {
21 | if (currentLine == null || textLayout == null)
22 | return 0;
23 |
24 | textLayout.HitTest(
25 | (float)cursorPosition.X - marginLeft, 0,
26 | out var textLayoutRegion);
27 | return textLayoutRegion.CharacterIndex;
28 | }
29 |
30 | //Return the position in pixels of the cursor in the current line
31 | public static float GetCursorPositionInLine(CanvasTextLayout currentLineTextLayout, CursorPosition cursorPosition, float xOffset)
32 | {
33 | if (currentLineTextLayout == null)
34 | return 0;
35 |
36 | return currentLineTextLayout.GetCaretPosition(cursorPosition.CharacterPosition < 0 ? 0 : cursorPosition.CharacterPosition, false).X + xOffset;
37 | }
38 |
39 | //Return the cursor Width
40 | public static void RenderCursor(CanvasTextLayout textLayout, int characterPosition, float xOffset, float y, float fontSize, CursorSize customSize, CanvasDrawEventArgs args, CanvasSolidColorBrush cursorColorBrush)
41 | {
42 | if (textLayout == null)
43 | return;
44 |
45 | Vector2 vector = textLayout.GetCaretPosition(characterPosition < 0 ? 0 : characterPosition, false);
46 | if (customSize == null)
47 | args.DrawingSession.FillRectangle(vector.X + xOffset, y, 1, fontSize, cursorColorBrush);
48 | else
49 | args.DrawingSession.FillRectangle(vector.X + xOffset + customSize.OffsetX, y + customSize.OffsetY, (float)customSize.Width, (float)customSize.Height, cursorColorBrush);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/TextControlBox/Renderer/SyntaxHighlightingRenderer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graphics.Canvas.Text;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Text.RegularExpressions;
5 | using Windows.UI.Text;
6 | using Windows.UI.Xaml;
7 |
8 | namespace TextControlBox.Renderer
9 | {
10 | internal class SyntaxHighlightingRenderer
11 | {
12 | public static FontWeight BoldFont = new FontWeight { Weight = 600 };
13 | public static FontStyle ItalicFont = FontStyle.Italic;
14 |
15 | public static void UpdateSyntaxHighlighting(CanvasTextLayout drawnTextLayout, ApplicationTheme theme, CodeLanguage codeLanguage, bool syntaxHighlighting, string renderedText)
16 | {
17 | if (codeLanguage == null || !syntaxHighlighting)
18 | return;
19 |
20 | var highlights = codeLanguage.Highlights;
21 | for (int i = 0; i < highlights.Length; i++)
22 | {
23 | var matches = Regex.Matches(renderedText, highlights[i].Pattern, RegexOptions.Compiled);
24 | var highlight = highlights[i];
25 | var color = theme == ApplicationTheme.Light ? highlight.ColorLight_Clr : highlight.ColorDark_Clr;
26 |
27 | for (int j = 0; j < matches.Count; j++)
28 | {
29 | var match = matches[j];
30 | int index = match.Index;
31 | int length = match.Length;
32 | drawnTextLayout.SetColor(index, length, color);
33 | if (highlight.CodeStyle != null)
34 | {
35 | if (highlight.CodeStyle.Italic)
36 | drawnTextLayout.SetFontStyle(index, length, ItalicFont);
37 | if (highlight.CodeStyle.Bold)
38 | drawnTextLayout.SetFontWeight(index, length, BoldFont);
39 | if (highlight.CodeStyle.Underlined)
40 | drawnTextLayout.SetUnderline(index, length, true);
41 | }
42 | }
43 | }
44 | }
45 |
46 | public static JsonLoadResult GetCodeLanguageFromJson(string json)
47 | {
48 | try
49 | {
50 | var jsonCodeLanguage = JsonConvert.DeserializeObject(json);
51 | //Apply the filter as an array
52 | var codelanguage = new CodeLanguage
53 | {
54 | Author = jsonCodeLanguage.Author,
55 | Description = jsonCodeLanguage.Description,
56 | Highlights = jsonCodeLanguage.Highlights,
57 | Name = jsonCodeLanguage.Name,
58 | Filter = jsonCodeLanguage.Filter.Split("|", StringSplitOptions.RemoveEmptyEntries),
59 | };
60 | return new JsonLoadResult(true, codelanguage);
61 | }
62 | catch (JsonReaderException)
63 | {
64 | return new JsonLoadResult(false, null);
65 | }
66 | catch (JsonSerializationException)
67 | {
68 | return new JsonLoadResult(false, null);
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/TextControlBox/Helper/TextLayoutHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graphics.Canvas;
2 | using Microsoft.Graphics.Canvas.Text;
3 | using Windows.Foundation;
4 | using Windows.UI.Xaml.Media;
5 |
6 | namespace TextControlBox.Helper
7 | {
8 | internal class TextLayoutHelper
9 | {
10 | public static CanvasTextLayout CreateTextResource(ICanvasResourceCreatorWithDpi resourceCreator, CanvasTextLayout textLayout, CanvasTextFormat textFormat, string text, Size targetSize)
11 | {
12 | if (textLayout != null)
13 | textLayout.Dispose();
14 |
15 | textLayout = CreateTextLayout(resourceCreator, textFormat, text, targetSize);
16 | textLayout.Options = CanvasDrawTextOptions.EnableColorFont;
17 | return textLayout;
18 | }
19 | public static CanvasTextFormat CreateCanvasTextFormat(float zoomedFontSize, FontFamily fontFamily)
20 | {
21 | return CreateCanvasTextFormat(zoomedFontSize, zoomedFontSize + 2, fontFamily);
22 | }
23 |
24 | public static CanvasTextFormat CreateCanvasTextFormat(float zoomedFontSize, float lineSpacing, FontFamily fontFamily)
25 | {
26 | CanvasTextFormat textFormat = new CanvasTextFormat()
27 | {
28 | FontSize = zoomedFontSize,
29 | HorizontalAlignment = CanvasHorizontalAlignment.Left,
30 | VerticalAlignment = CanvasVerticalAlignment.Top,
31 | WordWrapping = CanvasWordWrapping.NoWrap,
32 | LineSpacing = lineSpacing,
33 | };
34 | textFormat.IncrementalTabStop = zoomedFontSize * 3; //default 137px
35 | textFormat.FontFamily = fontFamily.Source;
36 | textFormat.TrimmingGranularity = CanvasTextTrimmingGranularity.None;
37 | textFormat.TrimmingSign = CanvasTrimmingSign.None;
38 | return textFormat;
39 | }
40 | public static CanvasTextLayout CreateTextLayout(ICanvasResourceCreator resourceCreator, CanvasTextFormat textFormat, string text, Size canvasSize)
41 | {
42 | return new CanvasTextLayout(resourceCreator, text, textFormat, (float)canvasSize.Width, (float)canvasSize.Height);
43 | }
44 | public static CanvasTextLayout CreateTextLayout(ICanvasResourceCreator resourceCreator, CanvasTextFormat textFormat, string text, float width, float height)
45 | {
46 | return new CanvasTextLayout(resourceCreator, text, textFormat, width, height);
47 | }
48 | public static CanvasTextFormat CreateLinenumberTextFormat(float fontSize, FontFamily fontFamily)
49 | {
50 | CanvasTextFormat textFormat = new CanvasTextFormat()
51 | {
52 | FontSize = fontSize,
53 | HorizontalAlignment = CanvasHorizontalAlignment.Right,
54 | VerticalAlignment = CanvasVerticalAlignment.Top,
55 | WordWrapping = CanvasWordWrapping.NoWrap,
56 | LineSpacing = fontSize + 2,
57 | };
58 | textFormat.FontFamily = fontFamily.Source;
59 | textFormat.TrimmingGranularity = CanvasTextTrimmingGranularity.None;
60 | textFormat.TrimmingSign = CanvasTrimmingSign.None;
61 | return textFormat;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/TextControlBox/Models/SyntaxHighlights.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using TextControlBox.Extensions;
3 |
4 | namespace TextControlBox
5 | {
6 | ///
7 | /// Represents a syntax highlight definition for a specific pattern in the text.
8 | ///
9 | public class SyntaxHighlights
10 | {
11 | private readonly ColorConverter ColorConverter = new ColorConverter();
12 |
13 | ///
14 | /// Initializes a new instance of the SyntaxHighlights class with the specified pattern, colors, and font styles.
15 | ///
16 | /// The pattern to be highlighted in the text content.
17 | /// The color representation for the pattern in the light theme (e.g., "#RRGGBB" format).
18 | /// The color representation for the pattern in the dark theme (e.g., "#RRGGBB" format).
19 | /// true if the pattern should be displayed in bold font; otherwise, false.
20 | /// true if the pattern should be displayed in italic font; otherwise, false.
21 | /// true if the pattern should be displayed with an underline; otherwise, false.
22 | public SyntaxHighlights(string pattern, string colorLight, string colorDark, bool bold = false, bool italic = false, bool underlined = false)
23 | {
24 | this.Pattern = pattern;
25 | this.ColorDark = colorDark;
26 | this.ColorLight = colorLight;
27 |
28 | if (underlined || italic || bold)
29 | this.CodeStyle = new CodeFontStyle(underlined, italic, bold);
30 | }
31 |
32 | ///
33 | /// Gets or sets the font style for the pattern.
34 | ///
35 | public CodeFontStyle CodeStyle { get; set; } = null;
36 |
37 | ///
38 | /// Gets or sets the pattern to be highlighted in the text content.
39 | ///
40 | public string Pattern { get; set; }
41 |
42 | ///
43 | /// Gets the color representation for the pattern in the dark theme.
44 | ///
45 | public Windows.UI.Color ColorDark_Clr { get; private set; }
46 |
47 | ///
48 | /// Gets the color representation for the pattern in the light theme.
49 | ///
50 | public Windows.UI.Color ColorLight_Clr { get; private set; }
51 |
52 | ///
53 | /// Sets the color representation for the pattern in the dark theme.
54 | ///
55 | /// The color representation for the pattern in the dark theme (e.g., "#RRGGBB" format).
56 | public string ColorDark
57 | {
58 | set => ColorDark_Clr = ((Color)ColorConverter.ConvertFromString(value)).ToMediaColor();
59 | }
60 |
61 | ///
62 | /// Sets the color representation for the pattern in the light theme.
63 | ///
64 | /// The color representation for the pattern in the light theme (e.g., "#RRGGBB" format).
65 | public string ColorLight
66 | {
67 | set => ColorLight_Clr = ((Color)ColorConverter.ConvertFromString(value)).ToMediaColor();
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/TextControlBox/TextControlBox.xaml:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
27 |
28 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
55 |
56 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/TextControlBox/Models/TextControlBoxDesign.cs:
--------------------------------------------------------------------------------
1 | using Windows.UI;
2 | using Windows.UI.Xaml.Media;
3 |
4 | namespace TextControlBox
5 | {
6 | ///
7 | /// Represents the design settings for the textbox.
8 | ///
9 | public class TextControlBoxDesign
10 | {
11 | ///
12 | /// Create an instance of the TextControlBoxDesign from a given design
13 | ///
14 | /// The design to create a new instance from
15 | public TextControlBoxDesign(TextControlBoxDesign design)
16 | {
17 | this.Background = design.Background;
18 | this.TextColor = design.TextColor;
19 | this.SelectionColor = design.SelectionColor;
20 | this.CursorColor = design.CursorColor;
21 | this.LineHighlighterColor = design.LineHighlighterColor;
22 | this.LineNumberColor = design.LineNumberColor;
23 | this.LineNumberBackground = design.LineNumberBackground;
24 | }
25 |
26 | ///
27 | /// Create a new instance of the TextControlBoxDesign class
28 | ///
29 | /// The background color of the textbox
30 | /// The color of the text
31 | /// The color of the selection
32 | /// The color of the cursor
33 | /// The color of the linehighlighter
34 | /// The color of the linenumber
35 | /// The background color of the linenumbers
36 | /// The color of the search highlights
37 | public TextControlBoxDesign(Brush background, Color textColor, Color selectionColor, Color cursorColor, Color lineHighlighterColor, Color lineNumberColor, Color lineNumberBackground, Color searchHighlightColor)
38 | {
39 | this.Background = background;
40 | this.TextColor = textColor;
41 | this.SelectionColor = selectionColor;
42 | this.CursorColor = cursorColor;
43 | this.LineHighlighterColor = lineHighlighterColor;
44 | this.LineNumberColor = lineNumberColor;
45 | this.LineNumberBackground = lineNumberBackground;
46 | this.SearchHighlightColor = searchHighlightColor;
47 | }
48 |
49 | ///
50 | /// Gets or sets the background color of the textbox.
51 | ///
52 | public Brush Background { get; set; }
53 |
54 | ///
55 | /// Gets or sets the text color of the textbox.
56 | ///
57 | public Color TextColor { get; set; }
58 |
59 | ///
60 | /// Gets or sets the color of the selected text in the textbox.
61 | ///
62 | public Color SelectionColor { get; set; }
63 |
64 | ///
65 | /// Gets or sets the color of the cursor in the textbox.
66 | ///
67 | public Color CursorColor { get; set; }
68 |
69 | ///
70 | /// Gets or sets the color of the line highlighter in the textbox.
71 | ///
72 | public Color LineHighlighterColor { get; set; }
73 |
74 | ///
75 | /// Gets or sets the color of the line numbers in the textbox.
76 | ///
77 | public Color LineNumberColor { get; set; }
78 |
79 | ///
80 | /// Gets or sets the background color of the line numbers in the textbox.
81 | ///
82 | public Color LineNumberBackground { get; set; }
83 |
84 | ///
85 | /// Gets or sets the color used to highlight search results in the textbox.
86 | ///
87 | public Color SearchHighlightColor { get; set; }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/TextControlBox-TestApp/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Windows.ApplicationModel;
3 | using Windows.ApplicationModel.Activation;
4 | using Windows.UI.Xaml;
5 | using Windows.UI.Xaml.Controls;
6 | using Windows.UI.Xaml.Navigation;
7 |
8 | namespace TextControlBox_TestApp
9 | {
10 | ///
11 | /// Stellt das anwendungsspezifische Verhalten bereit, um die Standardanwendungsklasse zu ergänzen.
12 | ///
13 | sealed partial class App : Application
14 | {
15 | ///
16 | /// Initialisiert das Singletonanwendungsobjekt. Dies ist die erste Zeile von erstelltem Code
17 | /// und daher das logische Äquivalent von main() bzw. WinMain().
18 | ///
19 | public App()
20 | {
21 | this.InitializeComponent();
22 | this.Suspending += OnSuspending;
23 | }
24 |
25 | ///
26 | /// Wird aufgerufen, wenn die Anwendung durch den Endbenutzer normal gestartet wird. Weitere Einstiegspunkte
27 | /// werden z. B. verwendet, wenn die Anwendung gestartet wird, um eine bestimmte Datei zu öffnen.
28 | ///
29 | /// Details über Startanforderung und -prozess.
30 | protected override void OnLaunched(LaunchActivatedEventArgs e)
31 | {
32 | Frame rootFrame = Window.Current.Content as Frame;
33 |
34 | // App-Initialisierung nicht wiederholen, wenn das Fenster bereits Inhalte enthält.
35 | // Nur sicherstellen, dass das Fenster aktiv ist.
36 | if (rootFrame == null)
37 | {
38 | // Frame erstellen, der als Navigationskontext fungiert und zum Parameter der ersten Seite navigieren
39 | rootFrame = new Frame();
40 |
41 | rootFrame.NavigationFailed += OnNavigationFailed;
42 |
43 | if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
44 | {
45 | //TODO: Zustand von zuvor angehaltener Anwendung laden
46 | }
47 |
48 | // Den Frame im aktuellen Fenster platzieren
49 | Window.Current.Content = rootFrame;
50 | }
51 |
52 | if (e.PrelaunchActivated == false)
53 | {
54 | if (rootFrame.Content == null)
55 | {
56 | // Wenn der Navigationsstapel nicht wiederhergestellt wird, zur ersten Seite navigieren
57 | // und die neue Seite konfigurieren, indem die erforderlichen Informationen als Navigationsparameter
58 | // übergeben werden
59 | rootFrame.Navigate(typeof(MainPage), e.Arguments);
60 | }
61 | // Sicherstellen, dass das aktuelle Fenster aktiv ist
62 | Window.Current.Activate();
63 | }
64 | }
65 |
66 | ///
67 | /// Wird aufgerufen, wenn die Navigation auf eine bestimmte Seite fehlschlägt
68 | ///
69 | /// Der Rahmen, bei dem die Navigation fehlgeschlagen ist
70 | /// Details über den Navigationsfehler
71 | void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
72 | {
73 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
74 | }
75 |
76 | ///
77 | /// Wird aufgerufen, wenn die Ausführung der Anwendung angehalten wird. Der Anwendungszustand wird gespeichert,
78 | /// ohne zu wissen, ob die Anwendung beendet oder fortgesetzt wird und die Speicherinhalte dabei
79 | /// unbeschädigt bleiben.
80 | ///
81 | /// Die Quelle der Anhalteanforderung.
82 | /// Details zur Anhalteanforderung.
83 | private void OnSuspending(object sender, SuspendingEventArgs e)
84 | {
85 | var deferral = e.SuspendingOperation.GetDeferral();
86 | //TODO: Anwendungszustand speichern und alle Hintergrundaktivitäten beenden
87 | deferral.Complete();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/TextControlBox-TestApp/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Text;
6 | using TextControlBox;
7 | using TextControlBox.Helper;
8 | using TextControlBox.Text;
9 | using Windows.Storage;
10 | using Windows.Storage.AccessCache;
11 | using Windows.Storage.Pickers;
12 | using Windows.UI;
13 | using Windows.UI.Xaml;
14 | using Windows.UI.Xaml.Controls;
15 | using Windows.UI.Xaml.Media;
16 |
17 | namespace TextControlBox_TestApp
18 | {
19 | public sealed partial class MainPage : Page
20 | {
21 | public MainPage()
22 | {
23 | this.InitializeComponent();
24 | Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown;
25 |
26 | textbox.FontSize = 20;
27 | Load();
28 |
29 | /*TextControlBox.Design = new TextControlBoxDesign
30 | {
31 | Background = new SolidColorBrush(Color.FromArgb(255, 59, 46, 69)),
32 | CursorColor = Color.FromArgb(255, 255, 100, 255),
33 | LineHighlighterColor = Color.FromArgb(255, 79, 66, 89),
34 | LineNumberBackground = Color.FromArgb(255, 59, 46, 69),
35 | LineNumberColor = Color.FromArgb(255, 93, 2, 163),
36 | TextColor = Color.FromArgb(255, 144, 0, 255),
37 | SelectionColor = Color.FromArgb(100, 144, 0, 255)
38 | };*/
39 | }
40 | private async void Load()
41 | {
42 | textbox.CodeLanguage = TextControlBox.TextControlBox.GetCodeLanguageFromId("C#");
43 | textbox.SyntaxHighlighting = true;
44 | textbox.LoadLines(GenerateContent());
45 | }
46 | private IEnumerable GenerateContent()
47 | {
48 | for (int i = 1000; i > 0; i--)
49 | {
50 | yield return "Line: " + i;
51 | }
52 | }
53 |
54 | private async void CoreWindow_KeyDown(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.KeyEventArgs args)
55 | {
56 | bool ControlKey = Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
57 | if (ControlKey && args.VirtualKey == Windows.System.VirtualKey.R)
58 | {
59 | textbox.LoadLines(GenerateContent());
60 | }
61 | if (ControlKey && args.VirtualKey == Windows.System.VirtualKey.E)
62 | {
63 | Load();
64 | }
65 | if (ControlKey && args.VirtualKey == Windows.System.VirtualKey.D)
66 | {
67 | textbox.RequestedTheme = textbox.RequestedTheme == ElementTheme.Dark ?
68 | ElementTheme.Light : textbox.RequestedTheme == ElementTheme.Default ? ElementTheme.Light : ElementTheme.Dark;
69 | }
70 | if (ControlKey && args.VirtualKey == Windows.System.VirtualKey.L)
71 | {
72 | textbox.ShowLineNumbers = !textbox.ShowLineNumbers;
73 | }
74 | if (ControlKey && args.VirtualKey == Windows.System.VirtualKey.O)
75 | {
76 | FileOpenPicker openPicker = new FileOpenPicker();
77 | openPicker.ViewMode = PickerViewMode.Thumbnail;
78 | openPicker.SuggestedStartLocation = PickerLocationId.Desktop;
79 | openPicker.FileTypeFilter.Add("*");
80 |
81 | var file = await openPicker.PickSingleFileAsync();
82 | if(file != null)
83 | {
84 | textbox.LoadLines(await FileIO.ReadLinesAsync(file));
85 | }
86 | }
87 | if(ControlKey && args.VirtualKey == Windows.System.VirtualKey.S)
88 | {
89 | FileSavePicker savepicker = new FileSavePicker();
90 | savepicker.SuggestedStartLocation = PickerLocationId.Desktop;
91 | savepicker.FileTypeChoices.Add("Plain Text", new List() { ".txt" });
92 |
93 | var file = await savepicker.PickSaveFileAsync();
94 | if (file != null)
95 | {
96 | //await FileIO.WriteLinesAsync(file, GenerateContent());
97 | await FileIO.WriteLinesAsync(file, textbox.Lines);
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/images/Icon1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
40 |
42 |
45 |
49 |
53 |
54 |
64 |
74 |
77 |
81 |
85 |
86 |
87 |
92 |
100 |
106 |
118 | </>|
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/TextControlBox/Text/TabKey.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using TextControlBox.Extensions;
3 |
4 | namespace TextControlBox.Text
5 | {
6 | internal class TabKey
7 | {
8 | public static TextSelection MoveTabBack(PooledList totalLines, TextSelection textSelection, CursorPosition cursorPosition, string tabCharacter, string newLineCharacter, UndoRedo undoRedo)
9 | {
10 | if (textSelection == null)
11 | {
12 | string line = totalLines.GetLineText(cursorPosition.LineNumber);
13 | if (line.Contains(tabCharacter, System.StringComparison.Ordinal) && cursorPosition.CharacterPosition > 0)
14 | cursorPosition.SubtractFromCharacterPos(tabCharacter.Length);
15 |
16 | undoRedo.RecordUndoAction(() =>
17 | {
18 | totalLines.SetLineText(cursorPosition.LineNumber, line.RemoveFirstOccurence(tabCharacter));
19 | }, totalLines, cursorPosition.LineNumber, 1, 1, newLineCharacter);
20 |
21 | return new TextSelection(cursorPosition, null);
22 | }
23 |
24 | textSelection = Selection.OrderTextSelection(textSelection);
25 | int selectedLinesCount = textSelection.EndPosition.LineNumber - textSelection.StartPosition.LineNumber;
26 |
27 | TextSelection tempSel = new TextSelection(textSelection);
28 | tempSel.StartPosition.CharacterPosition = 0;
29 | tempSel.EndPosition.CharacterPosition = totalLines.GetLineText(textSelection.EndPosition.LineNumber).Length + tabCharacter.Length;
30 |
31 | undoRedo.RecordUndoAction(() =>
32 | {
33 | for (int i = 0; i < selectedLinesCount + 1; i++)
34 | {
35 | int lineIndex = i + textSelection.StartPosition.LineNumber;
36 | string currentLine = totalLines.GetLineText(lineIndex);
37 |
38 | if (i == 0 && currentLine.Contains(tabCharacter, System.StringComparison.Ordinal) && cursorPosition.CharacterPosition > 0)
39 | textSelection.StartPosition.CharacterPosition -= tabCharacter.Length;
40 | else if (i == selectedLinesCount && currentLine.Contains(tabCharacter, System.StringComparison.Ordinal))
41 | {
42 | textSelection.EndPosition.CharacterPosition -= tabCharacter.Length;
43 | }
44 |
45 | totalLines.SetLineText(lineIndex, currentLine.RemoveFirstOccurence(tabCharacter));
46 | }
47 | }, totalLines, tempSel, selectedLinesCount, newLineCharacter);
48 |
49 | return new TextSelection(new CursorPosition(textSelection.StartPosition), new CursorPosition(textSelection.EndPosition));
50 | }
51 |
52 | public static TextSelection MoveTab(PooledList totalLines, TextSelection textSelection, CursorPosition cursorPosition, string tabCharacter, string newLineCharacter, UndoRedo undoRedo)
53 | {
54 | if (textSelection == null)
55 | {
56 | string line = totalLines.GetLineText(cursorPosition.LineNumber);
57 |
58 | undoRedo.RecordUndoAction(() =>
59 | {
60 | totalLines.SetLineText(cursorPosition.LineNumber, line.AddText(tabCharacter, cursorPosition.CharacterPosition));
61 | }, totalLines, cursorPosition.LineNumber, 1, 1, newLineCharacter);
62 |
63 | cursorPosition.AddToCharacterPos(tabCharacter.Length);
64 | return new TextSelection(cursorPosition, null);
65 | }
66 |
67 | textSelection = Selection.OrderTextSelection(textSelection);
68 | int selectedLinesCount = textSelection.EndPosition.LineNumber - textSelection.StartPosition.LineNumber;
69 |
70 | if (textSelection.StartPosition.LineNumber == textSelection.EndPosition.LineNumber) //Singleline
71 | textSelection.StartPosition = Selection.Replace(textSelection, totalLines, tabCharacter, newLineCharacter);
72 | else
73 | {
74 | TextSelection tempSel = new TextSelection(textSelection);
75 | tempSel.StartPosition.CharacterPosition = 0;
76 | tempSel.EndPosition.CharacterPosition = totalLines.GetLineText(textSelection.EndPosition.LineNumber).Length + tabCharacter.Length;
77 |
78 | textSelection.EndPosition.CharacterPosition += tabCharacter.Length;
79 | textSelection.StartPosition.CharacterPosition += tabCharacter.Length;
80 |
81 | undoRedo.RecordUndoAction(() =>
82 | {
83 | for (int i = textSelection.StartPosition.LineNumber; i < selectedLinesCount + textSelection.StartPosition.LineNumber + 1; i++)
84 | {
85 | totalLines.SetLineText(i, totalLines.GetLineText(i).AddToStart(tabCharacter));
86 | }
87 | }, totalLines, tempSel, selectedLinesCount + 1, newLineCharacter);
88 | }
89 | return new TextSelection(new CursorPosition(textSelection.StartPosition), new CursorPosition(textSelection.EndPosition));
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/TextControlBox-UWP.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32228.430
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextControlBox-TestApp", "TextControlBox-TestApp\TextControlBox-TestApp.csproj", "{386FA1B4-59CF-48B8-A3F1-F7644850D47D}"
7 | ProjectSection(ProjectDependencies) = postProject
8 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F} = {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}
9 | EndProjectSection
10 | EndProject
11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextControlBox", "TextControlBox\TextControlBox.csproj", "{2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}"
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Debug|ARM = Debug|ARM
17 | Debug|ARM64 = Debug|ARM64
18 | Debug|x64 = Debug|x64
19 | Debug|x86 = Debug|x86
20 | Release|Any CPU = Release|Any CPU
21 | Release|ARM = Release|ARM
22 | Release|ARM64 = Release|ARM64
23 | Release|x64 = Release|x64
24 | Release|x86 = Release|x86
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|Any CPU.ActiveCfg = Debug|x64
28 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|Any CPU.Build.0 = Debug|x64
29 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|Any CPU.Deploy.0 = Debug|x64
30 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|ARM.ActiveCfg = Debug|ARM
31 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|ARM.Build.0 = Debug|ARM
32 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|ARM.Deploy.0 = Debug|ARM
33 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|ARM64.ActiveCfg = Debug|ARM64
34 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|ARM64.Build.0 = Debug|ARM64
35 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|ARM64.Deploy.0 = Debug|ARM64
36 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|x64.ActiveCfg = Debug|x64
37 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|x64.Build.0 = Debug|x64
38 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|x64.Deploy.0 = Debug|x64
39 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|x86.ActiveCfg = Debug|x64
40 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|x86.Build.0 = Debug|x64
41 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Debug|x86.Deploy.0 = Debug|x64
42 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|Any CPU.ActiveCfg = Release|x64
43 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|Any CPU.Build.0 = Release|x64
44 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|Any CPU.Deploy.0 = Release|x64
45 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|ARM.ActiveCfg = Release|ARM
46 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|ARM.Build.0 = Release|ARM
47 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|ARM.Deploy.0 = Release|ARM
48 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|ARM64.ActiveCfg = Release|ARM64
49 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|ARM64.Build.0 = Release|ARM64
50 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|ARM64.Deploy.0 = Release|ARM64
51 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|x64.ActiveCfg = Release|x64
52 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|x64.Build.0 = Release|x64
53 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|x64.Deploy.0 = Release|x64
54 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|x86.ActiveCfg = Release|x86
55 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|x86.Build.0 = Release|x86
56 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}.Release|x86.Deploy.0 = Release|x86
57 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|ARM.ActiveCfg = Debug|ARM
60 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|ARM.Build.0 = Debug|ARM
61 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|ARM64.ActiveCfg = Debug|ARM64
62 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|ARM64.Build.0 = Debug|ARM64
63 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|x64.ActiveCfg = Debug|x64
64 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|x64.Build.0 = Debug|x64
65 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|x86.ActiveCfg = Debug|x64
66 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Debug|x86.Build.0 = Debug|x64
67 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|Any CPU.ActiveCfg = Release|Any CPU
68 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|Any CPU.Build.0 = Release|Any CPU
69 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|ARM.ActiveCfg = Release|ARM
70 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|ARM.Build.0 = Release|ARM
71 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|ARM64.ActiveCfg = Release|ARM64
72 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|ARM64.Build.0 = Release|ARM64
73 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|x64.ActiveCfg = Release|x64
74 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|x64.Build.0 = Release|x64
75 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|x86.ActiveCfg = Release|x86
76 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}.Release|x86.Build.0 = Release|x86
77 | EndGlobalSection
78 | GlobalSection(SolutionProperties) = preSolution
79 | HideSolutionNode = FALSE
80 | EndGlobalSection
81 | GlobalSection(ExtensibilityGlobals) = postSolution
82 | SolutionGuid = {6762D9BA-308B-4B62-BA24-A116500CA02D}
83 | EndGlobalSection
84 | EndGlobal
85 |
--------------------------------------------------------------------------------
/TextControlBox/Extensions/ListExtension.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using TextControlBox.Helper;
6 |
7 | namespace TextControlBox.Extensions
8 | {
9 | internal static class ListExtension
10 | {
11 | private static int CurrentLineIndex = 0;
12 |
13 | public static string GetLineText(this PooledList list, int index)
14 | {
15 | if (list.Count == 0)
16 | return "";
17 |
18 | if (index == -1 && list.Count > 0)
19 | return list[list.Count - 1];
20 |
21 | index = index >= list.Count ? list.Count - 1 : index > 0 ? index : 0;
22 | return list[index];
23 | }
24 |
25 | public static int GetLineLength(this PooledList list, int index)
26 | {
27 | return GetLineText(list, index).Length;
28 | }
29 |
30 | public static void AddLine(this PooledList list, string content = "")
31 | {
32 | list.Add(content);
33 | }
34 | public static void SetLineText(this PooledList list, int line, string text)
35 | {
36 | if (line == -1)
37 | line = list.Count - 1;
38 |
39 | line = Math.Clamp(line, 0, list.Count - 1);
40 |
41 | list[line] = text;
42 | }
43 | public static string GetCurrentLineText(this PooledList list)
44 | {
45 | return list[CurrentLineIndex < list.Count ? CurrentLineIndex : list.Count - 1 < 0 ? 0 : list.Count - 1];
46 | }
47 | public static void SetCurrentLineText(this PooledList list, string text)
48 | {
49 | list[CurrentLineIndex < list.Count ? CurrentLineIndex : list.Count - 1] = text;
50 | }
51 | public static int CurrentLineLength(this PooledList list)
52 | {
53 | return GetCurrentLineText(list).Length;
54 | }
55 | public static void UpdateCurrentLine(this PooledList _, int currentLine)
56 | {
57 | CurrentLineIndex = currentLine;
58 | }
59 | public static void String_AddToEnd(this PooledList list, int line, string add)
60 | {
61 | list[line] = list[line] + add;
62 | }
63 | public static void InsertOrAdd(this PooledList list, int index, string lineText)
64 | {
65 | if (index >= list.Count || index == -1)
66 | list.Add(lineText);
67 | else
68 | list.Insert(index, lineText);
69 | }
70 | public static void InsertOrAddRange(this PooledList list, IEnumerable lines, int index)
71 | {
72 | if (index >= list.Count)
73 | list.AddRange(lines);
74 | else
75 | list.InsertRange(index < 0 ? 0 : index, lines);
76 | }
77 | public static void InsertNewLine(this PooledList list, int index, string content = "")
78 | {
79 | list.InsertOrAdd(index, content);
80 | }
81 | public static void DeleteAt(this PooledList list, int index)
82 | {
83 | if (index >= list.Count)
84 | index = list.Count - 1 < 0 ? list.Count - 1 : 0;
85 |
86 | list.RemoveAt(index);
87 | list.TrimExcess();
88 | }
89 | private static IEnumerable GetLines_Small(this PooledList totalLines, int start, int count)
90 | {
91 | var res = ListHelper.CheckValues(totalLines, start, count);
92 |
93 | for (int i = 0; i < res.Count; i++)
94 | {
95 | yield return totalLines[i + res.Index];
96 | }
97 | }
98 |
99 | public static IEnumerable GetLines(this PooledList totalLines, int start, int count)
100 | {
101 | //use different algorithm for small amount of lines:
102 | if (start + count < 400)
103 | {
104 | return GetLines_Small(totalLines, start, count);
105 | }
106 |
107 | return totalLines.Skip(start).Take(count);
108 | }
109 |
110 | public static string GetString(this IEnumerable lines, string NewLineCharacter)
111 | {
112 | return string.Join(NewLineCharacter, lines);
113 | }
114 |
115 | public static void Safe_RemoveRange(this PooledList totalLines, int index, int count)
116 | {
117 | var res = ListHelper.CheckValues(totalLines, index, count);
118 | totalLines.RemoveRange(res.Index, res.Count);
119 | totalLines.TrimExcess();
120 |
121 | //clear up the memory of the list when more than 1mio items are removed
122 | if (res.Count > 1_000_000)
123 | ListHelper.GCList(totalLines);
124 | }
125 | public static string[] GetLines(this string[] lines, int start, int count)
126 | {
127 | return lines.Skip(start).Take(count).ToArray();
128 | }
129 |
130 | public static void SwapLines(this PooledList lines, int originalindex, int newindex)
131 | {
132 | string oldLine = lines.GetLineText(originalindex);
133 | lines.SetLineText(originalindex, lines.GetLineText(newindex));
134 | lines.SetLineText(newindex, oldLine);
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
TextControlBox-UWP
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | [](https://www.nuget.org/packages/TextControlBox.JuliusKirsch)
18 |
19 |
20 | ---
21 |
22 | > ⚠️ **This version is deprecated!** Check out the new version for WinUI-3:
23 | > 👉 [TextControlBox-WinUI](https://github.com/FrozenAssassine/TextControlBox-WinUI)
24 |
25 | ---
26 |
27 | ## 🤔 What is TextControlBox?
28 | A UWP-based textbox with syntax highlighting and support for handling very large amounts of text. This project is still in development.
29 |
30 | ## 🛠 Why I Built It
31 | UWP provides a default `TextBox` and `RichTextBox`, but both perform poorly when rendering thousands of lines. Additionally, selection handling is slow. To solve these issues, I created my own textbox control.
32 |
33 | ## 📥 Download
34 |
35 |
36 |
37 |
38 |
39 | ## 🔍 Features:
40 | - Viewing files with a million lines or more without performance issues
41 | - Syntaxhighlighting
42 | - Outstanding performance because it only renders the lines that are needed to display
43 | - Linenumbering
44 | - Linehighlighter
45 | - Json to create custom syntaxhighlighting
46 | - Highly customizable
47 |
48 |
49 | ## ❗ Problems:
50 | - Current text limit is 100 million characters
51 | - Currently there is no textwrapping
52 | - First version for WinUI with many problems and bugs, but working [Github](https://github.com/FrozenAssassine/TextControlBox-WinUI/)
53 |
54 | ## 🚩 Available languages:
55 | - Batch
56 | - Config file
57 | - C++
58 | - C#
59 | - CSV
60 | - CSS
61 | - GCode
62 | - Hex
63 | - Html
64 | - Java
65 | - Javascript
66 | - Json
67 | - Markdown
68 | - LaTex
69 | - PHP
70 | - Python
71 | - QSharp
72 | - Toml
73 | - Xml
74 |
75 | ## 🚀 Usage:
76 |
77 | Properties
78 |
79 | ```
80 | - ScrollBarPosition (get/set)
81 | - CharacterCount (get)
82 | - NumberOfLines (get)
83 | - CurrentLineIndex (get)
84 | - SelectedText (get/set)
85 | - SelectionStart (get/set)
86 | - SelectionLength (get/set)
87 | - ContextFlyoutDisabled (get/set)
88 | - ContextFlyout (get/set)
89 | - CursorSize (get/set)
90 | - ShowLineNumbers (get/set)
91 | - ShowLineHighlighter (get/set)
92 | - ZoomFactor (get/set)
93 | - IsReadonly (get/set)
94 | - Text (get/set)
95 | - RenderedFontSize (get)
96 | - FontSize (get/set)
97 | - FontFamily (get/set)
98 | - Cursorposition (get/set)
99 | - SpaceBetweenLineNumberAndText (get/set)
100 | - LineEnding (get/set)
101 | - SyntaxHighlighting (get/set)
102 | - CodeLanguage (get/set)
103 | - RequestedTheme (get/set)
104 | - Design (get/set)
105 | - static CodeLanguages (get/set)
106 | - VerticalScrollSensitivity (get/set)
107 | - HorizontalScrollSensitivity (get/set)
108 | - VerticalScroll (get/set)
109 | - HorizontalScroll (get/set)
110 | - CornerRadius (get/set)
111 | - UseSpacesInsteadTabs (get/set)
112 | - NumberOfSpacesForTab (get/set)
113 | ```
114 |
115 |
116 | Functions
117 |
118 | ```
119 | - SelectLine(index)
120 | - GoToLine(index)
121 | - SetText(text)
122 | - LoadText(text)
123 | - Paste()
124 | - Copy()
125 | - Cut()
126 | - GetText()
127 | - SetSelection(start, length)
128 | - SelectAll()
129 | - ClearSelection()
130 | - Undo()
131 | - Redo()
132 | - ScrollLineToCenter(line)
133 | - ScrollOneLineUp()
134 | - ScrollOneLineDown()
135 | - ScrollLineIntoView(line)
136 | - ScrollTopIntoView()
137 | - ScrollBottomIntoView()
138 | - ScrollPageUp()
139 | - ScrollPageDown()
140 | - GetLineContent(line)
141 | - GetLinesContent(startline, count)
142 | - SetLineContent(line, text)
143 | - DeleteLine(line)
144 | - AddLine(position, text)
145 | - FindInText(pattern)
146 | - SurroundSelectionWith(value)
147 | - SurroundSelectionWith(value1, value2)
148 | - DuplicateLine(line)
149 | - FindInText(word, up, matchCase, wholeWord)
150 | - ReplaceInText(word, replaceword, up, matchCase, wholeword)
151 | - ReplaceAll(word, replaceword, up, matchCase, wholeword)
152 | - static GetCodeLanguageFromJson(jsondata)
153 | - static SelectCodeLanguageById(identifier)
154 | - Unload()
155 | - ClearUndoRedoHistory()
156 | ```
157 |
158 |
159 | ## Create custom syntaxhighlighting languages with json:
160 | ```json
161 | {
162 | "Highlights": [
163 | {
164 | "CodeStyle": { //optional delete when not used
165 | "Bold": true,
166 | "Underlined": true,
167 | "Italic": true
168 | },
169 | "Pattern": "REGEX PATTERN",
170 | "ColorDark": "#ffffff", //color in dark theme
171 | "ColorLight": "#000000" //color in light theme
172 | },
173 | ],
174 | "Name": "NAME",
175 | "Filter": "EXTENSION1|EXTENSION2", //.cpp|.c
176 | "Description": "DESCRIPTION",
177 | "Author": "AUTHOR"
178 | }
179 | ```
180 |
181 | ### To bind it to the textbox you can use one of these ways:
182 | ```cs
183 |
184 | TextControlBox textbox = new TextControlBox();
185 |
186 | //Use a builtin language -> see list a bit higher
187 | //Language identifiers are case intensitive
188 | textbox.CodeLanguage = TextControlBox.GetCodeLanguageFromId("CSharp");
189 |
190 | //Use a custom language:
191 | var result = TextControlBox.GetCodeLanguageFromJson("JSON DATA");
192 | if(result.Succeed)
193 | textbox.CodeLanguage = result.CodeLanguage;
194 | ```
195 |
196 | ## Create custom designs in C#:
197 | ```cs
198 | textbox.Design = new TextControlBoxDesign(
199 | new SolidColorBrush(Color.FromArgb(255, 30, 30, 30)), //Background brush
200 | Color.FromArgb(255, 255, 255, 255), //Text color
201 | Color.FromArgb(100, 0, 100, 255), //Selection color
202 | Color.FromArgb(255, 255, 255, 255), //Cursor color
203 | Color.FromArgb(50, 100, 100, 100), //Linehighlighter color
204 | Color.FromArgb(255, 100, 100, 100), //Linenumber color
205 | Color.FromArgb(0, 0, 0, 0), //Linenumber background
206 | Color.FromArgb(100,255,150,0) //Search highlight color
207 | );
208 | ```
209 |
210 |
211 | ## 👨👩👧👦 Contributors:
212 | If you want to contribute for this project, feel free to open an issue or a pull request .
213 |
214 | ## 📸 Images
215 |
216 |
217 |
--------------------------------------------------------------------------------
/TextControlBox/Helper/Utils.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using Microsoft.Graphics.Canvas;
3 | using Microsoft.Graphics.Canvas.Text;
4 | using System;
5 | using System.Diagnostics;
6 | using System.Threading.Tasks;
7 | using TextControlBox.Extensions;
8 | using Windows.Foundation;
9 | using Windows.System;
10 | using Windows.UI.Core;
11 | using Windows.UI.Popups;
12 | using Windows.UI.Xaml;
13 |
14 | namespace TextControlBox.Helper
15 | {
16 | internal class Utils
17 | {
18 | public static Size MeasureTextSize(CanvasDevice device, string text, CanvasTextFormat textFormat, float limitedToWidth = 0.0f, float limitedToHeight = 0.0f)
19 | {
20 | CanvasTextLayout layout = new CanvasTextLayout(device, text, textFormat, limitedToWidth, limitedToHeight);
21 | return new Size(layout.DrawBounds.Width, layout.DrawBounds.Height);
22 | }
23 |
24 | public static Size MeasureLineLenght(CanvasDevice device, string text, CanvasTextFormat textFormat)
25 | {
26 | if (text.Length == 0)
27 | return new Size(0, 0);
28 |
29 | //If the text starts with a tab or a whitespace, replace it with the last character of the line, to
30 | //get the actual width of the line, because tabs and whitespaces at the beginning are not counted to the lenght
31 | //Do the same for the end
32 | double WidthOfPlaceHolder = 0;
33 | if (text.StartsWith('\t') || text.StartsWith(' '))
34 | {
35 | text = text.Insert(0, "|");
36 | WidthOfPlaceHolder += MeasureTextSize(device, "|", textFormat).Width;
37 | }
38 | if (text.EndsWith('\t') || text.EndsWith(' '))
39 | {
40 | text = text += "|";
41 | WidthOfPlaceHolder += MeasureTextSize(device, "|", textFormat).Width;
42 | }
43 |
44 | CanvasTextLayout layout = new CanvasTextLayout(device, text, textFormat, 0, 0);
45 | return new Size(layout.DrawBounds.Width - WidthOfPlaceHolder, layout.DrawBounds.Height);
46 | }
47 |
48 | //Get the longest line in the textbox
49 | public static int GetLongestLineIndex(PooledList totalLines)
50 | {
51 | int LongestIndex = 0;
52 | int OldLenght = 0;
53 | for (int i = 0; i < totalLines.Count; i++)
54 | {
55 | var lenght = totalLines[i].Length;
56 | if (lenght > OldLenght)
57 | {
58 | LongestIndex = i;
59 | OldLenght = lenght;
60 | }
61 | }
62 | return LongestIndex;
63 | }
64 | public static int GetLongestLineLength(string text)
65 | {
66 | var splitted = text.Split("\n");
67 | int OldLenght = 0;
68 | for (int i = 0; i < splitted.Length; i++)
69 | {
70 | var lenght = splitted[i].Length;
71 | if (lenght > OldLenght)
72 | {
73 | OldLenght = lenght;
74 | }
75 | }
76 | return OldLenght;
77 | }
78 |
79 | public static bool CursorPositionsAreEqual(CursorPosition first, CursorPosition second)
80 | {
81 | return first.LineNumber == second.LineNumber && first.CharacterPosition == second.CharacterPosition;
82 | }
83 |
84 | public static string[] SplitAt(string Text, int Index)
85 | {
86 | string First = Index < Text.Length ? Text.SafeRemove(Index) : Text;
87 | string Second = Index < Text.Length ? Text.Safe_Substring(Index) : "";
88 | return new string[] { First, Second };
89 | }
90 |
91 | public static int CountCharacters(PooledList totalLines)
92 | {
93 | int Count = 0;
94 | for (int i = 0; i < totalLines.Count; i++)
95 | {
96 | Count += totalLines[i].Length + 1;
97 | }
98 | return Count - 1;
99 | }
100 | public static void ChangeCursor(CoreCursorType cursorType)
101 | {
102 | Window.Current.CoreWindow.PointerCursor = new CoreCursor(cursorType, 0);
103 | }
104 | public static bool IsKeyPressed(VirtualKey key)
105 | {
106 | return Window.Current.CoreWindow.GetKeyState(key).HasFlag(CoreVirtualKeyStates.Down);
107 | }
108 | public static async Task IsOverTextLimit(int textLength)
109 | {
110 | if (textLength > 100000000)
111 | {
112 | await new MessageDialog("Current textlimit is 100 million characters, but your file has " + textLength + " characters").ShowAsync();
113 | return true;
114 | }
115 | return false;
116 | }
117 | public static void Benchmark(Action action, string text)
118 | {
119 | Stopwatch sw = new Stopwatch();
120 | sw.Start();
121 | action.Invoke();
122 | sw.Stop();
123 |
124 | Debug.WriteLine(text + " took " + sw.ElapsedMilliseconds + "::" + sw.ElapsedTicks);
125 | }
126 |
127 | public static ApplicationTheme ConvertTheme(ElementTheme theme)
128 | {
129 | switch (theme)
130 | {
131 | case ElementTheme.Light: return ApplicationTheme.Light;
132 | case ElementTheme.Dark: return ApplicationTheme.Dark;
133 | case ElementTheme.Default:
134 | var DefaultTheme = new Windows.UI.ViewManagement.UISettings();
135 | return DefaultTheme.GetColorValue(Windows.UI.ViewManagement.UIColorType.Background).ToString() == "#FF000000"
136 | ? ApplicationTheme.Dark : ApplicationTheme.Light;
137 |
138 | default: return ApplicationTheme.Light;
139 | }
140 | }
141 |
142 | public static Point GetPointFromCoreWindowRelativeTo(PointerEventArgs args, UIElement relative)
143 | {
144 | //Convert the point relative to the Canvas_Selection to get around Control position changes in the Window
145 | return args.CurrentPoint.Position.Subtract(GetTextboxstartingPoint(relative));
146 | }
147 |
148 | public static Point GetTextboxstartingPoint(UIElement relativeTo)
149 | {
150 | return relativeTo.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0));
151 | }
152 |
153 | public static int CountLines(string text, string newLineCharacter)
154 | {
155 | return (text.Length - text.Replace(newLineCharacter, "").Length) / newLineCharacter.Length + 1;
156 | }
157 |
158 | public static Rect CreateRect(Rect rect, float marginLeft = 0, float marginTop = 0)
159 | {
160 | return new Rect(
161 | new Point(
162 | Math.Floor(rect.Left + marginLeft),//X
163 | Math.Floor(rect.Top + marginTop)), //Y
164 | new Point(
165 | Math.Ceiling(rect.Right + marginLeft), //Width
166 | Math.Ceiling(rect.Bottom + marginTop))); //Height
167 | }
168 |
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/TextControlBox/Text/UndoRedo.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using System;
3 | using System.Collections.Generic;
4 | using TextControlBox.Extensions;
5 | using TextControlBox.Helper;
6 | using TextControlBox.Models;
7 |
8 | namespace TextControlBox.Text
9 | {
10 | internal class UndoRedo
11 | {
12 | private Stack UndoStack = new Stack();
13 | private Stack RedoStack = new Stack();
14 |
15 | private bool HasRedone = false;
16 |
17 | private void RecordRedo(UndoRedoItem item)
18 | {
19 | RedoStack.Push(item);
20 | }
21 | private void RecordUndo(UndoRedoItem item)
22 | {
23 | UndoStack.Push(item);
24 | }
25 |
26 | private void AddUndoItem(TextSelection selection, int startLine, string undoText, string redoText, int undoCount, int redoCount)
27 | {
28 | UndoStack.Push(new UndoRedoItem
29 | {
30 | RedoText = redoText,
31 | UndoText = undoText,
32 | Selection = selection,
33 | StartLine = startLine,
34 | UndoCount = undoCount,
35 | RedoCount = redoCount,
36 | });
37 | }
38 |
39 | private void RecordSingleLine(Action action, PooledList totalLines, int startline)
40 | {
41 | var lineBefore = totalLines.GetLineText(startline);
42 | action.Invoke();
43 | var lineAfter = totalLines.GetLineText(startline);
44 | AddUndoItem(null, startline, lineBefore, lineAfter, 1, 1);
45 | }
46 |
47 | public void RecordUndoAction(Action action, PooledList totalLines, int startline, int undocount, int redoCount, string newLineCharacter, CursorPosition cursorposition = null)
48 | {
49 | if (undocount == redoCount && redoCount == 1)
50 | {
51 | RecordSingleLine(action, totalLines, startline);
52 | return;
53 | }
54 |
55 | var linesBefore = totalLines.GetLines(startline, undocount).GetString(newLineCharacter);
56 | action.Invoke();
57 | var linesAfter = totalLines.GetLines(startline, redoCount).GetString(newLineCharacter);
58 |
59 | AddUndoItem(cursorposition == null ? null : new TextSelection(new CursorPosition(cursorposition), null), startline, linesBefore, linesAfter, undocount, redoCount);
60 | }
61 | public void RecordUndoAction(Action action, PooledList totalLines, TextSelection selection, int numberOfAddedLines, string newLineCharacter)
62 | {
63 | var orderedSel = Selection.OrderTextSelection(selection);
64 | if (orderedSel.StartPosition.LineNumber == orderedSel.EndPosition.LineNumber && orderedSel.StartPosition.LineNumber == 1)
65 | {
66 | RecordSingleLine(action, totalLines, orderedSel.StartPosition.LineNumber);
67 | return;
68 | }
69 |
70 | int numberOfRemovedLines = orderedSel.EndPosition.LineNumber - orderedSel.StartPosition.LineNumber + 1;
71 |
72 | if (numberOfAddedLines == 0 && !Selection.WholeLinesAreSelected(selection, totalLines) ||
73 | orderedSel.StartPosition.LineNumber == orderedSel.EndPosition.LineNumber && orderedSel.Length == totalLines.GetLineLength(orderedSel.StartPosition.LineNumber))
74 | numberOfAddedLines += 1;
75 |
76 | var linesBefore = totalLines.GetLines(orderedSel.StartPosition.LineNumber, numberOfRemovedLines).GetString(newLineCharacter);
77 | action.Invoke();
78 | var linesAfter = totalLines.GetLines(orderedSel.StartPosition.LineNumber, numberOfAddedLines).GetString(newLineCharacter);
79 |
80 | AddUndoItem(
81 | selection,
82 | orderedSel.StartPosition.LineNumber,
83 | linesBefore,
84 | linesAfter,
85 | numberOfRemovedLines,
86 | numberOfAddedLines
87 | );
88 | }
89 |
90 | public TextSelection Undo(PooledList totalLines, StringManager stringManager, string newLineCharacter)
91 | {
92 | if (UndoStack.Count < 1)
93 | return null;
94 |
95 | if (HasRedone)
96 | {
97 | HasRedone = false;
98 | while (RedoStack.Count > 0)
99 | {
100 | var redoItem = RedoStack.Pop();
101 | redoItem.UndoText = redoItem.RedoText = null;
102 | }
103 | }
104 |
105 | UndoRedoItem item = UndoStack.Pop();
106 | RecordRedo(item);
107 |
108 | //Faster for singleline
109 | if (item.UndoCount == 1 && item.RedoCount == 1)
110 | {
111 | totalLines.SetLineText(item.StartLine, stringManager.CleanUpString(item.UndoText));
112 | }
113 | else
114 | {
115 | totalLines.Safe_RemoveRange(item.StartLine, item.RedoCount);
116 | if (item.UndoCount > 0)
117 | totalLines.InsertOrAddRange(ListHelper.GetLinesFromString(stringManager.CleanUpString(item.UndoText), newLineCharacter), item.StartLine);
118 | }
119 |
120 | return item.Selection;
121 | }
122 |
123 | public TextSelection Redo(PooledList totalLines, StringManager stringmanager, string newLineCharacter)
124 | {
125 | if (RedoStack.Count < 1)
126 | return null;
127 |
128 | UndoRedoItem item = RedoStack.Pop();
129 | RecordUndo(item);
130 | HasRedone = true;
131 |
132 | //Faster for singleline
133 | if (item.UndoCount == 1 && item.RedoCount == 1)
134 | {
135 | totalLines.SetLineText(item.StartLine, stringmanager.CleanUpString(item.RedoText));
136 | }
137 | else
138 | {
139 | totalLines.Safe_RemoveRange(item.StartLine, item.UndoCount);
140 | if (item.RedoCount > 0)
141 | totalLines.InsertOrAddRange(ListHelper.GetLinesFromString(stringmanager.CleanUpString(item.RedoText), newLineCharacter), item.StartLine);
142 | }
143 | return null;
144 | }
145 |
146 | ///
147 | /// Clears all the items in the undo and redo stack
148 | ///
149 | public void ClearAll()
150 | {
151 | UndoStack.Clear();
152 | RedoStack.Clear();
153 | UndoStack.TrimExcess();
154 | RedoStack.TrimExcess();
155 |
156 | GC.Collect(GC.GetGeneration(UndoStack), GCCollectionMode.Optimized);
157 | GC.Collect(GC.GetGeneration(RedoStack), GCCollectionMode.Optimized);
158 | }
159 |
160 | public void NullAll()
161 | {
162 | UndoStack = null;
163 | RedoStack = null;
164 | }
165 |
166 | ///
167 | /// Gets if the undo stack contains actions
168 | ///
169 | public bool CanUndo { get => UndoStack.Count > 0; }
170 |
171 | ///
172 | /// Gets if the redo stack contains actions
173 | ///
174 | public bool CanRedo { get => RedoStack.Count > 0; }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
364 | /images
365 | /TextControlBox/TextControlBox.nuspec
366 | /TextControlBox/icon1.png
367 | /TextControlBox/.nuget
368 | /Cloned
369 |
--------------------------------------------------------------------------------
/TextControlBox/Text/Cursor.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using System;
3 | using TextControlBox.Extensions;
4 | using TextControlBox.Helper;
5 |
6 | namespace TextControlBox.Text
7 | {
8 | internal class Cursor
9 | {
10 | private static int CheckIndex(string str, int index) => Math.Clamp(index, 0, str.Length - 1);
11 | public static int CursorPositionToIndex(PooledList totalLines, CursorPosition cursorPosition)
12 | {
13 | int cursorIndex = cursorPosition.CharacterPosition;
14 | int lineNumber = cursorPosition.LineNumber < totalLines.Count ? cursorIndex : totalLines.Count - 1;
15 | for (int i = 0; i < lineNumber; i++)
16 | {
17 | cursorIndex += totalLines.GetLineLength(i) + 1;
18 | }
19 | return cursorIndex;
20 | }
21 | public static bool Equals(CursorPosition curPos1, CursorPosition curPos2)
22 | {
23 | if (curPos1 == null || curPos2 == null)
24 | return false;
25 |
26 | if (curPos1.LineNumber == curPos2.LineNumber)
27 | return curPos1.CharacterPosition == curPos2.CharacterPosition;
28 | return false;
29 | }
30 |
31 | //Calculate the number of characters from the cursorposition to the next character or digit to the left and to the right
32 | public static int CalculateStepsToMoveLeft2(string currentLine, int cursorCharPosition)
33 | {
34 | if (currentLine.Length == 0)
35 | return 0;
36 |
37 | int stepsToMove = 0;
38 | for (int i = cursorCharPosition - 1; i >= 0; i--)
39 | {
40 | char currentCharacter = currentLine[CheckIndex(currentLine, i)];
41 | if (char.IsLetterOrDigit(currentCharacter) || currentCharacter == '_')
42 | stepsToMove++;
43 | else if (i == cursorCharPosition - 1 && char.IsWhiteSpace(currentCharacter))
44 | return 0;
45 | else
46 | break;
47 | }
48 | return stepsToMove;
49 | }
50 | public static int CalculateStepsToMoveRight2(string currentLine, int cursorCharPosition)
51 | {
52 | if (currentLine.Length == 0)
53 | return 0;
54 |
55 | int stepsToMove = 0;
56 | for (int i = cursorCharPosition; i < currentLine.Length; i++)
57 | {
58 | char currentCharacter = currentLine[CheckIndex(currentLine, i)];
59 | if (char.IsLetterOrDigit(currentCharacter) || currentCharacter == '_')
60 | stepsToMove++;
61 | else if (i == cursorCharPosition && char.IsWhiteSpace(currentCharacter))
62 | return 0;
63 | else
64 | break;
65 | }
66 | return stepsToMove;
67 | }
68 |
69 | //Calculates how many characters the cursor needs to move if control is pressed
70 | //Returns 1 when control is not pressed
71 | public static int CalculateStepsToMoveLeft(string currentLine, int cursorCharPosition)
72 | {
73 | if (!Utils.IsKeyPressed(Windows.System.VirtualKey.Control))
74 | return 1;
75 |
76 | int stepsToMove = 0;
77 | for (int i = cursorCharPosition - 1; i >= 0; i--)
78 | {
79 | char CurrentCharacter = currentLine[CheckIndex(currentLine, i)];
80 | if (char.IsLetterOrDigit(CurrentCharacter) || CurrentCharacter == '_')
81 | stepsToMove++;
82 | else if (i == cursorCharPosition - 1 && char.IsWhiteSpace(CurrentCharacter))
83 | stepsToMove++;
84 | else
85 | break;
86 | }
87 | //If it ignores the ControlKey return the real value of stepsToMove otherwise
88 | //return 1 if stepsToMove is 0
89 | return stepsToMove == 0 ? 1 : stepsToMove;
90 | }
91 | public static int CalculateStepsToMoveRight(string currentLine, int cursorCharPosition)
92 | {
93 | if (!Utils.IsKeyPressed(Windows.System.VirtualKey.Control))
94 | return 1;
95 |
96 | int stepsToMove = 0;
97 | for (int i = cursorCharPosition; i < currentLine.Length; i++)
98 | {
99 | char CurrentCharacter = currentLine[CheckIndex(currentLine, i)];
100 | if (char.IsLetterOrDigit(CurrentCharacter) || CurrentCharacter == '_')
101 | stepsToMove++;
102 | else if (i == cursorCharPosition && char.IsWhiteSpace(CurrentCharacter))
103 | stepsToMove++;
104 | else
105 | break;
106 | }
107 | //If it ignores the ControlKey return the real value of stepsToMove otherwise
108 | //return 1 if stepsToMove is 0
109 | return stepsToMove == 0 ? 1 : stepsToMove;
110 | }
111 |
112 | //Move cursor:
113 | public static void MoveLeft(CursorPosition currentCursorPosition, PooledList totalLines, string currentLine)
114 | {
115 | if (currentCursorPosition.LineNumber < 0)
116 | return;
117 |
118 | int currentLineLength = totalLines.GetLineLength(currentCursorPosition.LineNumber);
119 | if (currentCursorPosition.CharacterPosition == 0 && currentCursorPosition.LineNumber > 0)
120 | {
121 | currentCursorPosition.CharacterPosition = totalLines.GetLineLength(currentCursorPosition.LineNumber - 1);
122 | currentCursorPosition.LineNumber -= 1;
123 | }
124 | else if (currentCursorPosition.CharacterPosition > currentLineLength)
125 | currentCursorPosition.CharacterPosition = currentLineLength - 1;
126 | else if (currentCursorPosition.CharacterPosition > 0)
127 | currentCursorPosition.CharacterPosition -= CalculateStepsToMoveLeft(currentLine, currentCursorPosition.CharacterPosition);
128 | }
129 | public static void MoveRight(CursorPosition currentCursorPosition, PooledList totalLines, string currentLine)
130 | {
131 | int lineLength = totalLines.GetLineLength(currentCursorPosition.LineNumber);
132 |
133 | if (currentCursorPosition.LineNumber > totalLines.Count - 1)
134 | return;
135 |
136 | if (currentCursorPosition.CharacterPosition == lineLength && currentCursorPosition.LineNumber < totalLines.Count - 1)
137 | {
138 | currentCursorPosition.CharacterPosition = 0;
139 | currentCursorPosition.LineNumber += 1;
140 | }
141 | else if (currentCursorPosition.CharacterPosition < lineLength)
142 | currentCursorPosition.CharacterPosition += CalculateStepsToMoveRight(currentLine, currentCursorPosition.CharacterPosition);
143 |
144 | if (currentCursorPosition.CharacterPosition > lineLength)
145 | currentCursorPosition.CharacterPosition = lineLength;
146 | }
147 | public static CursorPosition MoveDown(CursorPosition currentCursorPosition, int totalLinesLength)
148 | {
149 | CursorPosition returnValue = new CursorPosition(currentCursorPosition);
150 | if (currentCursorPosition.LineNumber < totalLinesLength - 1)
151 | returnValue = CursorPosition.ChangeLineNumber(currentCursorPosition, currentCursorPosition.LineNumber + 1);
152 | return returnValue;
153 | }
154 | public static CursorPosition MoveUp(CursorPosition currentCursorPosition)
155 | {
156 | CursorPosition returnValue = new CursorPosition(currentCursorPosition);
157 | if (currentCursorPosition.LineNumber > 0)
158 | returnValue = CursorPosition.ChangeLineNumber(returnValue, currentCursorPosition.LineNumber - 1);
159 | return returnValue;
160 | }
161 | public static void MoveToLineEnd(CursorPosition cursorPosition, string currentLine)
162 | {
163 | cursorPosition.CharacterPosition = currentLine.Length;
164 | }
165 | public static void MoveToLineStart(CursorPosition cursorPosition)
166 | {
167 | cursorPosition.CharacterPosition = 0;
168 | }
169 | }
170 | }
--------------------------------------------------------------------------------
/TextControlBox-TestApp/TextControlBox-TestApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | x86
7 | {386FA1B4-59CF-48B8-A3F1-F7644850D47D}
8 | AppContainerExe
9 | Properties
10 | TextControlBox_TestApp
11 | TextControlBox-TestApp
12 | de-DE
13 | UAP
14 | 10.0.22621.0
15 | 10.0.17763.0
16 | 14
17 | 512
18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
19 | true
20 | false
21 | False
22 | True
23 | True
24 | Always
25 | x64
26 | 0
27 |
28 |
29 | true
30 | bin\x86\Debug\
31 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
32 | ;2008
33 | full
34 | x86
35 | false
36 | prompt
37 | true
38 |
39 |
40 | bin\x86\Release\
41 | TRACE;NETFX_CORE;WINDOWS_UWP
42 | true
43 | ;2008
44 | pdbonly
45 | x86
46 | false
47 | prompt
48 | true
49 | true
50 |
51 |
52 | true
53 | bin\ARM\Debug\
54 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
55 | ;2008
56 | full
57 | ARM
58 | false
59 | prompt
60 | true
61 |
62 |
63 | bin\ARM\Release\
64 | TRACE;NETFX_CORE;WINDOWS_UWP
65 | true
66 | ;2008
67 | pdbonly
68 | ARM
69 | false
70 | prompt
71 | true
72 | true
73 |
74 |
75 | true
76 | bin\ARM64\Debug\
77 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
78 | ;2008
79 | full
80 | ARM64
81 | false
82 | prompt
83 | true
84 | true
85 |
86 |
87 | bin\ARM64\Release\
88 | TRACE;NETFX_CORE;WINDOWS_UWP
89 | true
90 | ;2008
91 | pdbonly
92 | ARM64
93 | false
94 | prompt
95 | true
96 | true
97 |
98 |
99 | true
100 | bin\x64\Debug\
101 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
102 | ;2008
103 | full
104 | x64
105 | false
106 | prompt
107 | true
108 | true
109 | bin\x64\Debug\TextControlBox-TestApp.XML
110 |
111 |
112 | bin\x64\Release\
113 | TRACE;NETFX_CORE;WINDOWS_UWP
114 | true
115 | ;2008
116 | pdbonly
117 | x64
118 | false
119 | prompt
120 | true
121 | true
122 |
123 |
124 | PackageReference
125 |
126 |
127 |
128 | App.xaml
129 |
130 |
131 | MainPage.xaml
132 |
133 |
134 |
135 |
136 |
137 | Designer
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | MSBuild:Compile
153 | Designer
154 |
155 |
156 | MSBuild:Compile
157 | Designer
158 |
159 |
160 |
161 |
162 | 6.2.14
163 |
164 |
165 | 2.8.1
166 |
167 |
168 | 6.0.0
169 |
170 |
171 |
172 |
173 | {2b1b83a3-b887-4ed3-a5d5-83e2c5abd39f}
174 | TextControlBox
175 |
176 |
177 |
178 |
179 | 14.0
180 |
181 |
182 |
189 |
--------------------------------------------------------------------------------
/TextControlBox/Renderer/SelectionRenderer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graphics.Canvas.Text;
2 | using Microsoft.Graphics.Canvas.UI.Xaml;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using TextControlBox.Helper;
7 | using TextControlBox.Text;
8 | using Windows.Foundation;
9 | using Windows.UI;
10 |
11 | namespace TextControlBox.Renderer
12 | {
13 | internal class SelectionRenderer
14 | {
15 | public bool HasSelection = false;
16 | public bool IsSelecting = false;
17 | public bool IsSelectingOverLinenumbers = false;
18 | public CursorPosition SelectionStartPosition = null;
19 | public CursorPosition SelectionEndPosition = null;
20 | public int SelectionLength = 0;
21 | public int SelectionStart = 0;
22 |
23 |
24 | //Draw the actual selection and return the cursorposition. Return -1 if no selection was drawn
25 | public TextSelection DrawSelection(
26 | CanvasTextLayout textLayout,
27 | IEnumerable renderedLines,
28 | CanvasDrawEventArgs args,
29 | float marginLeft,
30 | float marginTop,
31 | int unrenderedLinesToRenderStart,
32 | int numberOfRenderedLines,
33 | float fontSize,
34 | Color selectionColor
35 | )
36 | {
37 | if (HasSelection && SelectionEndPosition != null && SelectionStartPosition != null)
38 | {
39 | int selStartIndex = 0;
40 | int selEndIndex = 0;
41 | int characterPosStart = SelectionStartPosition.CharacterPosition;
42 | int characterPosEnd = SelectionEndPosition.CharacterPosition;
43 |
44 | //Render the selection on position 0 if the user scrolled the start away
45 | if (SelectionEndPosition.LineNumber < SelectionStartPosition.LineNumber)
46 | {
47 | if (SelectionEndPosition.LineNumber < unrenderedLinesToRenderStart)
48 | characterPosEnd = 0;
49 | if (SelectionStartPosition.LineNumber < unrenderedLinesToRenderStart + 1)
50 | characterPosStart = 0;
51 | }
52 | else if (SelectionEndPosition.LineNumber == SelectionStartPosition.LineNumber)
53 | {
54 | if (SelectionStartPosition.LineNumber < unrenderedLinesToRenderStart)
55 | characterPosStart = 0;
56 | if (SelectionEndPosition.LineNumber < unrenderedLinesToRenderStart)
57 | characterPosEnd = 0;
58 | }
59 | else
60 | {
61 | if (SelectionStartPosition.LineNumber < unrenderedLinesToRenderStart)
62 | characterPosStart = 0;
63 | if (SelectionEndPosition.LineNumber < unrenderedLinesToRenderStart + 1)
64 | characterPosEnd = 0;
65 | }
66 |
67 | if (SelectionStartPosition.LineNumber == SelectionEndPosition.LineNumber)
68 | {
69 | int lenghtToLine = 0;
70 | for (int i = 0; i < SelectionStartPosition.LineNumber - unrenderedLinesToRenderStart; i++)
71 | {
72 | if (i < numberOfRenderedLines)
73 | {
74 | lenghtToLine += renderedLines.ElementAt(i).Length + 1;
75 | }
76 | }
77 |
78 | selStartIndex = characterPosStart + lenghtToLine;
79 | selEndIndex = characterPosEnd + lenghtToLine;
80 | }
81 | else
82 | {
83 | for (int i = 0; i < SelectionStartPosition.LineNumber - unrenderedLinesToRenderStart; i++)
84 | {
85 | if (i >= numberOfRenderedLines) //Out of range of the List (do nothing)
86 | break;
87 | selStartIndex += renderedLines.ElementAt(i).Length + 1;
88 | }
89 | selStartIndex += characterPosStart;
90 |
91 | for (int i = 0; i < SelectionEndPosition.LineNumber - unrenderedLinesToRenderStart; i++)
92 | {
93 | if (i >= numberOfRenderedLines) //Out of range of the List (do nothing)
94 | break;
95 |
96 | selEndIndex += renderedLines.ElementAt(i).Length + 1;
97 | }
98 | selEndIndex += characterPosEnd;
99 | }
100 |
101 | SelectionStart = Math.Min(selStartIndex, selEndIndex);
102 |
103 | if (SelectionStart < 0)
104 | SelectionStart = 0;
105 | if (SelectionLength < 0)
106 | SelectionLength = 0;
107 |
108 | if (selEndIndex > selStartIndex)
109 | SelectionLength = selEndIndex - selStartIndex;
110 | else
111 | SelectionLength = selStartIndex - selEndIndex;
112 |
113 | CanvasTextLayoutRegion[] descriptions = textLayout.GetCharacterRegions(SelectionStart, SelectionLength);
114 | for (int i = 0; i < descriptions.Length; i++)
115 | {
116 | //Change the width if selection in an emty line or starts at a line end
117 | if (descriptions[i].LayoutBounds.Width == 0 && descriptions.Length > 1)
118 | {
119 | var bounds = descriptions[i].LayoutBounds;
120 | descriptions[i].LayoutBounds = new Rect { Width = fontSize / 4, Height = bounds.Height, X = bounds.X, Y = bounds.Y };
121 | }
122 |
123 | args.DrawingSession.FillRectangle(Utils.CreateRect(descriptions[i].LayoutBounds, marginLeft, marginTop), selectionColor);
124 | }
125 | return new TextSelection(SelectionStart, SelectionLength, new CursorPosition(SelectionStartPosition), new CursorPosition(SelectionEndPosition));
126 | }
127 | return null;
128 | }
129 |
130 | //returns whether the pointer is over a selection
131 | public bool PointerIsOverSelection(Point pointerPosition, TextSelection selection, CanvasTextLayout textLayout)
132 | {
133 | if (textLayout == null || selection == null)
134 | return false;
135 |
136 | CanvasTextLayoutRegion[] regions = textLayout.GetCharacterRegions(selection.Index, selection.Length);
137 | for (int i = 0; i < regions.Length; i++)
138 | {
139 | if (regions[i].LayoutBounds.Contains(pointerPosition))
140 | return true;
141 | }
142 | return false;
143 | }
144 |
145 | public bool CursorIsInSelection(CursorPosition cursorPosition, TextSelection textSelection)
146 | {
147 | if (textSelection == null)
148 | return false;
149 |
150 | textSelection = Selection.OrderTextSelection(textSelection);
151 |
152 | //Cursorposition is smaller than the start of selection
153 | if (textSelection.StartPosition.LineNumber > cursorPosition.LineNumber)
154 | return false;
155 |
156 | //Selectionend is smaller than Cursorposition -> not in selection
157 | if (textSelection.EndPosition.LineNumber < cursorPosition.LineNumber)
158 | return false;
159 |
160 | //Selection-start line equals Cursor line:
161 | if (cursorPosition.LineNumber == textSelection.StartPosition.LineNumber)
162 | return cursorPosition.CharacterPosition > textSelection.StartPosition.CharacterPosition;
163 |
164 | //Selection-end line equals Cursor line
165 | else if (cursorPosition.LineNumber == textSelection.EndPosition.LineNumber)
166 | return cursorPosition.CharacterPosition < textSelection.EndPosition.CharacterPosition;
167 | return true;
168 | }
169 |
170 | //Clear the selection
171 | public void ClearSelection()
172 | {
173 | HasSelection = false;
174 | IsSelecting = false;
175 | SelectionEndPosition = null;
176 | SelectionStartPosition = null;
177 | }
178 |
179 | public void SetSelection(TextSelection selection)
180 | {
181 | if (selection == null)
182 | return;
183 |
184 | SetSelection(selection.StartPosition, selection.EndPosition);
185 | }
186 | public void SetSelection(CursorPosition startPosition, CursorPosition endPosition)
187 | {
188 | IsSelecting = true;
189 | SelectionStartPosition = startPosition;
190 | SelectionEndPosition = endPosition;
191 | IsSelecting = false;
192 | HasSelection = true;
193 | }
194 | public void SetSelectionStart(CursorPosition startPosition)
195 | {
196 | IsSelecting = true;
197 | SelectionStartPosition = startPosition;
198 | IsSelecting = false;
199 | HasSelection = true;
200 | }
201 | public void SetSelectionEnd(CursorPosition endPosition)
202 | {
203 | IsSelecting = true;
204 | SelectionEndPosition = endPosition;
205 | IsSelecting = false;
206 | HasSelection = true;
207 | }
208 | }
209 | }
--------------------------------------------------------------------------------
/TextControlBox/Helper/SearchHelper.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text.RegularExpressions;
6 | using TextControlBox.Extensions;
7 | using TextControlBox.Text;
8 |
9 | namespace TextControlBox.Helper
10 | {
11 | internal class SearchHelper
12 | {
13 | public int CurrentSearchLine = 0;
14 | public int CurrentSearchArrayIndex = 0;
15 | public int OldSearchArrayIndex = 0;
16 | public int[] MatchingSearchLines = null;
17 | public bool IsSearchOpen = false;
18 | public SearchParameter SearchParameter = null;
19 | public MatchCollection CurrentLineMatches = null;
20 | private int RegexIndexInLine = 0;
21 |
22 | private int CheckIndexValue(int i)
23 | {
24 | return i >= MatchingSearchLines.Length ? MatchingSearchLines.Length - 1 : i < 0 ? 0 : i;
25 | }
26 |
27 | public InternSearchResult FindNext(PooledList totalLines, CursorPosition cursorPosition)
28 | {
29 | if (IsSearchOpen && MatchingSearchLines != null && MatchingSearchLines.Length > 0)
30 | {
31 | CurrentSearchLine = cursorPosition.LineNumber;
32 | int indexInList = Array.IndexOf(MatchingSearchLines, CurrentSearchLine);
33 | //When in a line without a match search for the next line with a match
34 | if (indexInList == -1)
35 | {
36 | for (int i = 0; i < MatchingSearchLines.Length; i++)
37 | {
38 | if (MatchingSearchLines[i] > CurrentSearchLine)
39 | {
40 | CurrentSearchArrayIndex = i - 1 < 0 ? 0 : i - 1;
41 | CurrentSearchLine = MatchingSearchLines[CurrentSearchArrayIndex];
42 | break;
43 | }
44 | }
45 | }
46 | else
47 | {
48 | CurrentSearchArrayIndex = indexInList;
49 | CurrentSearchLine = MatchingSearchLines[CurrentSearchArrayIndex];
50 | if (OldSearchArrayIndex != CurrentSearchArrayIndex)
51 | {
52 | OldSearchArrayIndex = CurrentSearchArrayIndex;
53 | CurrentLineMatches = null;
54 | }
55 | }
56 |
57 | //Search went through all matches in the current line:
58 | if (CurrentLineMatches == null || RegexIndexInLine >= CurrentLineMatches.Count)
59 | {
60 | RegexIndexInLine = 0;
61 | //back at start
62 | if (CurrentSearchLine < MatchingSearchLines[MatchingSearchLines.Length - 1])
63 | {
64 | CurrentSearchArrayIndex++;
65 | CurrentSearchLine = cursorPosition.LineNumber = MatchingSearchLines[CurrentSearchArrayIndex];
66 | CurrentLineMatches = Regex.Matches(totalLines[CurrentSearchLine], SearchParameter.SearchExpression);
67 | }
68 | else
69 | return new InternSearchResult(SearchResult.ReachedEnd, null);
70 | }
71 |
72 | RegexIndexInLine = Math.Clamp(RegexIndexInLine, 0, CurrentLineMatches.Count - 1);
73 |
74 | RegexIndexInLine++;
75 | if (RegexIndexInLine > CurrentLineMatches.Count || RegexIndexInLine < 0)
76 | return new InternSearchResult(SearchResult.NotFound, null);
77 |
78 | return new InternSearchResult(SearchResult.Found, new TextSelection(
79 | new CursorPosition(CurrentLineMatches[RegexIndexInLine - 1].Index, cursorPosition.LineNumber),
80 | new CursorPosition(CurrentLineMatches[RegexIndexInLine - 1].Index + CurrentLineMatches[RegexIndexInLine - 1].Length, cursorPosition.LineNumber)));
81 | }
82 | return new InternSearchResult(SearchResult.NotFound, null);
83 | }
84 | public InternSearchResult FindPrevious(PooledList totalLines, CursorPosition cursorPosition)
85 | {
86 | if (IsSearchOpen && MatchingSearchLines != null)
87 | {
88 | //Find the next linnenumber with a match if the line is not in the array of matching lines
89 | CurrentSearchLine = cursorPosition.LineNumber;
90 | int indexInList = Array.IndexOf(MatchingSearchLines, CurrentSearchLine);
91 | if (indexInList == -1)
92 | {
93 | //Find the first line with matches which is smaller than the current line:
94 | var lines = MatchingSearchLines.Where(x => x < CurrentSearchLine);
95 | if (lines.Count() < 1)
96 | {
97 | return new InternSearchResult(SearchResult.ReachedBegin, null);
98 | }
99 |
100 | CurrentSearchArrayIndex = Array.IndexOf(MatchingSearchLines, lines.Last());
101 | CurrentSearchLine = MatchingSearchLines[CurrentSearchArrayIndex];
102 | CurrentLineMatches = null;
103 | }
104 | else
105 | {
106 | CurrentSearchArrayIndex = indexInList;
107 | CurrentSearchLine = MatchingSearchLines[CurrentSearchArrayIndex];
108 |
109 | if (OldSearchArrayIndex != CurrentSearchArrayIndex)
110 | {
111 | OldSearchArrayIndex = CurrentSearchArrayIndex;
112 | CurrentLineMatches = null;
113 | }
114 | }
115 |
116 | //Search went through all matches in the current line:
117 | if (CurrentLineMatches == null || RegexIndexInLine < 0)
118 | {
119 | //back at start
120 | if (CurrentSearchLine == MatchingSearchLines[0])
121 | {
122 | return new InternSearchResult(SearchResult.ReachedBegin, null);
123 | }
124 | if (CurrentSearchLine < MatchingSearchLines[MatchingSearchLines.Length - 1])
125 | {
126 | CurrentSearchLine = cursorPosition.LineNumber = MatchingSearchLines[CheckIndexValue(CurrentSearchArrayIndex - 1)];
127 | CurrentLineMatches = Regex.Matches(totalLines[CurrentSearchLine], SearchParameter.SearchExpression);
128 | RegexIndexInLine = CurrentLineMatches.Count - 1;
129 | CurrentSearchArrayIndex--;
130 | }
131 | }
132 |
133 | if (CurrentLineMatches == null)
134 | return new InternSearchResult(SearchResult.NotFound, null);
135 |
136 | RegexIndexInLine = Math.Clamp(RegexIndexInLine, 0, CurrentLineMatches.Count - 1);
137 |
138 | //RegexIndexInLine--;
139 | if (RegexIndexInLine >= CurrentLineMatches.Count || RegexIndexInLine < 0)
140 | return new InternSearchResult(SearchResult.NotFound, null);
141 |
142 | return new InternSearchResult(SearchResult.Found, new TextSelection(
143 | new CursorPosition(CurrentLineMatches[RegexIndexInLine].Index, cursorPosition.LineNumber),
144 | new CursorPosition(CurrentLineMatches[RegexIndexInLine].Index + CurrentLineMatches[RegexIndexInLine--].Length, cursorPosition.LineNumber)));
145 | }
146 | return new InternSearchResult(SearchResult.NotFound, null);
147 | }
148 |
149 | public void UpdateSearchLines(PooledList totalLines)
150 | {
151 | MatchingSearchLines = FindIndexes(totalLines);
152 | }
153 |
154 | public SearchResult BeginSearch(PooledList totalLines, string word, bool matchCase, bool wholeWord)
155 | {
156 | SearchParameter = new SearchParameter(word, wholeWord, matchCase);
157 | UpdateSearchLines(totalLines);
158 |
159 | if (word == "" || word == null)
160 | return SearchResult.InvalidInput;
161 |
162 | //A result was found
163 | if (MatchingSearchLines.Length > 0)
164 | {
165 | IsSearchOpen = true;
166 | }
167 |
168 | return MatchingSearchLines.Length > 0 ? SearchResult.Found : SearchResult.NotFound;
169 | }
170 | public void EndSearch()
171 | {
172 | IsSearchOpen = false;
173 | MatchingSearchLines = null;
174 | }
175 |
176 | private int[] FindIndexes(PooledList totalLines)
177 | {
178 | List results = new List();
179 | for (int i = 0; i < totalLines.Count; i++)
180 | {
181 | if (totalLines[i].Contains(SearchParameter))
182 | results.Add(i);
183 | };
184 | return results.ToArray();
185 | }
186 | }
187 | internal class SearchParameter
188 | {
189 | public SearchParameter(string word, bool wholeWord = false, bool matchCase = false)
190 | {
191 | this.Word = word;
192 | this.WholeWord = wholeWord;
193 | this.MatchCase = matchCase;
194 |
195 | if (wholeWord)
196 | SearchExpression += @"\b" + (matchCase ? "" : "(?i)") + Regex.Escape(word) + @"\b";
197 | else
198 | SearchExpression += (matchCase ? "" : "(?i)") + Regex.Escape(word);
199 | }
200 |
201 | public bool WholeWord { get; set; }
202 | public bool MatchCase { get; set; }
203 | public string Word { get; set; }
204 | public string SearchExpression { get; set; } = "";
205 | }
206 |
207 | internal struct InternSearchResult
208 | {
209 | public InternSearchResult(SearchResult result, TextSelection selection)
210 | {
211 | this.Result = result;
212 | this.Selection = selection;
213 | }
214 |
215 | public TextSelection Selection;
216 | public SearchResult Result;
217 | }
218 | }
--------------------------------------------------------------------------------
/TextControlBox/TextControlBox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {2B1B83A3-B887-4ED3-A5D5-83E2C5ABD39F}
8 | Library
9 | Properties
10 | TextControlBox
11 | TextControlBox
12 | de-DE
13 | UAP
14 | 10.0.22621.0
15 | 10.0.17763.0
16 | 14
17 | 512
18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
19 |
20 |
21 | AnyCPU
22 | true
23 | full
24 | false
25 | bin\Debug\
26 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
27 | prompt
28 | 4
29 | bin\Debug\TextControlBox.XML
30 |
31 |
32 | AnyCPU
33 | pdbonly
34 | true
35 | bin\Release\
36 | TRACE;NETFX_CORE;WINDOWS_UWP
37 | prompt
38 | 4
39 |
40 |
41 | x86
42 | true
43 | bin\x86\Debug\
44 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
45 | ;2008
46 | full
47 | false
48 | prompt
49 |
50 |
51 |
52 |
53 | x86
54 | bin\x86\Release\
55 | TRACE;NETFX_CORE;WINDOWS_UWP
56 | true
57 | ;2008
58 | pdbonly
59 | false
60 | prompt
61 |
62 |
63 | ARM
64 | true
65 | bin\ARM\Debug\
66 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
67 | ;2008
68 | full
69 | false
70 | prompt
71 |
72 |
73 | ARM
74 | bin\ARM\Release\
75 | TRACE;NETFX_CORE;WINDOWS_UWP
76 | true
77 | ;2008
78 | pdbonly
79 | false
80 | prompt
81 |
82 |
83 | ARM64
84 | true
85 | bin\ARM64\Debug\
86 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
87 | ;2008
88 | full
89 | false
90 | prompt
91 |
92 |
93 | ARM64
94 | bin\ARM64\Release\
95 | TRACE;NETFX_CORE;WINDOWS_UWP
96 | true
97 | ;2008
98 | pdbonly
99 | false
100 | prompt
101 |
102 |
103 | x64
104 | true
105 | bin\x64\Debug\
106 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
107 | ;2008
108 | full
109 | false
110 | prompt
111 | false
112 | bin\Release\TextControlBox.XML
113 |
114 |
115 | x64
116 | bin\x64\Release\
117 | TRACE;NETFX_CORE;WINDOWS_UWP
118 | true
119 | ;2008
120 | pdbonly
121 | false
122 | prompt
123 |
124 |
125 | PackageReference
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | TextControlBox.xaml
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | 1.0.82
180 |
181 |
182 | 6.2.13
183 |
184 |
185 | 13.0.1
186 |
187 |
188 | 1.26.0
189 |
190 |
191 |
192 |
193 | MSBuild:Compile
194 | Designer
195 |
196 |
197 |
198 | 14.0
199 |
200 |
201 |
202 |
203 |
204 |
211 |
--------------------------------------------------------------------------------
/TextControlBox/Text/Selection.cs:
--------------------------------------------------------------------------------
1 | using Collections.Pooled;
2 | using System;
3 | using System.Linq;
4 | using System.Text;
5 | using TextControlBox.Extensions;
6 | using TextControlBox.Helper;
7 | using TextControlBox.Renderer;
8 |
9 | namespace TextControlBox.Text
10 | {
11 | internal class Selection
12 | {
13 | public static bool Equals(TextSelection sel1, TextSelection sel2)
14 | {
15 | if (sel1 == null || sel2 == null)
16 | return false;
17 |
18 | return Cursor.Equals(sel1.StartPosition, sel2.StartPosition) &&
19 | Cursor.Equals(sel1.EndPosition, sel2.EndPosition);
20 | }
21 |
22 | //Order the selection that StartPosition is always smaller than EndPosition
23 | public static TextSelection OrderTextSelection(TextSelection selection)
24 | {
25 | if (selection == null)
26 | return selection;
27 |
28 | int startLine = Math.Min(selection.StartPosition.LineNumber, selection.EndPosition.LineNumber);
29 | int endLine = Math.Max(selection.StartPosition.LineNumber, selection.EndPosition.LineNumber);
30 | int startPosition;
31 | int endPosition;
32 | if (startLine == endLine)
33 | {
34 | startPosition = Math.Min(selection.StartPosition.CharacterPosition, selection.EndPosition.CharacterPosition);
35 | endPosition = Math.Max(selection.StartPosition.CharacterPosition, selection.EndPosition.CharacterPosition);
36 | }
37 | else
38 | {
39 | if (selection.StartPosition.LineNumber < selection.EndPosition.LineNumber)
40 | {
41 | endPosition = selection.EndPosition.CharacterPosition;
42 | startPosition = selection.StartPosition.CharacterPosition;
43 | }
44 | else
45 | {
46 | endPosition = selection.StartPosition.CharacterPosition;
47 | startPosition = selection.EndPosition.CharacterPosition;
48 | }
49 | }
50 |
51 | return new TextSelection(selection.Index, selection.Length, new CursorPosition(startPosition, startLine), new CursorPosition(endPosition, endLine));
52 | }
53 |
54 | public static bool WholeTextSelected(TextSelection selection, PooledList totalLines)
55 | {
56 | if (selection == null)
57 | return false;
58 |
59 | var sel = OrderTextSelection(selection);
60 | return Utils.CursorPositionsAreEqual(sel.StartPosition, new CursorPosition(0, 0)) &&
61 | Utils.CursorPositionsAreEqual(sel.EndPosition, new CursorPosition(totalLines.GetLineLength(-1), totalLines.Count - 1));
62 | }
63 |
64 | //returns whether the selection starts at character zero and ends
65 | public static bool WholeLinesAreSelected(TextSelection selection, PooledList totalLines)
66 | {
67 | if (selection == null)
68 | return false;
69 |
70 | var sel = OrderTextSelection(selection);
71 | return Utils.CursorPositionsAreEqual(sel.StartPosition, new CursorPosition(0, sel.StartPosition.LineNumber)) &&
72 | Utils.CursorPositionsAreEqual(sel.EndPosition, new CursorPosition(totalLines.GetLineText(sel.EndPosition.LineNumber).Length, sel.EndPosition.LineNumber));
73 | }
74 |
75 | public static CursorPosition GetMax(CursorPosition pos1, CursorPosition pos2)
76 | {
77 | if (pos1.LineNumber == pos2.LineNumber)
78 | return pos1.CharacterPosition > pos2.CharacterPosition ? pos1 : pos2;
79 | return pos1.LineNumber > pos2.LineNumber ? pos1 : pos2;
80 | }
81 | public static CursorPosition GetMin(CursorPosition pos1, CursorPosition pos2)
82 | {
83 | if (pos1.LineNumber == pos2.LineNumber)
84 | return pos1.CharacterPosition > pos2.CharacterPosition ? pos2 : pos1;
85 | return pos1.LineNumber > pos2.LineNumber ? pos2 : pos1;
86 | }
87 | public static CursorPosition GetMin(TextSelection selection)
88 | {
89 | return GetMin(selection.StartPosition, selection.EndPosition);
90 | }
91 | public static CursorPosition GetMax(TextSelection selection)
92 | {
93 | return GetMax(selection.StartPosition, selection.EndPosition);
94 | }
95 |
96 | public static CursorPosition InsertText(TextSelection selection, CursorPosition cursorPosition, PooledList totalLines, string text, string newLineCharacter)
97 | {
98 | if (selection != null)
99 | return Replace(selection, totalLines, text, newLineCharacter);
100 |
101 | string curLine = totalLines.GetLineText(cursorPosition.LineNumber);
102 |
103 | string[] lines = text.Split(newLineCharacter);
104 |
105 | //Singleline
106 | if (lines.Length == 1 && text != string.Empty)
107 | {
108 | text = text.Replace("\r", string.Empty).Replace("\n", string.Empty);
109 | totalLines.SetLineText(-1, totalLines.GetLineText(-1).AddText(text, cursorPosition.CharacterPosition));
110 | cursorPosition.AddToCharacterPos(text.Length);
111 | return cursorPosition;
112 | }
113 |
114 | //Multiline:
115 | int curPos = cursorPosition.CharacterPosition;
116 | if (curPos > curLine.Length)
117 | curPos = curLine.Length;
118 |
119 | //GEt the text in front of the cursor
120 | string textInFrontOfCursor = curLine.Substring(0, curPos < 0 ? 0 : curPos);
121 | //Get the text behind the cursor
122 | string textBehindCursor = curLine.SafeRemove(0, curPos < 0 ? 0 : curPos);
123 |
124 | totalLines.DeleteAt(cursorPosition.LineNumber);
125 | totalLines.InsertOrAddRange(ListHelper.CreateLines(lines, 0, textInFrontOfCursor, textBehindCursor), cursorPosition.LineNumber);
126 |
127 | return new CursorPosition(cursorPosition.CharacterPosition + lines.Length > 0 ? lines[lines.Length - 1].Length : 0, cursorPosition.LineNumber + lines.Length - 1);
128 | }
129 |
130 | public static CursorPosition Replace(TextSelection selection, PooledList totalLines, string text, string newLineCharacter)
131 | {
132 | //Just delete the text if the string is emty
133 | if (text == "")
134 | return Remove(selection, totalLines);
135 |
136 | selection = OrderTextSelection(selection);
137 | int startLine = selection.StartPosition.LineNumber;
138 | int endLine = selection.EndPosition.LineNumber;
139 | int startPosition = selection.StartPosition.CharacterPosition;
140 | int endPosition = selection.EndPosition.CharacterPosition;
141 |
142 | string[] lines = text.Split(newLineCharacter);
143 | string start_Line = totalLines.GetLineText(startLine);
144 |
145 | //Selection is singleline and text to paste is also singleline
146 | if (startLine == endLine && lines.Length == 1)
147 | {
148 | start_Line =
149 | (startPosition == 0 && endPosition == totalLines.GetLineLength(endLine)) ?
150 | "" :
151 | start_Line.SafeRemove(startPosition, endPosition - startPosition
152 | );
153 |
154 | totalLines.SetLineText(startLine, start_Line.AddText(text, startPosition));
155 |
156 | return new CursorPosition(startPosition + text.Length, selection.StartPosition.LineNumber);
157 | }
158 | else if (startLine == endLine && lines.Length > 1 && (startPosition != 0 && endPosition != start_Line.Length))
159 | {
160 | string textTo = start_Line == "" ? "" : startPosition >= start_Line.Length ? start_Line : start_Line.Safe_Substring(0, startPosition);
161 | string textFrom = start_Line == "" ? "" : endPosition >= start_Line.Length ? start_Line : start_Line.Safe_Substring(endPosition);
162 |
163 | totalLines.SetLineText(startLine, (textTo + lines[0]));
164 | totalLines.InsertOrAddRange(ListHelper.CreateLines(lines, 1, "", textFrom), startLine + 1);
165 |
166 | return new CursorPosition(endPosition + text.Length, startLine + lines.Length - 1);
167 | }
168 | else if (WholeTextSelected(selection, totalLines))
169 | {
170 | if (lines.Length < totalLines.Count)
171 | {
172 | ListHelper.Clear(totalLines);
173 | totalLines.InsertOrAddRange(lines, 0);
174 | }
175 | else
176 | ReplaceLines(totalLines, 0, totalLines.Count, lines);
177 |
178 | return new CursorPosition(totalLines.GetLineLength(-1), totalLines.Count - 1);
179 | }
180 | else
181 | {
182 | string end_Line = totalLines.GetLineText(endLine);
183 |
184 | //All lines are selected from start to finish
185 | if (startPosition == 0 && endPosition == end_Line.Length)
186 | {
187 | totalLines.Safe_RemoveRange(startLine, endLine - startLine + 1);
188 | totalLines.InsertOrAddRange(lines, startLine);
189 | }
190 | //Only the startline is completely selected
191 | else if (startPosition == 0 && endPosition != end_Line.Length)
192 | {
193 | totalLines.SetLineText(endLine, end_Line.Substring(endPosition).AddToStart(lines[lines.Length - 1]));
194 |
195 | totalLines.Safe_RemoveRange(startLine, endLine - startLine);
196 | totalLines.InsertOrAddRange(lines.Take(lines.Length - 1), startLine);
197 | }
198 | //Only the endline is completely selected
199 | else if (startPosition != 0 && endPosition == end_Line.Length)
200 | {
201 | totalLines.SetLineText(startLine, start_Line.SafeRemove(startPosition).AddToEnd(lines[0]));
202 |
203 | totalLines.Safe_RemoveRange(startLine + 1, endLine - startLine);
204 | totalLines.InsertOrAddRange(lines.Skip(1), startLine + 1);
205 | }
206 | else
207 | {
208 | //Delete the selected parts
209 | start_Line = start_Line.SafeRemove(startPosition);
210 | end_Line = end_Line.Safe_Substring(endPosition);
211 |
212 | //Only one line to insert
213 | if (lines.Length == 1)
214 | {
215 | totalLines.SetLineText(startLine, start_Line.AddToEnd(lines[0] + end_Line));
216 | totalLines.Safe_RemoveRange(startLine + 1, endLine - startLine < 0 ? 0 : endLine - startLine);
217 | }
218 | else
219 | {
220 | totalLines.SetLineText(startLine, start_Line.AddToEnd(lines[0]));
221 | totalLines.SetLineText(endLine, end_Line.AddToStart(lines[lines.Length - 1]));
222 |
223 | totalLines.Safe_RemoveRange(startLine + 1, endLine - startLine - 1 < 0 ? 0 : endLine - startLine - 1);
224 | if (lines.Length > 2)
225 | totalLines.InsertOrAddRange(lines.GetLines(1, lines.Length - 2), startLine + 1);
226 | }
227 | }
228 | return new CursorPosition(start_Line.Length + end_Line.Length - 1, startLine + lines.Length - 1);
229 | }
230 | }
231 |
232 | public static CursorPosition Remove(TextSelection selection, PooledList totalLines)
233 | {
234 | selection = OrderTextSelection(selection);
235 | int startLine = selection.StartPosition.LineNumber;
236 | int endLine = selection.EndPosition.LineNumber;
237 | int startPosition = selection.StartPosition.CharacterPosition;
238 | int endPosition = selection.EndPosition.CharacterPosition;
239 |
240 | string start_Line = totalLines.GetLineText(startLine);
241 | string end_Line = totalLines.GetLineText(endLine);
242 |
243 | if (startLine == endLine)
244 | {
245 | totalLines.SetLineText(startLine,
246 | (startPosition == 0 && endPosition == end_Line.Length ?
247 | "" :
248 | start_Line.SafeRemove(startPosition, endPosition - startPosition))
249 | );
250 | }
251 | else if (WholeTextSelected(selection, totalLines))
252 | {
253 | ListHelper.Clear(totalLines, true);
254 | return new CursorPosition(0, totalLines.Count - 1);
255 | }
256 | else
257 | {
258 | //Whole lines are selected from start to finish
259 | if (startPosition == 0 && endPosition == end_Line.Length)
260 | {
261 | totalLines.Safe_RemoveRange(startLine, endLine - startLine + 1);
262 | }
263 | //Only the startline is completely selected
264 | else if (startPosition == 0 && endPosition != end_Line.Length)
265 | {
266 | totalLines.SetLineText(endLine, end_Line.Safe_Substring(endPosition));
267 | totalLines.Safe_RemoveRange(startLine, endLine - startLine);
268 | }
269 | //Only the endline is completely selected
270 | else if (startPosition != 0 && endPosition == end_Line.Length)
271 | {
272 | totalLines.SetLineText(startLine, start_Line.SafeRemove(startPosition));
273 | totalLines.Safe_RemoveRange(startLine + 1, endLine - startLine);
274 | }
275 | //Both startline and endline are not completely selected
276 | else
277 | {
278 | totalLines.SetLineText(startLine, start_Line.SafeRemove(startPosition) + end_Line.Safe_Substring(endPosition));
279 | totalLines.Safe_RemoveRange(startLine + 1, endLine - startLine);
280 | }
281 | }
282 |
283 | if (totalLines.Count == 0)
284 | totalLines.AddLine();
285 |
286 | return new CursorPosition(startPosition, startLine);
287 | }
288 |
289 | public static TextSelectionPosition GetIndexOfSelection(PooledList totalLines, TextSelection selection)
290 | {
291 | var sel = OrderTextSelection(selection);
292 | int startIndex = Cursor.CursorPositionToIndex(totalLines, sel.StartPosition);
293 | int endIndex = Cursor.CursorPositionToIndex(totalLines, sel.EndPosition);
294 |
295 | if (endIndex > startIndex)
296 | return new TextSelectionPosition(Math.Min(startIndex, endIndex), endIndex - startIndex);
297 | else
298 | return new TextSelectionPosition(Math.Min(startIndex, endIndex), startIndex - endIndex);
299 | }
300 |
301 | public static TextSelection GetSelectionFromPosition(PooledList totalLines, int startPosition, int length, int numberOfCharacters)
302 | {
303 | TextSelection returnValue = new TextSelection();
304 |
305 | if (startPosition + length > numberOfCharacters)
306 | {
307 | if (startPosition > numberOfCharacters)
308 | {
309 | startPosition = numberOfCharacters;
310 | length = 0;
311 | }
312 | else
313 | length = numberOfCharacters - startPosition;
314 | }
315 |
316 | void GetIndexInLine(int currentIndex, int currentTotalLength)
317 | {
318 | int position = Math.Abs(currentTotalLength - startPosition);
319 |
320 | returnValue.StartPosition =
321 | new CursorPosition(position, currentIndex);
322 |
323 | if (length == 0)
324 | returnValue.EndPosition = new CursorPosition(returnValue.StartPosition);
325 | else
326 | {
327 | int lengthCount = 0;
328 | for (int i = currentIndex; i < totalLines.Count; i++)
329 | {
330 | int lineLength = totalLines[i].Length + 1;
331 | if (lengthCount + lineLength > length)
332 | {
333 | returnValue.EndPosition = new CursorPosition(Math.Abs(lengthCount - length) + position, i);
334 | break;
335 | }
336 | lengthCount += lineLength;
337 | }
338 | }
339 | }
340 |
341 | //Get the Length
342 | int totalLength = 0;
343 | for (int i = 0; i < totalLines.Count; i++)
344 | {
345 | int lineLength = totalLines[i].Length + 1;
346 | if (totalLength + lineLength > startPosition)
347 | {
348 | GetIndexInLine(i, totalLength);
349 | break;
350 | }
351 |
352 | totalLength += lineLength;
353 | }
354 | return returnValue;
355 | }
356 |
357 | public static string GetSelectedText(PooledList totalLines, TextSelection textSelection, int currentLineIndex, string newLineCharacter)
358 | {
359 | //return the current line, if no text is selected:
360 | if (textSelection == null)
361 | return totalLines.GetLineText(currentLineIndex) + newLineCharacter;
362 |
363 | int startLine = Math.Min(textSelection.StartPosition.LineNumber, textSelection.EndPosition.LineNumber);
364 | int endLine = Math.Max(textSelection.StartPosition.LineNumber, textSelection.EndPosition.LineNumber);
365 | int endIndex = Math.Max(textSelection.StartPosition.CharacterPosition, textSelection.EndPosition.CharacterPosition);
366 | int startIndex = Math.Min(textSelection.StartPosition.CharacterPosition, textSelection.EndPosition.CharacterPosition);
367 |
368 | StringBuilder stringBuilder = new StringBuilder();
369 |
370 | if (startLine == endLine) //Singleline
371 | {
372 | string line = totalLines.GetLineText(startLine < totalLines.Count ? startLine : totalLines.Count - 1);
373 |
374 | if (startIndex == 0 && endIndex != line.Length)
375 | stringBuilder.Append(line.SafeRemove(endIndex));
376 | else if (endIndex == line.Length && startIndex != 0)
377 | stringBuilder.Append(line.Safe_Substring(startIndex));
378 | else if (startIndex == 0 && endIndex == line.Length)
379 | stringBuilder.Append(line);
380 | else stringBuilder.Append(line.SafeRemove(endIndex).Substring(startIndex));
381 | }
382 | else if (WholeTextSelected(textSelection, totalLines))
383 | {
384 | stringBuilder.Append(ListHelper.GetLinesAsString(totalLines, newLineCharacter));
385 | }
386 | else //Multiline
387 | {
388 | //StartLine
389 | stringBuilder.Append(totalLines.GetLineText(startLine).Substring(startIndex) + newLineCharacter);
390 |
391 | //Other lines
392 | if (endLine - startLine > 1)
393 | stringBuilder.Append(totalLines.GetLines(startLine + 1, endLine - startLine - 1).GetString(newLineCharacter) + newLineCharacter);
394 |
395 | //Endline
396 | string currentLine = totalLines.GetLineText(endLine);
397 |
398 | stringBuilder.Append(endIndex >= currentLine.Length ? currentLine : currentLine.SafeRemove(endIndex));
399 | }
400 | return stringBuilder.ToString();
401 | }
402 |
403 | ///
404 | /// Replaces the lines in TotalLines, starting by Start replacing Count number of items, with the string in SplittedText
405 | /// All lines that can be replaced get replaced all lines that are needed additionally get added
406 | ///
407 | ///
408 | ///
409 | ///
410 | ///
411 | public static void ReplaceLines(PooledList totalLines, int start, int count, string[] splittedText)
412 | {
413 | if (splittedText.Length == 0)
414 | {
415 | totalLines.Safe_RemoveRange(start, count);
416 | return;
417 | }
418 |
419 | //Same line-length -> check for any differences in the individual lines
420 | if (count == splittedText.Length)
421 | {
422 | for (int i = 0; i < count; i++)
423 | {
424 | if (!totalLines.GetLineText(i).Equals(splittedText[i], StringComparison.Ordinal))
425 | {
426 | totalLines.SetLineText(start + i, splittedText[i]);
427 | }
428 | }
429 | }
430 | //Delete items from start to count; Insert splittedText at start
431 | else if (count > splittedText.Length)
432 | {
433 | for (int i = 0; i < count; i++)
434 | {
435 | if (i < splittedText.Length)
436 | {
437 | totalLines.SetLineText(start + i, splittedText[i]);
438 | }
439 | else
440 | {
441 | totalLines.Safe_RemoveRange(start + i, count - i);
442 | break;
443 | }
444 | }
445 | }
446 | //Replace all items from start - count with existing (add more if out of range)
447 | else //SplittedText.Length > Count:
448 | {
449 | for (int i = 0; i < splittedText.Length; i++)
450 | {
451 | //replace all possible lines
452 | if (i < count)
453 | {
454 | totalLines.SetLineText(start + i, splittedText[i]);
455 | }
456 | else //Add new lines
457 | {
458 | totalLines.InsertOrAddRange(splittedText.Skip(start + i), start + i);
459 | break;
460 | }
461 | }
462 | }
463 | }
464 |
465 | public static bool MoveLinesUp(PooledList totalLines, TextSelection selection, CursorPosition cursorposition)
466 | {
467 | //Move single line
468 | if (selection != null)
469 | return false;
470 |
471 | if (cursorposition.LineNumber > 0)
472 | {
473 | totalLines.SwapLines(cursorposition.LineNumber, cursorposition.LineNumber - 1);
474 | cursorposition.LineNumber -= 1;
475 | return true;
476 | }
477 | return false;
478 | }
479 | public static bool MoveLinesDown(PooledList totalLines, TextSelection selection, CursorPosition cursorposition)
480 | {
481 | //Move single line
482 | if (selection != null || selection.StartPosition.LineNumber != selection.EndPosition.LineNumber)
483 | return false;
484 |
485 | if (cursorposition.LineNumber < totalLines.Count)
486 | {
487 | totalLines.SwapLines(cursorposition.LineNumber, cursorposition.LineNumber + 1);
488 | cursorposition.LineNumber += 1;
489 | return true;
490 | }
491 | return false;
492 | }
493 | public static void ClearSelectionIfNeeded(TextControlBox textbox, SelectionRenderer selectionrenderer)
494 | {
495 | //If the selection is visible, but is not getting set, clear the selection
496 | if (selectionrenderer.HasSelection && !selectionrenderer.IsSelecting)
497 | {
498 | textbox.ClearSelection();
499 | }
500 | }
501 |
502 |
503 | public static bool SelectionIsNull(SelectionRenderer selectionrenderer, TextSelection selection)
504 | {
505 | if (selection == null)
506 | return true;
507 | return selectionrenderer.SelectionStartPosition == null || selectionrenderer.SelectionEndPosition == null;
508 | }
509 | public static void SelectSingleWord(CanvasHelper canvashelper, SelectionRenderer selectionrenderer, CursorPosition cursorPosition, string currentLine)
510 | {
511 | int characterpos = cursorPosition.CharacterPosition;
512 | //Update variables
513 | selectionrenderer.SelectionStartPosition =
514 | new CursorPosition(characterpos - Cursor.CalculateStepsToMoveLeft2(currentLine, characterpos), cursorPosition.LineNumber);
515 |
516 | selectionrenderer.SelectionEndPosition =
517 | new CursorPosition(characterpos + Cursor.CalculateStepsToMoveRight2(currentLine, characterpos), cursorPosition.LineNumber);
518 |
519 | cursorPosition.CharacterPosition = selectionrenderer.SelectionEndPosition.CharacterPosition;
520 | selectionrenderer.HasSelection = true;
521 |
522 | //Render it
523 | canvashelper.UpdateSelection();
524 | canvashelper.UpdateCursor();
525 | }
526 | }
527 | }
--------------------------------------------------------------------------------