├── 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 | [![NuGet version (TextControlBox)](https://img.shields.io/nuget/v/TextControlBox.JuliusKirsch)](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 | } --------------------------------------------------------------------------------