Skip to content

Commit

Permalink
Manual conflict resolution (WIP)
Browse files Browse the repository at this point in the history
--> references #37
  • Loading branch information
Mikescher committed May 23, 2018
1 parent e718600 commit 6820417
Show file tree
Hide file tree
Showing 37 changed files with 834 additions and 82 deletions.
77 changes: 77 additions & 0 deletions Source/AlephNote.App/AlephNote.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,16 @@
<ApplicationIcon>IconMain.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="DiffPlex, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1d35e91d1bd7bc0f, processorArchitecture=MSIL">
<HintPath>..\..\Lib\DiffPlex.1.4.1\lib\netstandard1.0\DiffPlex.dll</HintPath>
</Reference>
<Reference Include="Hardcodet.Wpf.TaskbarNotification, Version=1.0.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\Lib\Hardcodet.NotifyIcon.Wpf.1.0.8\lib\net451\Hardcodet.Wpf.TaskbarNotification.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\Lib\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
Expand All @@ -57,9 +63,59 @@
<HintPath>..\..\Lib\jacobslusser.ScintillaNET.3.6.3\lib\net40\ScintillaNET.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.AppContext, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.AppContext.4.3.0\lib\net46\System.AppContext.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath>
</Reference>
<Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
Expand All @@ -69,6 +125,9 @@
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\Lib\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
Expand Down Expand Up @@ -99,6 +158,9 @@
<Compile Include="WPF\Controls\ConnectionDisplayControl.xaml.cs">
<DependentUpon>ConnectionDisplayControl.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\Controls\DiffViewer.xaml.cs">
<DependentUpon>DiffViewer.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\Controls\GlobalSearchTextBox.xaml.cs">
<DependentUpon>GlobalSearchTextBox.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -142,6 +204,13 @@
</Compile>
<Compile Include="WPF\Extensions\ThemeSlaveBinding.cs" />
<Compile Include="WPF\Util\CheckableTag.cs" />
<Compile Include="WPF\Util\SideBySideDiffModelVisualizer.cs" />
<Compile Include="WPF\Windows\ConflictWindow.xaml.cs">
<DependentUpon>ConflictWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\Windows\ConflictWindowViewmodel.cs">
<DependentUpon>ConflictWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\Windows\ThemeEditor.xaml.cs">
<DependentUpon>ThemeEditor.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -266,6 +335,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WPF\Controls\DiffViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WPF\Controls\FontBox.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
Expand Down Expand Up @@ -306,6 +379,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WPF\Windows\ConflictWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WPF\Windows\ThemeEditor.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
31 changes: 31 additions & 0 deletions Source/AlephNote.App/WPF/Controls/DiffViewer.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<UserControl x:Class="AlephNote.WPF.Controls.DiffViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:util="clr-namespace:AlephNote.WPF.Util"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="DiffRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>

<GridSplitter Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="LightGray" ShowsPreview="true" Width="5" />

<RichTextBox Grid.Column="0" x:Name="leftDiff"
util:SideBySideDiffModelVisualizer.Left="{Binding Diff}" FontFamily="Consolas"
AcceptsReturn="True" IsReadOnly="True"
VerticalScrollBarVisibility="Visible" ScrollViewer.ScrollChanged="ScrollChanged"/>

<RichTextBox Grid.Column="2" x:Name="rightDiff"
util:SideBySideDiffModelVisualizer.Right="{Binding Diff}" FontFamily="Consolas"
AcceptsReturn="True" IsReadOnly="True"
VerticalScrollBarVisibility="Visible" ScrollViewer.ScrollChanged="ScrollChanged" />

</Grid>
</UserControl>
105 changes: 105 additions & 0 deletions Source/AlephNote.App/WPF/Controls/DiffViewer.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using DiffPlex;
using DiffPlex.DiffBuilder;
using DiffPlex.DiffBuilder.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace AlephNote.WPF.Controls
{
/// <summary>
/// Interaction logic for DiffViewer.xaml
/// </summary>
public partial class DiffViewer : UserControl, INotifyPropertyChanged
{
#region INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

private void OnExplicitPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

#endregion

public static readonly DependencyProperty TextLeftProperty =
DependencyProperty.Register(
"TextLeft",
typeof(string),
typeof(DiffViewer),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.None, (o,e) => { ((DiffViewer)o).OnTextChanged(e); }));

public static readonly DependencyProperty TextRightProperty =
DependencyProperty.Register(
"TextRight",
typeof(string),
typeof(DiffViewer),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.None, (o,e) => { ((DiffViewer)o).OnTextChanged(e); }));

