refactor: rewrite TextDiffView

This commit is contained in:
leo 2024-06-12 21:12:45 +08:00
parent eab680ae55
commit 68061f82b1
No known key found for this signature in database
GPG key ID: B528468E49CD0E58
6 changed files with 318 additions and 497 deletions

View file

@ -25,11 +25,11 @@
<Color x:Key="Color.FG1">#FF1F1F1F</Color> <Color x:Key="Color.FG1">#FF1F1F1F</Color>
<Color x:Key="Color.FG2">#FF6F6F6F</Color> <Color x:Key="Color.FG2">#FF6F6F6F</Color>
<Color x:Key="Color.FG3">#FFFFFFFF</Color> <Color x:Key="Color.FG3">#FFFFFFFF</Color>
<Color x:Key="Color.TextDiffView.LineBG1.EMPTY">#3C000000</Color> <Color x:Key="Color.Diff.EmptyBG">#3C000000</Color>
<Color x:Key="Color.TextDiffView.LineBG1.ADD">#3C00FF00</Color> <Color x:Key="Color.Diff.AddedBG">#3C00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG1.DELETED">#3CFF0000</Color> <Color x:Key="Color.Diff.DeletedBG">#3CFF0000</Color>
<Color x:Key="Color.TextDiffView.LineBG2.ADD">#5A00FF00</Color> <Color x:Key="Color.Diff.AddedHighlight">#5A00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG2.DELETED">#50FF0000</Color> <Color x:Key="Color.Diff.DeletedHighlight">#50FF0000</Color>
</ResourceDictionary> </ResourceDictionary>
<ResourceDictionary x:Key="Dark"> <ResourceDictionary x:Key="Dark">
@ -56,11 +56,11 @@
<Color x:Key="Color.FG1">#FFDDDDDD</Color> <Color x:Key="Color.FG1">#FFDDDDDD</Color>
<Color x:Key="Color.FG2">#40F1F1F1</Color> <Color x:Key="Color.FG2">#40F1F1F1</Color>
<Color x:Key="Color.FG3">#FF252525</Color> <Color x:Key="Color.FG3">#FF252525</Color>
<Color x:Key="Color.TextDiffView.LineBG1.EMPTY">#3C000000</Color> <Color x:Key="Color.Diff.EmptyBG">#3C000000</Color>
<Color x:Key="Color.TextDiffView.LineBG1.ADD">#3C00FF00</Color> <Color x:Key="Color.Diff.AddedBG">#3C00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG1.DELETED">#3CFF0000</Color> <Color x:Key="Color.Diff.DeletedBG">#3CFF0000</Color>
<Color x:Key="Color.TextDiffView.LineBG2.ADD">#5A00FF00</Color> <Color x:Key="Color.Diff.AddedHighlight">#5A00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG2.DELETED">#50FF0000</Color> <Color x:Key="Color.Diff.DeletedHighlight">#50FF0000</Color>
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries> </ResourceDictionary.ThemeDictionaries>
@ -89,9 +89,9 @@
<SolidColorBrush x:Key="Brush.FG3" Color="{DynamicResource Color.FG3}"/> <SolidColorBrush x:Key="Brush.FG3" Color="{DynamicResource Color.FG3}"/>
<SolidColorBrush x:Key="Brush.Accent" Color="{DynamicResource SystemAccentColor}"/> <SolidColorBrush x:Key="Brush.Accent" Color="{DynamicResource SystemAccentColor}"/>
<SolidColorBrush x:Key="Brush.AccentHovered" Color="{DynamicResource SystemListLowColor}"/> <SolidColorBrush x:Key="Brush.AccentHovered" Color="{DynamicResource SystemListLowColor}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG1.EMPTY" Color="{DynamicResource Color.TextDiffView.LineBG1.EMPTY}"/> <SolidColorBrush x:Key="Brush.Diff.EmptyBG" Color="{DynamicResource Color.Diff.EmptyBG}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG1.ADD" Color="{DynamicResource Color.TextDiffView.LineBG1.ADD}"/> <SolidColorBrush x:Key="Brush.Diff.AddedBG" Color="{DynamicResource Color.Diff.AddedBG}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG1.DELETED" Color="{DynamicResource Color.TextDiffView.LineBG1.DELETED}"/> <SolidColorBrush x:Key="Brush.Diff.DeletedBG" Color="{DynamicResource Color.Diff.DeletedBG}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG2.ADD" Color="{DynamicResource Color.TextDiffView.LineBG2.ADD}"/> <SolidColorBrush x:Key="Brush.Diff.AddedHighlight" Color="{DynamicResource Color.Diff.AddedHighlight}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG2.DELETED" Color="{DynamicResource Color.TextDiffView.LineBG2.DELETED}"/> <SolidColorBrush x:Key="Brush.Diff.DeletedHighlight" Color="{DynamicResource Color.Diff.DeletedHighlight}"/>
</ResourceDictionary> </ResourceDictionary>

