From b8f289988e08deed0e7a6f84761e5493f8881ce5 Mon Sep 17 00:00:00 2001 From: Jonas Hertzman Date: Tue, 28 Jan 2025 15:27:39 +0100 Subject: [PATCH 01/20] Paint semi transparernt white space characters --- FileDiff/DiffControl.cs | 14 ++++++++++++++ FileDiff/TextSegment.cs | 2 +- FileDiff/TextUtils.cs | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/FileDiff/DiffControl.cs b/FileDiff/DiffControl.cs index a7ca80a..65087fa 100644 --- a/FileDiff/DiffControl.cs +++ b/FileDiff/DiffControl.cs @@ -233,6 +233,20 @@ protected override void OnRender(DrawingContext drawingContext) } drawingContext.DrawGlyphRun(AppSettings.ShowLineChanges ? textSegment.ForegroundBrush : line.ForegroundBrush, segmentRun); + + if (AppSettings.ShowWhiteSpaceCharacters) + { + double offset = 0; + foreach (char x in textSegment.Text) + { + if (x == ' ') + { + drawingContext.DrawEllipse(new SolidColorBrush(Color.FromArgb(128, 255, 0, 0)), null, new Point(nextPosition + offset + segmentRun.AdvanceWidths[textSegment.Text.IndexOf(x)] / 2, characterHeight / 2), 2, 2); + } + + offset += segmentRun.AdvanceWidths[textSegment.Text.IndexOf(x)]; + } + } } nextPosition += runWidth; diff --git a/FileDiff/TextSegment.cs b/FileDiff/TextSegment.cs index 3626fb9..1d24189 100644 --- a/FileDiff/TextSegment.cs +++ b/FileDiff/TextSegment.cs @@ -33,7 +33,7 @@ public override string ToString() public TextState Type { get; set; } - private string Text { get; set; } + public string Text { get; set; } public bool IsWhiteSpace { diff --git a/FileDiff/TextUtils.cs b/FileDiff/TextUtils.cs index 7b9b1d3..2fdfebb 100644 --- a/FileDiff/TextUtils.cs +++ b/FileDiff/TextUtils.cs @@ -128,7 +128,7 @@ private static ushort ReplaceGlyph(int codePoint, GlyphTypeface glyphTypeface, d if (codePoint == '\t') { - displayCodePoint = AppSettings.ShowWhiteSpaceCharacters ? '›' : ' '; + displayCodePoint = AppSettings.ShowWhiteSpaceCharacters ? ' ' : ' '; glyphTypeface.CharacterToGlyphMap.TryGetValue(displayCodePoint, out glyphIndex); double tabCharacterWidth = AppSettings.TabSize * characterWidth; From e0add879d655014a2f77a6875fcf86bdf11e9479 Mon Sep 17 00:00:00 2001 From: Jonas Hertzman Date: Wed, 29 Jan 2025 13:03:52 +0100 Subject: [PATCH 02/20] . --- FileDiff/DiffControl.cs | 12 +++++++----- FileDiff/TextUtils.cs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/FileDiff/DiffControl.cs b/FileDiff/DiffControl.cs index 65087fa..0add09a 100644 --- a/FileDiff/DiffControl.cs +++ b/FileDiff/DiffControl.cs @@ -237,14 +237,16 @@ protected override void OnRender(DrawingContext drawingContext) if (AppSettings.ShowWhiteSpaceCharacters) { double offset = 0; - foreach (char x in textSegment.Text) + for (int characterIndex = 0; characterIndex < textSegment.Text.Length; characterIndex++) { + char x = textSegment.Text[characterIndex]; if (x == ' ') { - drawingContext.DrawEllipse(new SolidColorBrush(Color.FromArgb(128, 255, 0, 0)), null, new Point(nextPosition + offset + segmentRun.AdvanceWidths[textSegment.Text.IndexOf(x)] / 2, characterHeight / 2), 2, 2); + //drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(70, 255, 255, 0)), null, new Rect(offset, 0, segmentRun.AdvanceWidths[characterIndex], characterHeight)); + drawingContext.DrawEllipse(new SolidColorBrush(Color.FromArgb(255, 0, 0, 255)), null, new Point(offset + segmentRun.AdvanceWidths[characterIndex] / 2, characterHeight / 2), 2, 2); } - offset += segmentRun.AdvanceWidths[textSegment.Text.IndexOf(x)]; + offset += segmentRun.AdvanceWidths[characterIndex]; } } } @@ -471,7 +473,7 @@ protected override void OnKeyDown(KeyEventArgs e) { if (Lines[i].Text.Length > 0 && Lines[i].Text[0] == '\t') { - Lines[i].Text = Lines[i].Text.Remove(0, 1); + Lines[i].Text = Lines[i].Text[1..]; } } else @@ -542,7 +544,7 @@ protected override void OnKeyDown(KeyEventArgs e) DeleteSelection(); } - string[] pastedRows = Clipboard.GetText().Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None); + string[] pastedRows = Clipboard.GetText().Split(["\r\n", "\n"], StringSplitOptions.None); string leftOfCursor = Lines[cursorLine].Text[..cursorCharacter]; string rightOfCursor = Lines[cursorLine].Text[cursorCharacter..]; diff --git a/FileDiff/TextUtils.cs b/FileDiff/TextUtils.cs index 2fdfebb..e344db0 100644 --- a/FileDiff/TextUtils.cs +++ b/FileDiff/TextUtils.cs @@ -143,7 +143,7 @@ private static ushort ReplaceGlyph(int codePoint, GlyphTypeface glyphTypeface, d } else if (codePoint == ' ') { - displayCodePoint = AppSettings.ShowWhiteSpaceCharacters ? '·' : ' '; + displayCodePoint = AppSettings.ShowWhiteSpaceCharacters ? ' ' : ' '; glyphTypeface.CharacterToGlyphMap.TryGetValue(displayCodePoint, out glyphIndex); width = Math.Ceiling(glyphTypeface.AdvanceWidths[glyphIndex] * fontSize / dpiScale) * dpiScale; From b48f977aef36d4c7b0303d552d9390df2ff25991 Mon Sep 17 00:00:00 2001 From: Jonas Hertzman Date: Mon, 14 Apr 2025 12:08:32 +0200 Subject: [PATCH 03/20] . --- FileDiff/DiffControl.cs | 42 +++++++++++++++++++++++++++++++++-------- FileDiff/Utils.cs | 14 ++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/FileDiff/DiffControl.cs b/FileDiff/DiffControl.cs index 0add09a..61585b2 100644 --- a/FileDiff/DiffControl.cs +++ b/FileDiff/DiffControl.cs @@ -129,6 +129,13 @@ protected override void OnRender(DrawingContext drawingContext) currentDiffPen.Freeze(); GuidelineSet currentDiffGuide = CreateGuidelineSet(currentDiffPen); + SolidColorBrush whiteSpaceBrush = new SolidColorBrush(Color.FromArgb(255, 100, 100, 255)); + Pen whiteSpacePen = new(whiteSpaceBrush, RoundToWholePixels(1)); + GuidelineSet whiteSpacePenGuide = CreateGuidelineSet(whiteSpacePen); + + + + textMargin = RoundToWholePixels(4); lineNumberMargin = RoundToWholePixels(characterWidth * Lines.Count.ToString().Length) + (2 * textMargin) + borderPen.Thickness; @@ -236,18 +243,37 @@ protected override void OnRender(DrawingContext drawingContext) if (AppSettings.ShowWhiteSpaceCharacters) { - double offset = 0; - for (int characterIndex = 0; characterIndex < textSegment.Text.Length; characterIndex++) + drawingContext.PushGuidelineSet(whiteSpacePenGuide); { - char x = textSegment.Text[characterIndex]; - if (x == ' ') + double offset = 0; + for (int characterIndex = 0; characterIndex < textSegment.Text.Length; characterIndex++) { - //drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(70, 255, 255, 0)), null, new Rect(offset, 0, segmentRun.AdvanceWidths[characterIndex], characterHeight)); - drawingContext.DrawEllipse(new SolidColorBrush(Color.FromArgb(255, 0, 0, 255)), null, new Point(offset + segmentRun.AdvanceWidths[characterIndex] / 2, characterHeight / 2), 2, 2); - } + char x = textSegment.Text[characterIndex]; + double characterWidth = segmentRun.AdvanceWidths[characterIndex]; + double size = Math.Min(characterWidth, characterHeight) / 2 - 2; - offset += segmentRun.AdvanceWidths[characterIndex]; + + if (x.In([' ', '\t'])) + { + drawingContext.DrawRectangle(/*new SolidColorBrush(Color.FromArgb(50, 128, 128, 128))*/null, borderPen, new Rect(offset, 0, characterWidth, characterHeight)); + + if (x == ' ') + { + drawingContext.DrawEllipse(whiteSpaceBrush, null, new Point(offset + characterWidth / 2, characterHeight / 2), 2, 2); + } + if (x == '\t') + { + drawingContext.DrawLine(whiteSpacePen, new Point(offset + 2, characterHeight / 2), new Point(offset + characterWidth - 2, characterHeight / 2)); + + drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - size, characterHeight / 2 - size), new Point(offset + characterWidth - 2, characterHeight / 2)); + drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - size, characterHeight / 2 + size), new Point(offset + characterWidth - 2, characterHeight / 2)); + } + } + + offset += characterWidth; + } } + drawingContext.Pop(); } } nextPosition += runWidth; diff --git a/FileDiff/Utils.cs b/FileDiff/Utils.cs index 15249b1..7084119 100644 --- a/FileDiff/Utils.cs +++ b/FileDiff/Utils.cs @@ -66,4 +66,18 @@ public static string FixRootPath(string path) return path; } + #region Extention Methods + + public static bool In(this T item, params T[] list) + { + return list.Contains(item); + } + + public static bool NotIn(this T item, params T[] list) + { + return !list.Contains(item); + } + + #endregion + } From 822a7e5d5a5dbc4ec4d62430e0f0d74f62f13b21 Mon Sep 17 00:00:00 2001 From: Jonas Hertzman Date: Mon, 14 Apr 2025 14:26:34 +0200 Subject: [PATCH 04/20] WIP --- FileDiff/DiffControl.cs | 25 +++++++++++-------------- FileDiff/TextUtils.cs | 16 +++++----------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/FileDiff/DiffControl.cs b/FileDiff/DiffControl.cs index 61585b2..7bae77f 100644 --- a/FileDiff/DiffControl.cs +++ b/FileDiff/DiffControl.cs @@ -129,13 +129,11 @@ protected override void OnRender(DrawingContext drawingContext) currentDiffPen.Freeze(); GuidelineSet currentDiffGuide = CreateGuidelineSet(currentDiffPen); - SolidColorBrush whiteSpaceBrush = new SolidColorBrush(Color.FromArgb(255, 100, 100, 255)); + SolidColorBrush whiteSpaceBrush = new(Color.FromArgb(255, 100, 100, 255)); Pen whiteSpacePen = new(whiteSpaceBrush, RoundToWholePixels(1)); GuidelineSet whiteSpacePenGuide = CreateGuidelineSet(whiteSpacePen); - - textMargin = RoundToWholePixels(4); lineNumberMargin = RoundToWholePixels(characterWidth * Lines.Count.ToString().Length) + (2 * textMargin) + borderPen.Thickness; @@ -248,25 +246,24 @@ protected override void OnRender(DrawingContext drawingContext) double offset = 0; for (int characterIndex = 0; characterIndex < textSegment.Text.Length; characterIndex++) { - char x = textSegment.Text[characterIndex]; + char character = textSegment.Text[characterIndex]; double characterWidth = segmentRun.AdvanceWidths[characterIndex]; - double size = Math.Min(characterWidth, characterHeight) / 2 - 2; - + double arrowSize = characterHeight * .2; - if (x.In([' ', '\t'])) + if (character.In([' ', '\t'])) { - drawingContext.DrawRectangle(/*new SolidColorBrush(Color.FromArgb(50, 128, 128, 128))*/null, borderPen, new Rect(offset, 0, characterWidth, characterHeight)); + // drawingContext.DrawRectangle(/*new SolidColorBrush(Color.FromArgb(50, 128, 128, 128))*/null, borderPen, new Rect(offset, 0, characterWidth, characterHeight)); - if (x == ' ') + if (character == ' ') { - drawingContext.DrawEllipse(whiteSpaceBrush, null, new Point(offset + characterWidth / 2, characterHeight / 2), 2, 2); + drawingContext.DrawEllipse(whiteSpaceBrush, null, new Point(offset + characterWidth / 2, characterHeight / 2), arrowSize / 2, arrowSize / 2); } - if (x == '\t') + if (character == '\t') { - drawingContext.DrawLine(whiteSpacePen, new Point(offset + 2, characterHeight / 2), new Point(offset + characterWidth - 2, characterHeight / 2)); + drawingContext.DrawLine(whiteSpacePen, new Point(offset + 1, characterHeight / 2), new Point(offset + characterWidth - 2, characterHeight / 2)); - drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - size, characterHeight / 2 - size), new Point(offset + characterWidth - 2, characterHeight / 2)); - drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - size, characterHeight / 2 + size), new Point(offset + characterWidth - 2, characterHeight / 2)); + drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - arrowSize - 1, characterHeight / 2 - arrowSize), new Point(offset + characterWidth - 1, characterHeight / 2)); + drawingContext.DrawLine(whiteSpacePen, new Point(offset + characterWidth - arrowSize - 1, characterHeight / 2 + arrowSize), new Point(offset + characterWidth - 1, characterHeight / 2)); } } diff --git a/FileDiff/TextUtils.cs b/FileDiff/TextUtils.cs index e344db0..ee6111c 100644 --- a/FileDiff/TextUtils.cs +++ b/FileDiff/TextUtils.cs @@ -128,9 +128,11 @@ private static ushort ReplaceGlyph(int codePoint, GlyphTypeface glyphTypeface, d if (codePoint == '\t') { - displayCodePoint = AppSettings.ShowWhiteSpaceCharacters ? ' ' : ' '; + displayCodePoint = ' '; glyphTypeface.CharacterToGlyphMap.TryGetValue(displayCodePoint, out glyphIndex); + + // Tab width varies depending on the position of the tab character in the line. double tabCharacterWidth = AppSettings.TabSize * characterWidth; width = tabCharacterWidth - ((characterStartPosition + tabCharacterWidth) % tabCharacterWidth); @@ -141,16 +143,8 @@ private static ushort ReplaceGlyph(int codePoint, GlyphTypeface glyphTypeface, d return glyphIndex; } - else if (codePoint == ' ') - { - displayCodePoint = AppSettings.ShowWhiteSpaceCharacters ? ' ' : ' '; - - glyphTypeface.CharacterToGlyphMap.TryGetValue(displayCodePoint, out glyphIndex); - width = Math.Ceiling(glyphTypeface.AdvanceWidths[glyphIndex] * fontSize / dpiScale) * dpiScale; - return glyphIndex; - } - // Most fixed width fonts don't have a glyph for zero width space, use the space glyph but set width to 0 - // to not get black squares when painting. + // Most fixed width fonts don't have a glyph for zero width space, use the space glyph + // but set width to 0 to not get black squares when painting. else if (codePoint == '\u200B') { displayCodePoint = ' '; From e4c734d62000f7a89c964531fe66073dbd776590 Mon Sep 17 00:00:00 2001 From: Jonas Hertzman Date: Mon, 14 Apr 2025 16:25:08 +0200 Subject: [PATCH 05/20] Customizable white space colors --- FileDiff/AppSettings.cs | 14 +++++ FileDiff/ColorTheme.cs | 2 + FileDiff/DefaultSettings.cs | 4 ++ FileDiff/DiffControl.cs | 5 +- FileDiff/Windows/MainWindow.xaml | 4 +- FileDiff/Windows/MainWindowViewModel.cs | 6 ++ FileDiff/Windows/OptionsWindow.xaml | 70 +++++++++++----------- FileDiff/Windows/OptionsWindow.xaml.cs | 78 +++++++++++++------------ 8 files changed, 107 insertions(+), 76 deletions(-) diff --git a/FileDiff/AppSettings.cs b/FileDiff/AppSettings.cs index bdbc5b8..e9a4684 100644 --- a/FileDiff/AppSettings.cs +++ b/FileDiff/AppSettings.cs @@ -462,6 +462,18 @@ public static SolidColorBrush MovedToBackground } } + private static SolidColorBrush whiteSpaceForeground; + public static SolidColorBrush WhiteSpaceForeground + { + get { return whiteSpaceForeground; } + set + { + whiteSpaceForeground = value; + whiteSpaceForeground.Freeze(); + CurrentTheme.WhiteSpaceForeground = value.Color.ToString(); + } + } + // Editor colors private static SolidColorBrush lineNumberColor; @@ -881,6 +893,8 @@ private static void UpdateCachedSettings() MovedFromBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.MovedFromBackground)); MovedToBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.MovedToBackground)); + WhiteSpaceForeground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.WhiteSpaceForeground)); + // Editor colors LineNumberColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.LineNumberColor)); CurrentDiffColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString(CurrentTheme.CurrentDiffColor)); diff --git a/FileDiff/ColorTheme.cs b/FileDiff/ColorTheme.cs index f7398a9..9e8974d 100644 --- a/FileDiff/ColorTheme.cs +++ b/FileDiff/ColorTheme.cs @@ -42,6 +42,8 @@ public class ColorTheme public required string MovedFromBackground { get; set; } public required string MovedToBackground { get; set; } + public required string WhiteSpaceForeground { get; set; } + // Editor colors public required string LineNumberColor { get; set; } public required string CurrentDiffColor { get; set; } diff --git a/FileDiff/DefaultSettings.cs b/FileDiff/DefaultSettings.cs index d2b984b..8ad5675 100644 --- a/FileDiff/DefaultSettings.cs +++ b/FileDiff/DefaultSettings.cs @@ -44,6 +44,8 @@ public static class DefaultSettings MovedFromBackground = "#FF2D1B1B", MovedToBackground = "#FF1B261B", + WhiteSpaceForeground = "#FF0000FF", + // Editor colors LineNumberColor = "#FF797979", CurrentDiffColor = "#FF252525", @@ -106,6 +108,8 @@ public static class DefaultSettings MovedFromBackground = "#FFFFF0F0", MovedToBackground = "#FFF0FFF0", + WhiteSpaceForeground = "#FF0000FF", + // Editor colors LineNumberColor = "#FF585858", CurrentDiffColor = "#FFB7B7B7", diff --git a/FileDiff/DiffControl.cs b/FileDiff/DiffControl.cs index 7bae77f..34e7319 100644 --- a/FileDiff/DiffControl.cs +++ b/FileDiff/DiffControl.cs @@ -129,8 +129,7 @@ protected override void OnRender(DrawingContext drawingContext) currentDiffPen.Freeze(); GuidelineSet currentDiffGuide = CreateGuidelineSet(currentDiffPen); - SolidColorBrush whiteSpaceBrush = new(Color.FromArgb(255, 100, 100, 255)); - Pen whiteSpacePen = new(whiteSpaceBrush, RoundToWholePixels(1)); + Pen whiteSpacePen = new(AppSettings.WhiteSpaceForeground, RoundToWholePixels(1)); GuidelineSet whiteSpacePenGuide = CreateGuidelineSet(whiteSpacePen); @@ -256,7 +255,7 @@ protected override void OnRender(DrawingContext drawingContext) if (character == ' ') { - drawingContext.DrawEllipse(whiteSpaceBrush, null, new Point(offset + characterWidth / 2, characterHeight / 2), arrowSize / 2, arrowSize / 2); + drawingContext.DrawEllipse(AppSettings.WhiteSpaceForeground, null, new Point(offset + characterWidth / 2, characterHeight / 2), arrowSize / 2, arrowSize / 2); } if (character == '\t') { diff --git a/FileDiff/Windows/MainWindow.xaml b/FileDiff/Windows/MainWindow.xaml index 3b1bb15..8fbc95b 100644 --- a/FileDiff/Windows/MainWindow.xaml +++ b/FileDiff/Windows/MainWindow.xaml @@ -433,7 +433,7 @@ - + @@ -446,7 +446,7 @@ - + diff --git a/FileDiff/Windows/MainWindowViewModel.cs b/FileDiff/Windows/MainWindowViewModel.cs index 606a22d..26f9c19 100644 --- a/FileDiff/Windows/MainWindowViewModel.cs +++ b/FileDiff/Windows/MainWindowViewModel.cs @@ -545,6 +545,12 @@ public Brush MovedToBackground set { AppSettings.MovedToBackground = value as SolidColorBrush; OnPropertyChangedRepaint(nameof(MovedToBackground)); } } + public Brush WhiteSpaceForeground + { + get { return AppSettings.WhiteSpaceForeground; } + set { AppSettings.WhiteSpaceForeground = value as SolidColorBrush; OnPropertyChangedRepaint(nameof(WhiteSpaceForeground)); } + } + // Editor colors public Brush SelectionBackground diff --git a/FileDiff/Windows/OptionsWindow.xaml b/FileDiff/Windows/OptionsWindow.xaml index 4e61535..3364bd7 100644 --- a/FileDiff/Windows/OptionsWindow.xaml +++ b/FileDiff/Windows/OptionsWindow.xaml @@ -123,6 +123,7 @@ + @@ -197,20 +198,23 @@ -