public string TextLeft
{
get => (string)GetValue(TextLeftProperty);
set => SetValue(TextLeftProperty, value);
}

public string TextRight
{
get => (string)GetValue(TextRightProperty);
set => SetValue(TextRightProperty, value);
}

private SideBySideDiffModel _diff;
public SideBySideDiffModel Diff
{
get => _diff;
private set { _diff = value; OnPropertyChanged(); }
}

public DiffViewer()
{
InitializeComponent();
Compare();

DiffRoot.DataContext = this;
}

private void OnTextChanged(DependencyPropertyChangedEventArgs e)
{
Compare();
}

private void Compare()
{
var diffBuilder = new SideBySideDiffBuilder(new Differ());
Diff = diffBuilder.BuildDiffModel(TextLeft ?? string.Empty, TextRight ?? string.Empty);
}

private void ScrollChanged(object sender, ScrollChangedEventArgs e)
{
leftDiff.ScrollToVerticalOffset(e.VerticalOffset);
leftDiff.ScrollToHorizontalOffset(e.HorizontalOffset);

rightDiff.ScrollToVerticalOffset(e.VerticalOffset);
rightDiff.ScrollToHorizontalOffset(e.HorizontalOffset);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ private void OnAllNotesCollectionChanged(object sender, NotifyCollectionChangedE

private void ResyncDisplayItems(IList<INote> notes)
{
App.Logger.Trace("NotesViewHierachical", "ResyncDisplayItems", string.Join("\n", notes.Select(n => $"{n.GetUniqueName()} {n.Title}")));
App.Logger.Trace("NotesViewHierachical", "ResyncDisplayItems", string.Join("\n", notes.Select(n => $"{n.UniqueName} {n.Title}")));

HierachicalWrapper_Folder root = new HierachicalWrapper_Folder("ROOT", this, DirectoryPath.Root(), true, false);
if (Settings?.FolderViewShowRootNode == true)
Expand Down
105 changes: 105 additions & 0 deletions Source/AlephNote.App/WPF/Util/SideBySideDiffModelVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using DiffPlex.DiffBuilder.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace AlephNote.WPF.Util
{
public class SideBySideDiffModelVisualizer
{
public static SideBySideDiffModel GetLeft(DependencyObject obj) { return (SideBySideDiffModel)obj.GetValue(LeftProperty); }
public static void SetLeft(DependencyObject obj, SideBySideDiffModel value) { obj.SetValue(LeftProperty, value); }
public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(SideBySideDiffModel), typeof(SideBySideDiffModelVisualizer), new PropertyMetadata(null, new PropertyChangedCallback(LeftChanged)));
private static void LeftChanged(DependencyObject dep, DependencyPropertyChangedEventArgs e)
{
var richTextBox = (RichTextBox)dep;
var diff = (SideBySideDiffModel)e.NewValue;

var zippedDiffs = Enumerable.Zip(diff.OldText.Lines, diff.NewText.Lines, (oldLine, newLine) => new OldNew<DiffPiece> { Old = oldLine, New = newLine }).ToList();
ShowDiffs(richTextBox, zippedDiffs, line => line.Old, piece => piece.Old);
}

public static SideBySideDiffModel GetRight(DependencyObject obj) { return (SideBySideDiffModel)obj.GetValue(RightProperty); }
public static void SetRight(DependencyObject obj, SideBySideDiffModel value) { obj.SetValue(RightProperty, value); }
public static readonly DependencyProperty RightProperty = DependencyProperty.RegisterAttached("Right", typeof(SideBySideDiffModel), typeof(SideBySideDiffModelVisualizer), new PropertyMetadata(null, new PropertyChangedCallback(RightChanged)));
private static void RightChanged(DependencyObject dep, DependencyPropertyChangedEventArgs e)
{
var richTextBox = (RichTextBox)dep;
var diff = (SideBySideDiffModel)e.NewValue;

var zippedDiffs = Enumerable.Zip(diff.OldText.Lines, diff.NewText.Lines, (oldLine, newLine) => new OldNew<DiffPiece> { Old = oldLine, New = newLine }).ToList();
ShowDiffs(richTextBox, zippedDiffs, line => line.New, piece => piece.New);
}

private static void ShowDiffs(RichTextBox diffBox, List<OldNew<DiffPiece>> lines, Func<OldNew<DiffPiece>, DiffPiece> lineSelector, Func<OldNew<DiffPiece>, DiffPiece> pieceSelector)
{
diffBox.Document.Blocks.Clear();
foreach (var line in lines)
{
var synchroLineLength = Math.Max(line.Old.Text?.Length ?? 0, line.New.Text?.Length ?? 0);
var lineSubPieces = Enumerable.Zip(line.Old.SubPieces, line.New.SubPieces, (oldPiece, newPiece) => new OldNew<DiffPiece> { Old = oldPiece, New = newPiece, Length = Math.Max(oldPiece.Text?.Length ?? 0, newPiece.Text?.Length ?? 0) });

var oldNewLine = lineSelector(line);
switch (oldNewLine.Type)
{
case ChangeType.Unchanged: AppendParagraph(diffBox, oldNewLine.Text ?? string.Empty); break;
case ChangeType.Imaginary: AppendParagraph(diffBox, new string(BreakingSpace, synchroLineLength), Brushes.Gray, Brushes.LightCyan); break;
case ChangeType.Inserted: AppendParagraph(diffBox, oldNewLine.Text ?? string.Empty, Brushes.LightGreen); break;
case ChangeType.Deleted: AppendParagraph(diffBox, oldNewLine.Text ?? string.Empty, Brushes.OrangeRed); break;
case ChangeType.Modified:
var paragraph = AppendParagraph(diffBox, string.Empty);
foreach (var subPiece in lineSubPieces)
{
var oldNewPiece = pieceSelector(subPiece);
switch (oldNewPiece.Type)
{
case ChangeType.Unchanged: paragraph.Inlines.Add(NewRun(oldNewPiece.Text ?? string.Empty, Brushes.Yellow)); break;
case ChangeType.Imaginary: paragraph.Inlines.Add(NewRun(oldNewPiece.Text ?? string.Empty)); break;
case ChangeType.Inserted: paragraph.Inlines.Add(NewRun(oldNewPiece.Text ?? string.Empty, Brushes.LightGreen)); break;
case ChangeType.Deleted: paragraph.Inlines.Add(NewRun(oldNewPiece.Text ?? string.Empty, Brushes.OrangeRed)); break;
case ChangeType.Modified: paragraph.Inlines.Add(NewRun(oldNewPiece.Text ?? string.Empty, Brushes.Yellow)); break;
default: throw new ArgumentException();
}
paragraph.Inlines.Add(NewRun(new string(BreakingSpace, subPiece.Length - (oldNewPiece.Text ?? string.Empty).Length), Brushes.Gray, Brushes.LightCyan));
}
break;
default: throw new ArgumentException();
}
}
}

private class OldNew<T>
{
public T Old { get; set; }
public T New { get; set; }
public int Length { get; set; }
}

private static char BreakingSpace = '\u2219';

private static Paragraph AppendParagraph(RichTextBox textBox, string text, Brush background = null, Brush foreground = null)
{
var paragraph = new Paragraph(new Run(text))
{
LineHeight = 0.5,
Background = background ?? Brushes.Transparent,
Foreground = foreground ?? Brushes.Black,
BorderBrush = Brushes.DarkGray,
BorderThickness = new Thickness(0, 0, 0, 1),
};

textBox.Document.Blocks.Add(paragraph);
return paragraph;
}

private static Run NewRun(string text, Brush background = null, Brush foreground = null) => new Run(text)
{
Background = background ?? Brushes.Transparent,
Foreground = foreground ?? Brushes.Black,
};
}
}
Loading

0 comments on commit 6820417

Please sign in to comment.