View file

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels namespace SourceGit.ViewModels
@ -11,7 +11,13 @@ namespace SourceGit.ViewModels
public List<Models.TextDiffLine> New { get; set; } = new List<Models.TextDiffLine>(); public List<Models.TextDiffLine> New { get; set; } = new List<Models.TextDiffLine>();
public int MaxLineNumber = 0; public int MaxLineNumber = 0;
public TwoSideTextDiff(Models.TextDiff diff) public Vector SyncScrollOffset
{
get => _syncScrollOffset;
set => SetProperty(ref _syncScrollOffset, value);
}
public TwoSideTextDiff(Models.TextDiff diff, TwoSideTextDiff previous = null)
{ {
File = diff.File; File = diff.File;
MaxLineNumber = diff.MaxLineNumber; MaxLineNumber = diff.MaxLineNumber;
@ -35,6 +41,9 @@ namespace SourceGit.ViewModels
} }
FillEmptyLines(); FillEmptyLines();
if (previous != null && previous.File == File)
_syncScrollOffset = previous._syncScrollOffset;
} }
private void FillEmptyLines() private void FillEmptyLines()
@ -52,5 +61,7 @@ namespace SourceGit.ViewModels
New.Add(new Models.TextDiffLine()); New.Add(new Models.TextDiffLine());
} }
} }
private Vector _syncScrollOffset = Vector.Zero;
} }
} }

View file

@ -379,27 +379,19 @@ namespace SourceGit.ViewModels
if (isUnstaged) if (isUnstaged)
{ {
if (changes.Count == _unstaged.Count && _staged.Count == 0) if (changes.Count == _unstaged.Count && _staged.Count == 0)
{
PopupHost.ShowPopup(new Discard(_repo)); PopupHost.ShowPopup(new Discard(_repo));
}
else else
{
PopupHost.ShowPopup(new Discard(_repo, changes, true)); PopupHost.ShowPopup(new Discard(_repo, changes, true));
} }
}
else else
{ {
if (changes.Count == _staged.Count && _unstaged.Count == 0) if (changes.Count == _staged.Count && _unstaged.Count == 0)
{
PopupHost.ShowPopup(new Discard(_repo)); PopupHost.ShowPopup(new Discard(_repo));
}
else else
{
PopupHost.ShowPopup(new Discard(_repo, changes, false)); PopupHost.ShowPopup(new Discard(_repo, changes, false));
} }
} }
} }
}
public void Commit() public void Commit()
{ {
@ -921,24 +913,11 @@ namespace SourceGit.ViewModels
var isUnstaged = _selectedUnstaged != null && _selectedUnstaged.Count > 0; var isUnstaged = _selectedUnstaged != null && _selectedUnstaged.Count > 0;
if (change == null) if (change == null)
{
DetailContext = null; DetailContext = null;
}
else if (change.IsConflit && isUnstaged) else if (change.IsConflit && isUnstaged)
{
DetailContext = new ConflictContext(_repo.FullPath, change); DetailContext = new ConflictContext(_repo.FullPath, change);
}
else else
{ DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), _detailContext as DiffContext);
if (_detailContext is DiffContext previous)
{
DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), previous);
}
else
{
DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged));
}
}
} }
private async void UseTheirs(List<Models.Change> changes) private async void UseTheirs(List<Models.Change> changes)

View file

@ -234,9 +234,7 @@
<!-- Text Diff --> <!-- Text Diff -->
<DataTemplate DataType="m:TextDiff"> <DataTemplate DataType="m:TextDiff">
<v:TextDiffView TextDiff="{Binding}" <v:TextDiffView UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"/>
SyncScrollOffset="{Binding SyncScrollOffset, Mode=TwoWay}"
UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"/>
</DataTemplate> </DataTemplate>
<!-- Empty or only EOL changes --> <!-- Empty or only EOL changes -->

View file

@ -11,18 +11,16 @@
Background="{DynamicResource Brush.Contents}"> Background="{DynamicResource Brush.Contents}">
<UserControl.DataTemplates> <UserControl.DataTemplates>
<DataTemplate DataType="m:TextDiff"> <DataTemplate DataType="m:TextDiff">
<v:CombinedTextDiffPresenter BorderBrush="{DynamicResource Brush.Border2}" <v:CombinedTextDiffPresenter FileName="{Binding File}"
BorderThickness="0"
LineBGEmpty = "{DynamicResource Brush.TextDiffView.LineBG1.EMPTY}"
LineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG1.ADD}"
LineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG1.DELETED}"
SecondaryLineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG2.ADD}"
SecondaryLineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG2.DELETED}"
Foreground="{DynamicResource Brush.FG1}" Foreground="{DynamicResource Brush.FG1}"
SecondaryFG="{DynamicResource Brush.FG2}" LineBrush="{DynamicResource Brush.Border2}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
DiffData="{Binding}"
SyncScrollOffset="{Binding #ThisControl.SyncScrollOffset, Mode=TwoWay}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/> WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/>
</DataTemplate> </DataTemplate>
@ -30,40 +28,36 @@
<DataTemplate DataType="vm:TwoSideTextDiff"> <DataTemplate DataType="vm:TwoSideTextDiff">
<Grid ColumnDefinitions="*,1,*"> <Grid ColumnDefinitions="*,1,*">
<v:SingleSideTextDiffPresenter Grid.Column="0" <v:SingleSideTextDiffPresenter Grid.Column="0"
SyncScrollOffset="{Binding #ThisControl.SyncScrollOffset, Mode=TwoWay}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
IsOld="True" IsOld="True"
BorderBrush="{DynamicResource Brush.Border2}" FileName="{Binding File}"
BorderThickness="0"
LineBGEmpty = "{DynamicResource Brush.TextDiffView.LineBG1.EMPTY}"
LineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG1.ADD}"
LineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG1.DELETED}"
SecondaryLineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG2.ADD}"
SecondaryLineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG2.DELETED}"
Foreground="{DynamicResource Brush.FG1}" Foreground="{DynamicResource Brush.FG1}"
SecondaryFG="{DynamicResource Brush.FG2}" LineBrush="{DynamicResource Brush.Border2}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
DiffData="{Binding}"/> UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/>
<Rectangle Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/> <Rectangle Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
<v:SingleSideTextDiffPresenter Grid.Column="2" <v:SingleSideTextDiffPresenter Grid.Column="2"
SyncScrollOffset="{Binding #ThisControl.SyncScrollOffset, Mode=TwoWay}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
IsOld="False" IsOld="False"
BorderBrush="{DynamicResource Brush.Border2}" FileName="{Binding File}"
BorderThickness="0"
LineBGEmpty = "{DynamicResource Brush.TextDiffView.LineBG1.EMPTY}"
LineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG1.ADD}"
LineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG1.DELETED}"
SecondaryLineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG2.ADD}"
SecondaryLineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG2.DELETED}"
Foreground="{DynamicResource Brush.FG1}" Foreground="{DynamicResource Brush.FG1}"
SecondaryFG="{DynamicResource Brush.FG2}" LineBrush="{DynamicResource Brush.Border2}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
DiffData="{Binding}"/> UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</UserControl.DataTemplates> </UserControl.DataTemplates>

View file

@ -7,6 +7,7 @@ using System.Text;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
@ -21,7 +22,161 @@ using AvaloniaEdit.Utils;
namespace SourceGit.Views namespace SourceGit.Views
{ {
public class CombinedTextDiffPresenter : TextEditor public class IThemedTextDiffPresenter : TextEditor
{
public static readonly StyledProperty<string> FileNameProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, string>(nameof(FileName), string.Empty);
public string FileName
{
get => GetValue(FileNameProperty);
set => SetValue(FileNameProperty, value);
}
public static readonly StyledProperty<IBrush> LineBrushProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(LineBrush), new SolidColorBrush(Colors.DarkGray));
public IBrush LineBrush
{
get => GetValue(LineBrushProperty);
set => SetValue(LineBrushProperty, value);
}
public static readonly StyledProperty<IBrush> EmptyContentBackgroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(EmptyContentBackground), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)));
public IBrush EmptyContentBackground
{
get => GetValue(EmptyContentBackgroundProperty);
set => SetValue(EmptyContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> AddedContentBackgroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(AddedContentBackground), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush AddedContentBackground
{
get => GetValue(AddedContentBackgroundProperty);
set => SetValue(AddedContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> DeletedContentBackgroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(DeletedContentBackground), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush DeletedContentBackground
{
get => GetValue(DeletedContentBackgroundProperty);
set => SetValue(DeletedContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> AddedHighlightBrushProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(AddedHighlightBrush), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)));
public IBrush AddedHighlightBrush
{
get => GetValue(AddedHighlightBrushProperty);
set => SetValue(AddedHighlightBrushProperty, value);
}
public static readonly StyledProperty<IBrush> DeletedHighlightBrushProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(DeletedHighlightBrush), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)));
public IBrush DeletedHighlightBrush
{
get => GetValue(DeletedHighlightBrushProperty);
set => SetValue(DeletedHighlightBrushProperty, value);
}
public static readonly StyledProperty<IBrush> IndicatorForegroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(IndicatorForeground), Brushes.Gray);
public IBrush IndicatorForeground
{
get => GetValue(IndicatorForegroundProperty);
set => SetValue(IndicatorForegroundProperty, value);
}
public static readonly StyledProperty<bool> UseSyntaxHighlightingProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, bool>(nameof(UseSyntaxHighlighting), false);
public bool UseSyntaxHighlighting
{
get => GetValue(UseSyntaxHighlightingProperty);
set => SetValue(UseSyntaxHighlightingProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public IThemedTextDiffPresenter(TextArea area, TextDocument doc): base(area, doc)
{
IsReadOnly = true;
ShowLineNumbers = false;
BorderThickness = new Thickness(0);
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
UpdateTextMate();
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == UseSyntaxHighlightingProperty)
UpdateTextMate();
else if (change.Property == FileNameProperty)
Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName);
else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null)
Models.TextMateHelper.SetThemeByApp(_textMate);
}
protected void UpdateTextMate()
{
if (UseSyntaxHighlighting)
{
if (_textMate == null)
{
TextArea.TextView.LineTransformers.Remove(_lineStyleTransformer);
_textMate = Models.TextMateHelper.CreateForEditor(this);
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName);
}
}
else
{
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
GC.Collect();
TextArea.TextView.Redraw();
}
}
}
private TextMate.Installation _textMate = null;
protected IVisualLineTransformer _lineStyleTransformer = null;
}
public class CombinedTextDiffPresenter : IThemedTextDiffPresenter
{ {
public class LineNumberMargin : AbstractMargin public class LineNumberMargin : AbstractMargin
{ {
@ -104,7 +259,7 @@ namespace SourceGit.Views
public override void Render(DrawingContext context) public override void Render(DrawingContext context)
{ {
var pen = new Pen(_editor.BorderBrush, 1); var pen = new Pen(_editor.LineBrush, 1);
context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height)); context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height));
} }
@ -155,11 +310,11 @@ namespace SourceGit.Views
switch (type) switch (type)
{ {
case Models.TextDiffLineType.None: case Models.TextDiffLineType.None:
return _editor.LineBGEmpty; return _editor.EmptyContentBackground;
case Models.TextDiffLineType.Added: case Models.TextDiffLineType.Added:
return _editor.LineBGAdd; return _editor.AddedContentBackground;
case Models.TextDiffLineType.Deleted: case Models.TextDiffLineType.Deleted:
return _editor.LineBGDeleted; return _editor.DeletedContentBackground;
default: default:
return null; return null;
} }
@ -186,7 +341,7 @@ namespace SourceGit.Views
{ {
ChangeLinePart(line.Offset, line.EndOffset, v => ChangeLinePart(line.Offset, line.EndOffset, v =>
{ {
v.TextRunProperties.SetForegroundBrush(_editor.SecondaryFG); v.TextRunProperties.SetForegroundBrush(_editor.IndicatorForeground);
v.TextRunProperties.SetTypeface(new Typeface(_editor.FontFamily, FontStyle.Italic)); v.TextRunProperties.SetTypeface(new Typeface(_editor.FontFamily, FontStyle.Italic));
}); });
@ -195,7 +350,7 @@ namespace SourceGit.Views
if (info.Highlights.Count > 0) if (info.Highlights.Count > 0)
{ {
var bg = info.Type == Models.TextDiffLineType.Added ? _editor.SecondaryLineBGAdd : _editor.SecondaryLineBGDeleted; var bg = info.Type == Models.TextDiffLineType.Added ? _editor.AddedHighlightBrush : _editor.DeletedHighlightBrush;
foreach (var highlight in info.Highlights) foreach (var highlight in info.Highlights)
{ {
ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v => ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v =>
@ -209,129 +364,57 @@ namespace SourceGit.Views
private readonly CombinedTextDiffPresenter _editor; private readonly CombinedTextDiffPresenter _editor;
} }
public static readonly StyledProperty<Models.TextDiff> DiffDataProperty = public Models.TextDiff DiffData => DataContext as Models.TextDiff;
AvaloniaProperty.Register<CombinedTextDiffPresenter, Models.TextDiff>(nameof(DiffData));
public Models.TextDiff DiffData
{
get => GetValue(DiffDataProperty);
set => SetValue(DiffDataProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGEmptyProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(LineBGEmpty), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)));
public IBrush LineBGEmpty
{
get => GetValue(LineBGEmptyProperty);
set => SetValue(LineBGEmptyProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGAddProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(LineBGAdd), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush LineBGAdd
{
get => GetValue(LineBGAddProperty);
set => SetValue(LineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGDeletedProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(LineBGDeleted), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush LineBGDeleted
{
get => GetValue(LineBGDeletedProperty);
set => SetValue(LineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGAddProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(SecondaryLineBGAdd), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)));
public IBrush SecondaryLineBGAdd
{
get => GetValue(SecondaryLineBGAddProperty);
set => SetValue(SecondaryLineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGDeletedProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(SecondaryLineBGDeleted), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)));
public IBrush SecondaryLineBGDeleted
{
get => GetValue(SecondaryLineBGDeletedProperty);
set => SetValue(SecondaryLineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryFGProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(SecondaryFG), Brushes.Gray);
public IBrush SecondaryFG
{
get => GetValue(SecondaryFGProperty);
set => SetValue(SecondaryFGProperty, value);
}
public static readonly StyledProperty<Vector> SyncScrollOffsetProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, Vector>(nameof(SyncScrollOffset));
public Vector SyncScrollOffset
{
get => GetValue(SyncScrollOffsetProperty);
set => SetValue(SyncScrollOffsetProperty, value);
}
public static readonly StyledProperty<bool> UseSyntaxHighlightingProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, bool>(nameof(UseSyntaxHighlighting), false);
public bool UseSyntaxHighlighting
{
get => GetValue(UseSyntaxHighlightingProperty);
set => SetValue(UseSyntaxHighlightingProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public CombinedTextDiffPresenter() : base(new TextArea(), new TextDocument()) public CombinedTextDiffPresenter() : base(new TextArea(), new TextDocument())
{ {
_lineStyleTransformer = new LineStyleTransformer(this); _lineStyleTransformer = new LineStyleTransformer(this);
IsReadOnly = true;
ShowLineNumbers = false;
TextArea.LeftMargins.Add(new LineNumberMargin(this, true) { Margin = new Thickness(8, 0) }); TextArea.LeftMargins.Add(new LineNumberMargin(this, true) { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this)); TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.LeftMargins.Add(new LineNumberMargin(this, false) { Margin = new Thickness(8, 0) }); TextArea.LeftMargins.Add(new LineNumberMargin(this, false) { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this)); TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this)); TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer); TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
TextArea.TextView.Options.EnableHyperlinks = false; }
TextArea.TextView.Options.EnableEmailHyperlinks = false;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var scroller = (ScrollViewer)e.NameScope.Find("PART_ScrollViewer");
scroller.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.TwoWay));
} }
protected override void OnLoaded(RoutedEventArgs e) protected override void OnLoaded(RoutedEventArgs e)
{ {
base.OnLoaded(e); base.OnLoaded(e);
UpdateTextMate();
TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.ContextRequested += OnTextViewContextRequested;
TextArea.TextView.ScrollOffsetChanged += OnTextViewScrollOffsetChanged;
} }
protected override void OnUnloaded(RoutedEventArgs e) protected override void OnUnloaded(RoutedEventArgs e)
{ {
base.OnUnloaded(e); base.OnUnloaded(e);
TextArea.TextView.ContextRequested -= OnTextViewContextRequested; TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
TextArea.TextView.ScrollOffsetChanged -= OnTextViewScrollOffsetChanged; }
if (_textMate != null) protected override void OnDataContextChanged(EventArgs e)
{ {
_textMate.Dispose(); base.OnDataContextChanged(e);
_textMate = null;
var textDiff = DataContext as Models.TextDiff;
if (textDiff != null)
{
var builder = new StringBuilder();
foreach (var line in textDiff.Lines)
builder.AppendLine(line.Content);
Text = builder.ToString();
}
else
{
Text = string.Empty;
} }
GC.Collect(); GC.Collect();
@ -346,9 +429,7 @@ namespace SourceGit.Views
var menu = new ContextMenu(); var menu = new ContextMenu();
var parentView = this.FindAncestorOfType<TextDiffView>(); var parentView = this.FindAncestorOfType<TextDiffView>();
if (parentView != null) if (parentView != null)
{
parentView.FillContextMenuForWorkingCopyChange(menu, selection.StartPosition.Line, selection.EndPosition.Line, false); parentView.FillContextMenuForWorkingCopyChange(menu, selection.StartPosition.Line, selection.EndPosition.Line, false);
}
var copy = new MenuItem(); var copy = new MenuItem();
copy.Header = App.Text("Copy"); copy.Header = App.Text("Copy");
@ -364,84 +445,9 @@ namespace SourceGit.Views
TextArea.TextView.OpenContextMenu(menu); TextArea.TextView.OpenContextMenu(menu);
e.Handled = true; e.Handled = true;
} }
private void OnTextViewScrollOffsetChanged(object sender, EventArgs e)
{
SetCurrentValue(SyncScrollOffsetProperty, TextArea.TextView.ScrollOffset);
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) public class SingleSideTextDiffPresenter : IThemedTextDiffPresenter
{
base.OnPropertyChanged(change);
if (change.Property == DiffDataProperty)
{
if (DiffData != null)
{
var builder = new StringBuilder();
foreach (var line in DiffData.Lines)
{
builder.AppendLine(line.Content);
}
Text = builder.ToString();
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
else
{
Text = string.Empty;
}
}
else if (change.Property == SyncScrollOffsetProperty)
{
if (TextArea.TextView.ScrollOffset != SyncScrollOffset)
{
IScrollable scrollable = TextArea.TextView;
scrollable.Offset = SyncScrollOffset;
}
}
else if (change.Property == UseSyntaxHighlightingProperty)
{
UpdateTextMate();
}
else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null)
{
Models.TextMateHelper.SetThemeByApp(_textMate);
}
}
private void UpdateTextMate()
{
if (UseSyntaxHighlighting)
{
if (_textMate == null)
{
TextArea.TextView.LineTransformers.Remove(_lineStyleTransformer);
_textMate = Models.TextMateHelper.CreateForEditor(this);
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
if (DiffData != null)
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
}
else
{
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
GC.Collect();
TextArea.TextView.Redraw();
}
}
}
private TextMate.Installation _textMate;
private readonly LineStyleTransformer _lineStyleTransformer = null;
}
public class SingleSideTextDiffPresenter : TextEditor
{ {
public class LineNumberMargin : AbstractMargin public class LineNumberMargin : AbstractMargin
{ {
@ -523,7 +529,7 @@ namespace SourceGit.Views
public override void Render(DrawingContext context) public override void Render(DrawingContext context)
{ {
var pen = new Pen(_editor.BorderBrush, 1); var pen = new Pen(_editor.LineBrush, 1);
context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height)); context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height));
} }
@ -575,11 +581,11 @@ namespace SourceGit.Views
switch (type) switch (type)
{ {
case Models.TextDiffLineType.None: case Models.TextDiffLineType.None:
return _editor.LineBGEmpty; return _editor.EmptyContentBackground;
case Models.TextDiffLineType.Added: case Models.TextDiffLineType.Added:
return _editor.LineBGAdd; return _editor.AddedContentBackground;
case Models.TextDiffLineType.Deleted: case Models.TextDiffLineType.Deleted:
return _editor.LineBGDeleted; return _editor.DeletedContentBackground;
default: default:
return null; return null;
} }
@ -607,7 +613,7 @@ namespace SourceGit.Views
{ {
ChangeLinePart(line.Offset, line.EndOffset, v => ChangeLinePart(line.Offset, line.EndOffset, v =>
{ {
v.TextRunProperties.SetForegroundBrush(_editor.SecondaryFG); v.TextRunProperties.SetForegroundBrush(_editor.IndicatorForeground);
v.TextRunProperties.SetTypeface(new Typeface(_editor.FontFamily, FontStyle.Italic)); v.TextRunProperties.SetTypeface(new Typeface(_editor.FontFamily, FontStyle.Italic));
}); });
@ -616,7 +622,7 @@ namespace SourceGit.Views
if (info.Highlights.Count > 0) if (info.Highlights.Count > 0)
{ {
var bg = info.Type == Models.TextDiffLineType.Added ? _editor.LineBGAdd : _editor.LineBGDeleted; var bg = info.Type == Models.TextDiffLineType.Added ? _editor.AddedHighlightBrush : _editor.DeletedHighlightBrush;
foreach (var highlight in info.Highlights) foreach (var highlight in info.Highlights)
{ {
ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v => ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v =>
@ -639,103 +645,16 @@ namespace SourceGit.Views
set => SetValue(IsOldProperty, value); set => SetValue(IsOldProperty, value);
} }
public static readonly StyledProperty<ViewModels.TwoSideTextDiff> DiffDataProperty = public ViewModels.TwoSideTextDiff DiffData => DataContext as ViewModels.TwoSideTextDiff;
AvaloniaProperty.Register<SingleSideTextDiffPresenter, ViewModels.TwoSideTextDiff>(nameof(DiffData));
public ViewModels.TwoSideTextDiff DiffData
{
get => GetValue(DiffDataProperty);
set => SetValue(DiffDataProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGEmptyProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(LineBGEmpty), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)));
public IBrush LineBGEmpty
{
get => GetValue(LineBGEmptyProperty);
set => SetValue(LineBGEmptyProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGAddProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(LineBGAdd), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush LineBGAdd
{
get => GetValue(LineBGAddProperty);
set => SetValue(LineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGDeletedProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(LineBGDeleted), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush LineBGDeleted
{
get => GetValue(LineBGDeletedProperty);
set => SetValue(LineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGAddProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(SecondaryLineBGAdd), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)));
public IBrush SecondaryLineBGAdd
{
get => GetValue(SecondaryLineBGAddProperty);
set => SetValue(SecondaryLineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGDeletedProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(SecondaryLineBGDeleted), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)));
public IBrush SecondaryLineBGDeleted
{
get => GetValue(SecondaryLineBGDeletedProperty);
set => SetValue(SecondaryLineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryFGProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(SecondaryFG), Brushes.Gray);
public IBrush SecondaryFG
{
get => GetValue(SecondaryFGProperty);
set => SetValue(SecondaryFGProperty, value);
}
public static readonly StyledProperty<Vector> SyncScrollOffsetProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, Vector>(nameof(SyncScrollOffset), Vector.Zero);
public Vector SyncScrollOffset
{
get => GetValue(SyncScrollOffsetProperty);
set => SetValue(SyncScrollOffsetProperty, value);
}
public static readonly StyledProperty<bool> UseSyntaxHighlightingProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, bool>(nameof(UseSyntaxHighlighting), false);
public bool UseSyntaxHighlighting
{
get => GetValue(UseSyntaxHighlightingProperty);
set => SetValue(UseSyntaxHighlightingProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public SingleSideTextDiffPresenter() : base(new TextArea(), new TextDocument()) public SingleSideTextDiffPresenter() : base(new TextArea(), new TextDocument())
{ {
_lineStyleTransformer = new LineStyleTransformer(this); _lineStyleTransformer = new LineStyleTransformer(this);
IsReadOnly = true;
ShowLineNumbers = false;
TextArea.LeftMargins.Add(new LineNumberMargin(this) { Margin = new Thickness(8, 0) }); TextArea.LeftMargins.Add(new LineNumberMargin(this) { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this)); TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this)); TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer); TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
} }
protected override void OnLoaded(RoutedEventArgs e) protected override void OnLoaded(RoutedEventArgs e)
@ -745,12 +664,10 @@ namespace SourceGit.Views
_scrollViewer = this.FindDescendantOfType<ScrollViewer>(); _scrollViewer = this.FindDescendantOfType<ScrollViewer>();
if (_scrollViewer != null) if (_scrollViewer != null)
{ {
_scrollViewer.Offset = SyncScrollOffset;
_scrollViewer.ScrollChanged += OnTextViewScrollChanged; _scrollViewer.ScrollChanged += OnTextViewScrollChanged;
_scrollViewer.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.OneWay));
} }
UpdateTextMate();
TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged; TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged;
TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.ContextRequested += OnTextViewContextRequested;
} }
@ -765,18 +682,31 @@ namespace SourceGit.Views
_scrollViewer = null; _scrollViewer = null;
} }
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
}
TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged; TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged;
TextArea.TextView.ContextRequested -= OnTextViewContextRequested; TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
GC.Collect(); GC.Collect();
} }
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is ViewModels.TwoSideTextDiff diff)
{
var builder = new StringBuilder();
var lines = IsOld ? diff.Old : diff.New;
foreach (var line in lines)
builder.AppendLine(line.Content);
Text = builder.ToString();
}
else
{
Text = string.Empty;
}
}
private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e) private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e)
{ {
if (!TextArea.IsFocused) if (!TextArea.IsFocused)
@ -785,8 +715,8 @@ namespace SourceGit.Views
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e) private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
{ {
if (TextArea.IsFocused) if (TextArea.IsFocused && DataContext is ViewModels.TwoSideTextDiff diff)
SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset); diff.SyncScrollOffset = _scrollViewer.Offset;
} }
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
@ -798,9 +728,7 @@ namespace SourceGit.Views
var menu = new ContextMenu(); var menu = new ContextMenu();
var parentView = this.FindAncestorOfType<TextDiffView>(); var parentView = this.FindAncestorOfType<TextDiffView>();
if (parentView != null) if (parentView != null)
{
parentView.FillContextMenuForWorkingCopyChange(menu, selection.StartPosition.Line, selection.EndPosition.Line, IsOld); parentView.FillContextMenuForWorkingCopyChange(menu, selection.StartPosition.Line, selection.EndPosition.Line, IsOld);
}
var copy = new MenuItem(); var copy = new MenuItem();
copy.Header = App.Text("Copy"); copy.Header = App.Text("Copy");
@ -817,96 +745,11 @@ namespace SourceGit.Views
e.Handled = true; e.Handled = true;
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DiffDataProperty)
{
if (DiffData != null)
{
var builder = new StringBuilder();
if (IsOld)
{
foreach (var line in DiffData.Old)
{
builder.AppendLine(line.Content);
}
}
else
{
foreach (var line in DiffData.New)
{
builder.AppendLine(line.Content);
}
}
Text = builder.ToString();
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
else
{
Text = string.Empty;
}
}
else if (change.Property == SyncScrollOffsetProperty)
{
if (!TextArea.IsFocused && _scrollViewer != null)
_scrollViewer.Offset = SyncScrollOffset;
}
else if (change.Property == UseSyntaxHighlightingProperty)
{
UpdateTextMate();
}
else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null)
{
Models.TextMateHelper.SetThemeByApp(_textMate);
}
}
private void UpdateTextMate()
{
if (UseSyntaxHighlighting)
{
if (_textMate == null)
{
TextArea.TextView.LineTransformers.Remove(_lineStyleTransformer);
_textMate = Models.TextMateHelper.CreateForEditor(this);
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
if (DiffData != null)
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
}
else
{
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
GC.Collect();
TextArea.TextView.Redraw();
}
}
}
private TextMate.Installation _textMate;
private readonly LineStyleTransformer _lineStyleTransformer = null;
private ScrollViewer _scrollViewer = null; private ScrollViewer _scrollViewer = null;
} }
public partial class TextDiffView : UserControl public partial class TextDiffView : UserControl
{ {
public static readonly StyledProperty<Models.TextDiff> TextDiffProperty =
AvaloniaProperty.Register<TextDiffView, Models.TextDiff>(nameof(TextDiff), null);
public Models.TextDiff TextDiff
{
get => GetValue(TextDiffProperty);
set => SetValue(TextDiffProperty, value);
}
public static readonly StyledProperty<bool> UseSideBySideDiffProperty = public static readonly StyledProperty<bool> UseSideBySideDiffProperty =
AvaloniaProperty.Register<TextDiffView, bool>(nameof(UseSideBySideDiff), false); AvaloniaProperty.Register<TextDiffView, bool>(nameof(UseSideBySideDiff), false);
@ -916,13 +759,20 @@ namespace SourceGit.Views
set => SetValue(UseSideBySideDiffProperty, value); set => SetValue(UseSideBySideDiffProperty, value);
} }
public static readonly StyledProperty<Vector> SyncScrollOffsetProperty = static TextDiffView()
AvaloniaProperty.Register<TextDiffView, Vector>(nameof(SyncScrollOffset));
public Vector SyncScrollOffset
{ {
get => GetValue(SyncScrollOffsetProperty); UseSideBySideDiffProperty.Changed.AddClassHandler<TextDiffView>((v, e) =>
set => SetValue(SyncScrollOffsetProperty, value); {
if (v.DataContext is Models.TextDiff diff)
{
diff.SyncScrollOffset = Vector.Zero;
if (v.UseSideBySideDiff)
v.Content = new ViewModels.TwoSideTextDiff(diff);
else
v.Content = diff;
}
});
} }
public TextDiffView() public TextDiffView()
@ -932,6 +782,10 @@ namespace SourceGit.Views
public void FillContextMenuForWorkingCopyChange(ContextMenu menu, int startLine, int endLine, bool isOldSide) public void FillContextMenuForWorkingCopyChange(ContextMenu menu, int startLine, int endLine, bool isOldSide)
{ {
var diff = DataContext as Models.TextDiff;
if (diff == null)
return;
var parentView = this.FindAncestorOfType<DiffView>(); var parentView = this.FindAncestorOfType<DiffView>();
if (parentView == null) if (parentView == null)
return; return;
@ -951,7 +805,7 @@ namespace SourceGit.Views
endLine = tmp; endLine = tmp;
} }
var selection = GetUnifiedSelection(startLine, endLine, isOldSide); var selection = GetUnifiedSelection(diff, startLine, endLine, isOldSide);
if (!selection.HasChanges) if (!selection.HasChanges)
return; return;
@ -1033,17 +887,17 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName(); var tmpFile = Path.GetTempFileName();
if (change.WorkTree == Models.ChangeState.Untracked) if (change.WorkTree == Models.ChangeState.Untracked)
{ {
TextDiff.GenerateNewPatchFromSelection(change, null, selection, false, tmpFile); diff.GenerateNewPatchFromSelection(change, null, selection, false, tmpFile);
} }
else if (!UseSideBySideDiff) else if (!UseSideBySideDiff)
{ {
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result(); var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile); diff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile);
} }
else else
{ {
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result(); var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, isOldSide, tmpFile); diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, isOldSide, tmpFile);
} }
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--cache --index").Exec(); new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--cache --index").Exec();
@ -1065,17 +919,17 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName(); var tmpFile = Path.GetTempFileName();
if (change.WorkTree == Models.ChangeState.Untracked) if (change.WorkTree == Models.ChangeState.Untracked)
{ {
TextDiff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile); diff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile);
} }
else if (!UseSideBySideDiff) else if (!UseSideBySideDiff)
{ {
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result(); var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
} }
else else
{ {
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result(); var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile); diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
} }
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--reverse").Exec(); new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--reverse").Exec();
@ -1103,15 +957,15 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName(); var tmpFile = Path.GetTempFileName();
if (change.Index == Models.ChangeState.Added) if (change.Index == Models.ChangeState.Added)
{ {
TextDiff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile); diff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile);
} }
else if (!UseSideBySideDiff) else if (!UseSideBySideDiff)
{ {
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
} }
else else
{ {
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile); diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
} }
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--cache --index --reverse").Exec(); new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--cache --index --reverse").Exec();
@ -1133,17 +987,17 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName(); var tmpFile = Path.GetTempFileName();
if (change.WorkTree == Models.ChangeState.Untracked) if (change.WorkTree == Models.ChangeState.Untracked)
{ {
TextDiff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile); diff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile);
} }
else if (!UseSideBySideDiff) else if (!UseSideBySideDiff)
{ {
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result(); var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
} }
else else
{ {
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result(); var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile); diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
} }
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--index --reverse").Exec(); new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--index --reverse").Exec();
@ -1162,44 +1016,29 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(new MenuItem() { Header = "-" });
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnDataContextChanged(EventArgs e)
{ {
base.OnPropertyChanged(change); base.OnDataContextChanged(e);
var data = TextDiff; var diff = DataContext as Models.TextDiff;
if (data == null) if (diff == null)
{ {
Content = null; Content = null;
SyncScrollOffset = Vector.Zero; GC.Collect();
return; return;
} }
if (change.Property == TextDiffProperty)
{
if (UseSideBySideDiff) if (UseSideBySideDiff)
Content = new ViewModels.TwoSideTextDiff(TextDiff); Content = new ViewModels.TwoSideTextDiff(diff, Content as ViewModels.TwoSideTextDiff);
else else
Content = TextDiff; Content = diff;
SetCurrentValue(SyncScrollOffsetProperty, TextDiff.SyncScrollOffset);
}
else if (change.Property == UseSideBySideDiffProperty)
{
if (UseSideBySideDiff)
Content = new ViewModels.TwoSideTextDiff(TextDiff);
else
Content = TextDiff;
SetCurrentValue(SyncScrollOffsetProperty, Vector.Zero);
}
} }
private Models.TextDiffSelection GetUnifiedSelection(int startLine, int endLine, bool isOldSide) private Models.TextDiffSelection GetUnifiedSelection(Models.TextDiff diff, int startLine, int endLine, bool isOldSide)
{ {
var rs = new Models.TextDiffSelection(); var rs = new Models.TextDiffSelection();
var diff = TextDiff;
endLine = Math.Min(endLine, TextDiff.Lines.Count); endLine = Math.Min(endLine, diff.Lines.Count);
if (Content is ViewModels.TwoSideTextDiff twoSides) if (Content is ViewModels.TwoSideTextDiff twoSides)
{ {
var target = isOldSide ? twoSides.Old : twoSides.New; var target = isOldSide ? twoSides.Old : twoSides.New;
@ -1233,8 +1072,8 @@ namespace SourceGit.Views
var firstContent = target[firstContentLine]; var firstContent = target[firstContentLine];
var endContent = target[endContentLine]; var endContent = target[endContentLine];
startLine = TextDiff.Lines.IndexOf(firstContent) + 1; startLine = diff.Lines.IndexOf(firstContent) + 1;
endLine = TextDiff.Lines.IndexOf(endContent) + 1; endLine = diff.Lines.IndexOf(endContent) + 1;
} }
rs.StartLine = startLine; rs.StartLine = startLine;