#region License /* MIT License Copyright(c) 2020 Petteri Kautonen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Text; using System.Windows.Forms; using DiffPlex; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; using DiffPlex.Model; using ScintillaDiff.Enumerations; using ScintillaDiff.UtilityClasses; using ScintillaNET; using static ScintillaDiff.ScintillaDiffStyles; namespace ScintillaDiff { /// /// A control for comparing two text files using controls. /// Implements the /// /// public partial class ScintillaDiffControl : UserControl { /// /// Initializes a new instance of the class. /// public ScintillaDiffControl() { InitializeComponent(); if(!this.DesignMode) { // // scintillaOne // scintillaOne = new ScrollSyncScintilla(); this.scintillaOne.AutoCMaxHeight = 9; this.scintillaOne.BiDirectionality = ScintillaNET.BiDirectionalDisplayType.Disabled; this.scintillaOne.CaretLineBackColor = System.Drawing.Color.White; this.scintillaOne.CaretLineVisible = true; this.scintillaOne.Dock = System.Windows.Forms.DockStyle.Fill; this.scintillaOne.LexerName = null; this.scintillaOne.Location = new System.Drawing.Point(0, 23); this.scintillaOne.ScrollSync = this.scintillaTwo; this.scintillaOne.ScrollWidth = 1; this.scintillaOne.Size = new System.Drawing.Size(434, 431); this.scintillaOne.TabIndents = true; this.scintillaOne.TabIndex = 0; this.scintillaOne.UseRightToLeftReadingLayout = false; this.scintillaOne.WrapMode = ScintillaNET.WrapMode.None; this.scintillaOne.TextChanged += new System.EventHandler(this.Scintilla_TextChanged); // // scintillaTwo // scintillaTwo = new ScrollSyncScintilla(); this.scintillaTwo.AutoCMaxHeight = 9; this.scintillaTwo.BiDirectionality = ScintillaNET.BiDirectionalDisplayType.Disabled; this.scintillaTwo.CaretLineBackColor = System.Drawing.Color.White; this.scintillaTwo.CaretLineVisible = true; this.scintillaTwo.Dock = System.Windows.Forms.DockStyle.Fill; this.scintillaTwo.LexerName = null; this.scintillaTwo.Location = new System.Drawing.Point(0, 23); this.scintillaTwo.ScrollSync = this.scintillaOne; this.scintillaTwo.ScrollWidth = 1; this.scintillaTwo.Size = new System.Drawing.Size(480, 431); this.scintillaTwo.TabIndents = true; this.scintillaTwo.TabIndex = 0; this.scintillaTwo.UseRightToLeftReadingLayout = false; this.scintillaTwo.WrapMode = ScintillaNET.WrapMode.None; this.scintillaTwo.TextChanged += new System.EventHandler(this.Scintilla_TextChanged); this.scMain.Panel1.Controls.Add(scintillaOne); this.scMain.Panel1.Controls.SetChildIndex(scintillaOne, 0); this.scMain.Panel2.Controls.Add(scintillaTwo); this.scMain.Panel2.Controls.SetChildIndex(scintillaTwo, 0); scintillaOne.Tag = -1; scintillaTwo.Tag = -1; ReInit(); } // this tag values are for line number length counting.. } public void ReInit() { SetSymbolMasks(); InitScintillaMargins(); SetSymbols(); SetLineBackgroundColors(); } #region PrivateEvents // if the control size has changed, set the splitter to the middle.. private void ScintillaDiffer_SizeChanged(object sender, EventArgs e) { RecalculateSize(); } // ReSharper disable once CommentTypo #region https://github.com/jacobslusser/ScintillaNET/wiki/Displaying-Line-Numbers private void Scintilla_TextChanged(object sender, EventArgs e) { Scintilla scintilla = (Scintilla) sender; int maxLineNumberCharLengthFromTag = (int) scintilla.Tag; // ReSharper disable once CommentTypo // Did the number of characters in the line number display change? // i.e. nnn VS nn, or nnnn VS nn, etc... var maxLineNumberCharLength = scintilla.Lines.Count.ToString().Length; if (maxLineNumberCharLength == maxLineNumberCharLengthFromTag) return; // Calculate the width required to display the last line number // and include some padding for good measure. const int padding = 2; scintilla.Margins[0].Width = scintilla.TextWidth(Style.LineNumber, new string('9', maxLineNumberCharLength + 1)) + padding; scintilla.Tag = maxLineNumberCharLength; } #endregion #endregion #region PrivateFields private ScrollSyncScintilla scintillaOne; private ScrollSyncScintilla scintillaTwo; private string textLeft = string.Empty; private string textRight = string.Empty; private Bitmap imageRowAdded = RySmartEditor.Properties.Resources.plus; private Bitmap imageRowDeleted = RySmartEditor.Properties.Resources.minus; private Bitmap imageRowOk = RySmartEditor.Properties.Resources.ok; private Bitmap imageRowDiff = RySmartEditor.Properties.Resources.diff; private int imageRowAddedScintillaIndex = 28; private int imageRowDeletedScintillaIndex = 29; private int imageRowOkScintillaIndex = 30; private int imageRowDiffScintillaIndex = 31; private int markColorCharacterChanged = 27; private int markColorCharacterRemoved = 28; private int markColorCharacterAdded = 29; private int markColorIndexRemovedOrAdded = 30; private int markColorIndexModifiedBackground = 31; private bool useRowOkSign; private DiffStyle diffStyle = DiffStyle.DiffList; private Color diffColorDeleted = Color.FromArgb(0xFF, 0XFF, 0XB2, 0XB2); private Color diffColorAdded = Color.FromArgb(0xFF, 0XD4, 0XF2, 0XC4); private Color diffColorCharDeleted = Color.FromArgb(0xFF, 0XE1, 0X7D, 0X7D); private Color diffColorCharAdded = Color.FromArgb(0xFF, 0X9A, 0XEA, 0X6F); private Color diffColorChangeBackground = Color.FromArgb(0xFF, 0XFC, 0XFF, 0X8C); private int diffIndex; private readonly StringBuilder builderLeft = new StringBuilder(); private readonly StringBuilder builderRight = new StringBuilder(); private bool characterComparison; private bool characterComparisonMarkAddRemove; #endregion #region PublicProperties /// /// Gets the left control. /// [Browsable(false)] // ReSharper disable once ConvertToAutoPropertyWhenPossible // ReSharper disable once UnusedMember.Global public Scintilla LeftScintilla => scintillaOne; /// /// Gets the value indicating whether a navigation to the previous difference is possible. (). /// [Browsable(false)] public bool CanGoPrevious => diffIndex > 0 && DiffLocations.Count > 0; /// /// Gets the value indicating whether a navigation to the next difference is possible. (). /// [Browsable(false)] public bool CanGoNext => diffIndex + 1 < DiffLocations.Count; /// /// Gets the right control. /// [Browsable(false)] // ReSharper disable once ConvertToAutoPropertyWhenPossible // ReSharper disable once UnusedMember.Global public Scintilla RightScintilla => scintillaTwo; /// /// Gets the difference locations found by the class. /// [Browsable(false)] public List DiffLocations { get; internal set; } = new List(); /// /// Gets a value indicating whether the two compared texts differs from each-other. /// // ReSharper disable once UnusedMember.Global [Browsable(false)] public bool IsMatch => textLeft.Equals(textRight); /// /// Gets or sets the value indicating whether the entire line of a change should be highlighted, or just the text within that line. /// [Browsable(false)] public bool IsEntireLineHighlighted { get; set; } = false; /// /// Gets or sets the value indicating whether the entire line of a change should be highlighted, or just the text within that line. /// [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] // ReSharper disable once IdentifierTypo :: this is left behind to maintain backwards compatibility.. // ReSharper disable once UnusedMember.Global :: this is left behind to maintain backwards compatibility.. public bool IsEntireLineHighligted => IsEntireLineHighlighted; // don't #endregion #region PublicEvents /// /// A delegate for the event. /// /// The sender. /// The instance containing the event data. public delegate void OnExternalStyleNeeded(object sender, StyleRefreshEventArgs e); /// /// Occurs when external styling is needed to keep the document style up to date /// (i.e. a class property change has caused the diff to update thus clearing the document's style). /// public event OnExternalStyleNeeded ExternalStyleNeeded; #endregion #region PublicVisualProperties /// /// Gets or sets a value indicating whether to use character comparison on lines. /// /// true if to use character comparison on lines; otherwise, false. [Browsable(true)] [Category("Behaviour")] [Description("Gets or sets a value indicating whether to use character comparison on lines.")] public bool CharacterComparison { get => characterComparison; set { if (value != characterComparison) { characterComparison = value; DiffTexts(); } } } /// /// Gets or sets a value indicating whether the character comparison should mark added and removed characters. /// /// true if the character comparison should mark added and removed characters; otherwise, false. [Browsable(true)] [Category("Behaviour")] [Description("Gets or sets a value indicating whether the character comparison should mark added and removed characters.")] public bool CharacterComparisonMarkAddRemove { get => characterComparisonMarkAddRemove; set { if (value != characterComparisonMarkAddRemove) { characterComparisonMarkAddRemove = value; DiffTexts(); } } } /// /// Gets or sets the symbol for a removed character. /// /// The removed character symbol. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the symbol for a removed character.")] public char RemovedCharacterSymbol { get; set; } = '-'; /// /// Gets or sets the symbol for an added character. /// /// The added character symbol. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the symbol for an added character.")] public char AddedCharacterSymbol { get; set; } = '+'; /// /// Gets or sets the index for the style for a mark color used by the control to indicate a addition or a deletion difference. /// /// The value must be between 0 and 31. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the index for the style index for a mark color used by the Scintilla control to indicate a addition or a deletion difference.")] public int MarkColorIndexModifiedBackground { get => markColorIndexModifiedBackground; set { if (value != markColorIndexModifiedBackground) { if (value < 0 || value > 31) { // ReSharper disable once LocalizableElement throw new ArgumentOutOfRangeException(nameof(value), "The value must be between 0 and 31."); } markColorIndexModifiedBackground = value; DiffTexts(); } } } /// /// Gets or sets the index for the style for a background color color used by the control to indicate a change in file line. /// /// The value must be between 0 and 31. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the index for the style for a background color color used by the Scintilla control to indicate a change in file line.")] public int MarkColorIndexRemovedOrAdded { get => markColorIndexRemovedOrAdded; set { if (value != markColorIndexRemovedOrAdded) { if (value < 0 || value > 31) { // ReSharper disable once LocalizableElement throw new ArgumentOutOfRangeException(nameof(value), "The value must be between 0 and 31."); } markColorIndexRemovedOrAdded = value; DiffTexts(); } } } /// /// Gets or sets a value indicating whether to mark unchanged lines with an ok sign. /// [Browsable(true)] [Category("Appearance")] [Description("Gets or sets a value indicating whether to mark unchanged lines with an ok sign.")] public bool UseRowOkSign { get => useRowOkSign; set { if (useRowOkSign != value) { useRowOkSign = value; DiffTexts(); } } } /// /// Gets or sets a diff style of the control. /// [Browsable(true)] [Category("Appearance")] [Description("Gets or sets a diff style of the control.")] public DiffStyle DiffStyle { get => diffStyle; set { if (diffStyle != value) { diffStyle = value; // don't synchronize the scroll bars with list view.. if (diffStyle == DiffStyle.DiffList) { scintillaOne.ScrollSync = null; scintillaTwo.ScrollSync = null; } else { // synchronize the scroll bars with side-by-side view.. scintillaOne.ScrollSync = scintillaTwo; scintillaTwo.ScrollSync = scintillaOne; } DiffTexts(); } } } /// /// Gets or sets the indicator for the diff that a row was added. /// [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the indicator for the diff that a row was added.")] public Bitmap ImageRowAdded { get => imageRowAdded; set { if (value != imageRowAdded && value != null) { imageRowAdded = value; SetSymbols(); } } } /// /// Gets or sets the index for the used by the control. /// /// The value must be between 0 and 31. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the index for the ImageRowAdded used by the Scintilla control.")] public int ImageRowAddedScintillaIndex { get => imageRowAddedScintillaIndex; set { if (value != imageRowAddedScintillaIndex) { if (value < 0 || value > 31) { // ReSharper disable once LocalizableElement throw new ArgumentOutOfRangeException(nameof(value), "The value must be between 0 and 31."); } imageRowAddedScintillaIndex = value; SetSymbolMasks(); } } } /// /// Gets or sets the indicator for the diff that a row was deleted. /// [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the indicator for the diff that a row was deleted.")] public Bitmap ImageRowDeleted { get => imageRowDeleted; set { if (value != imageRowDeleted && value != null) { imageRowDeleted = value; SetSymbols(); } } } /// /// Gets or sets the index for the used by the control. /// /// The value must be between 0 and 31. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the index for the ImageRowDeleted used by the Scintilla control.")] public int ImageRowDeletedScintillaIndex { get => imageRowDeletedScintillaIndex; set { if (value != imageRowDeletedScintillaIndex) { if (value < 0 || value > 31) { // ReSharper disable once LocalizableElement throw new ArgumentOutOfRangeException(nameof(value), "The value must be between 0 and 31."); } imageRowDeletedScintillaIndex = value; SetSymbolMasks(); } } } /// /// Gets or sets the indicator for the diff that a row hasn't changed. /// [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the indicator for the diff that a row hasn't changed.")] public Bitmap ImageRowOk { get => imageRowOk; set { if (value != imageRowOk && value != null) { imageRowOk = value; SetSymbols(); } } } /// /// Gets or sets the index for the used by the control. /// /// The value must be between 0 and 31. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the index for the ImageRowOk used by the Scintilla control.")] public int ImageRowOkScintillaIndex { get => imageRowOkScintillaIndex; set { if (value != imageRowOkScintillaIndex) { if (value < 0 || value > 31) { // ReSharper disable once LocalizableElement throw new ArgumentOutOfRangeException(nameof(value), "The value must be between 0 and 31."); } imageRowOkScintillaIndex = value; SetSymbolMasks(); } } } /// /// Gets or sets the indicator for the diff that two rows have some differences. /// [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the indicator for the diff that two rows have some differences.")] public Bitmap ImageRowDiff { get => imageRowDiff; set { if (value != imageRowDiff && value != null) { imageRowDiff = value; SetSymbols(); } } } /// /// Gets or sets the index for the used by the control. /// /// The value must be between 0 and 31. [Browsable(true)] [Category("Appearance")] [Description("Gets or sets the index for the ImageRowDiff used by the Scintilla control.")] public int ImageRowDiffScintillaIndex { get => imageRowDiffScintillaIndex; set { if (value != imageRowDiffScintillaIndex) { if (value < 0 || value > 31) { // ReSharper disable once LocalizableElement throw new ArgumentOutOfRangeException(nameof(value), "The value must be between 0 and 31."); } imageRowDiffScintillaIndex = value; SetSymbolMasks(); } } } /// /// Gets or sets the text on the left control. /// [Browsable(true)] [Category("Diff")] [Description("Gets or sets the text on the left Scintilla control.")] public string TextLeft { get => textLeft; set { textLeft = value; DiffTexts(); } } /// /// 获取或修改左边区域的标题 /// [Browsable(true)] [Category("Diff")] [Description("获取或修改左边区域的标题")] public string TitleLeft { get => label1.Text; set { label1.Text = value; } } /// /// 获取或修改右边区域的标题 /// [Browsable(true)] [Category("Diff")] [Description("获取或修改右边区域的标题")] public string TitleRight { get => label2.Text; set { label2.Text = value; } } /// /// 获取或修改标题是否可见 /// [Browsable(true)] [Category("Diff")] [Description("获取或修改标题是否可见")] public bool TitleVisible { get => panel1.Visible; set { panel1.Visible = panel2.Visible= value; } } /// /// Gets or sets the text on the right control. /// [Browsable(true)] [Category("Diff")] [Description("Gets or sets the text on the right Scintilla control.")] public string TextRight { get => textRight; set { textRight = value; DiffTexts(); } } /// /// Gets or sets the text deleted color for the control. /// [Browsable(true)] [Category("Diff")] [Description("Gets or sets the text deleted color for the Scintilla control.")] public Color DiffColorDeleted { get => diffColorDeleted; set { if (value != diffColorDeleted) { diffColorDeleted = value; DiffTexts(); } } } /// /// Gets or sets the text inserted color for the control. /// [Browsable(true)] [Category("Diff")] [Description("Gets or sets the text inserted color for the Scintilla control.")] public Color DiffColorAdded { get => diffColorAdded; set { if (value != diffColorAdded) { diffColorAdded = value; DiffTexts(); } } } /// /// Gets or sets the text deleted color for the control. /// [Browsable(true)] [Category("Diff")] [Description("Gets or sets the character deleted color for the Scintilla control.")] public Color DiffColorCharDeleted { get => diffColorCharDeleted; set { if (value != diffColorCharDeleted) { diffColorCharDeleted = value; DiffTexts(); } } } /// /// Gets or sets the text inserted color for the control. /// [Browsable(true)] [Category("Diff")] [Description("Gets or sets the character inserted color for the Scintilla control.")] public Color DiffColorCharAdded { get => diffColorCharAdded; set { if (value != diffColorCharAdded) { diffColorCharAdded = value; DiffTexts(); } } } /// /// Gets or sets the background color for a changed text row for the control. /// [Browsable(true)] [Category("Diff")] [Description("Gets or sets the background color for a changed text row for the Scintilla control.")] public Color DiffColorChangeBackground { get => diffColorChangeBackground; set { if (value != diffColorChangeBackground) { diffColorChangeBackground = value; DiffTexts(); } } } #endregion #region PrivateProperties #endregion #region PrivateMethods /// /// Re-calculates the split container's () splitter position. /// private void RecalculateSize() { int size = scMain.ClientSize.Width - scMain.SplitterWidth; if (size >= 0) scMain.SplitterDistance = size / 2; } /// /// Sets the margin styles of both the controls. /// private void InitScintillaMargins() { scintillaOne.Margins[0].Type = MarginType.Number; scintillaTwo.Margins[0].Type = MarginType.Number; scintillaOne.Margins[1].Width = 25; scintillaOne.Margins[1].Type = MarginType.Symbol; scintillaTwo.Margins[1].Width = 25; scintillaTwo.Margins[1].Type = MarginType.Symbol; } /// /// Sets the bit masks to the second margin symbols. /// public void SetSymbolMasks() { // the Scintilla does like this bit masks or "bitmaps".. scintillaOne.Margins[1].Mask = GetScintillaSymbolIndex(imageRowAddedScintillaIndex) | GetScintillaSymbolIndex(imageRowDeletedScintillaIndex) | GetScintillaSymbolIndex(imageRowDiffScintillaIndex) | GetScintillaSymbolIndex(imageRowOkScintillaIndex); scintillaTwo.Margins[1].Mask = scintillaOne.Margins[1].Mask; } /// /// Shifts value of one with the given amount to the left. /// /// The amount of how much to shift number one to the left. /// An unsigned integer containing the value shifted to the left. uint GetScintillaSymbolIndex(int amount) { return (uint) 1 << amount; } /// /// Sets the symbol images for both the left and right controls. /// public void SetSymbols() { // the plus-symbol.. scintillaOne.Markers[imageRowAddedScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaOne.Markers[imageRowAddedScintillaIndex].DefineRgbaImage(imageRowAdded); // the minus-symbol.. scintillaOne.Markers[imageRowDeletedScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaOne.Markers[imageRowDeletedScintillaIndex].DefineRgbaImage(imageRowDeleted); // the plus-symbol.. scintillaTwo.Markers[imageRowAddedScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaTwo.Markers[imageRowAddedScintillaIndex].DefineRgbaImage(imageRowAdded); // the minus-symbol.. scintillaTwo.Markers[imageRowDeletedScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaTwo.Markers[imageRowDeletedScintillaIndex].DefineRgbaImage(imageRowDeleted); // the row ok symbol.. scintillaOne.Markers[imageRowOkScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaOne.Markers[imageRowOkScintillaIndex].DefineRgbaImage(imageRowOk); // the row ok symbol.. scintillaTwo.Markers[imageRowOkScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaTwo.Markers[imageRowOkScintillaIndex].DefineRgbaImage(imageRowOk); // the row diff symbol.. scintillaOne.Markers[imageRowDiffScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaOne.Markers[imageRowDiffScintillaIndex].DefineRgbaImage(imageRowDiff); // the row diff symbol.. scintillaTwo.Markers[imageRowDiffScintillaIndex].Symbol = MarkerSymbol.RgbaImage; scintillaTwo.Markers[imageRowDiffScintillaIndex].DefineRgbaImage(imageRowDiff); } /// /// Sets the colors for the background of lines, depending on change type. /// public void SetLineBackgroundColors() { int deleted = (int)ChangeType.Deleted; int inserted = (int)ChangeType.Inserted; int modified = (int)ChangeType.Modified; scintillaOne.Styles[deleted].FillLine = true; scintillaOne.Styles[deleted].BackColor = DiffColorDeleted; scintillaOne.Styles[inserted].FillLine = true; scintillaOne.Styles[inserted].BackColor = DiffColorAdded; scintillaOne.Styles[modified].FillLine = true; scintillaOne.Styles[modified].BackColor = DiffColorChangeBackground; scintillaTwo.Styles[deleted].FillLine = true; scintillaTwo.Styles[deleted].BackColor = DiffColorDeleted; scintillaTwo.Styles[inserted].FillLine = true; scintillaTwo.Styles[inserted].BackColor = DiffColorAdded; scintillaTwo.Styles[modified].FillLine = true; scintillaTwo.Styles[modified].BackColor = DiffColorChangeBackground; } /// /// Appends a row to the left control. /// /// The text to append to the left document row. private void AppendRowAdded(string rowText) { builderLeft.AppendLine(rowText); // scintillaOne.Text += rowText + Environment.NewLine; } /// /// Sets a row added marker based on the given row amount. /// /// The index of the row to set the marker to. private void AppendRowAddedMarker(int index) { scintillaOne.Lines[index].MarkerAdd(imageRowAddedScintillaIndex); } /// /// Sets a row added marker based on the given row amount to either the left side or the right side document. /// /// The index of the row to set the marker to. /// A value indicating whether to append the marker to the left or to the right side document. private void AppendRowAddedMarker(int index, bool left) { if (left) { scintillaOne.Lines[index].MarkerAdd(imageRowAddedScintillaIndex); } else { scintillaTwo.Lines[index].MarkerAdd(imageRowAddedScintillaIndex); } } /// /// Appends a row to the left control. /// /// The text to append to the right document row. private void AppendRowDeleted(string rowText) { builderLeft.AppendLine(rowText); //scintillaOne.Text += rowText + Environment.NewLine; } /// /// Sets a row deleted marker based on the given row amount. /// /// The index of the row to set the marker to. private void AppendRowDeletedMarker(int index) { scintillaOne.Lines[index].MarkerAdd(imageRowDeletedScintillaIndex); } /// /// Sets a row deleted marker based on the given row amount to either the left side or the right side document. /// /// The index of the row to set the marker to. /// A value indicating whether to append the marker to the left or to the right side document. private void AppendRowDeletedMarker(int index, bool left) { if (left) { scintillaOne.Lines[index].MarkerAdd(imageRowDeletedScintillaIndex); } else { scintillaTwo.Lines[index].MarkerAdd(imageRowDeletedScintillaIndex); } } /// /// Sets a row ok marker based on the given row amount. /// /// The index of the row to set the marker to. private void AppendRowOkMarker(int index) { if (!UseRowOkSign) { return; } scintillaOne.Lines[index].MarkerAdd(imageRowOkScintillaIndex); } /// /// Sets a row ok marker based on the given row amount to either the left side or the right side document. /// /// The index of the row to set the marker to. /// A value indicating whether to append the marker to the left or to the right side document. private void AppendRowOkMarker(int index, bool left) { if (!UseRowOkSign) { return; } if (left) { scintillaOne.Lines[index].MarkerAdd(imageRowOkScintillaIndex); } else { scintillaTwo.Lines[index].MarkerAdd(imageRowOkScintillaIndex); } } /// /// Sets a row differ marker based on the given row amount. /// /// The index of the row to set the marker to. private void AppendRowDiffMarker(int index) { scintillaOne.Lines[index].MarkerAdd(imageRowDiffScintillaIndex); } /// /// Sets a row differ marker based on the given row amount to either the left side or the right side document. /// /// The index of the row to set the marker to. /// A value indicating whether to append the marker to the left or to the right side document. private void AppendRowDiffMarker(int index, bool left) { if (left) { scintillaOne.Lines[index].MarkerAdd(imageRowDiffScintillaIndex); } else { scintillaTwo.Lines[index].MarkerAdd(imageRowDiffScintillaIndex); } } /// /// Appends the same row to the left and right controls. /// /// The text to append to the right and to the left document rows. private void AppendRow(string rowText) { builderLeft.AppendLine(rowText); builderRight.AppendLine(rowText); //scintillaOne.Text += rowText + Environment.NewLine; //scintillaTwo.Text += rowText + Environment.NewLine; } /// /// Appends different rows to the left and right side controls. /// /// The text to append to the the left document rows. /// The text to append to the right document rows. private void AppendRow(string rowTextLeft, string rowTextRight) { builderLeft.AppendLine(rowTextLeft); builderRight.AppendLine(rowTextRight); // scintillaOne.Text += rowTextLeft + Environment.NewLine; // scintillaTwo.Text += rowTextRight + Environment.NewLine; } /// /// Compares to contents of the two texts if both are assigned in a list style view. /// private void DiffTextsList() { // collapse the right panel of the split control.. scMain.Panel2Collapsed = true; // validate that there is text to compare.. if (!string.IsNullOrEmpty(TextLeft) || !string.IsNullOrEmpty(TextRight)) { // clear the two Scintilla control contents.. scintillaOne.Text = string.Empty; scintillaTwo.Text = string.Empty; // clear the two StringBuilder instance contents.. builderLeft.Clear(); builderRight.Clear(); // create a diff for a list style text comparison.. var diffBuilder = new InlineDiffBuilder(new Differ()); // compare the two texts.. var diff = diffBuilder.BuildDiffModel(TextLeft, TextRight); // output the diff data to the left side Scintilla control; // first the rows so the style can be appended afterwards.. foreach (var line in diff.Lines) { switch (line.Type) { case ChangeType.Inserted: AppendRowAdded(line.Text); break; case ChangeType.Deleted: AppendRowDeleted(line.Text); break; case ChangeType.Unchanged: AppendRow(line.Text); break; } } scintillaOne.Text = builderLeft.ToString(); // set a variable for the line index.. int lineIndex = 0; // set the style for the lines now that the Scintilla document's // contents have been set.. foreach (var line in diff.Lines) { if (IsEntireLineHighlighted) SetLineBackgroundColor(lineIndex, line.Type); switch (line.Type) { case ChangeType.Inserted: // save the line location.. SaveLineLocation(lineIndex); AppendRowAddedMarker(lineIndex); break; case ChangeType.Deleted: // save the line location.. SaveLineLocation(lineIndex); AppendRowDeletedMarker(lineIndex); break; case ChangeType.Unchanged: AppendRowOkMarker(lineIndex); break; case ChangeType.Modified: // save the line location.. SaveLineLocation(lineIndex); AppendRowDiffMarker(lineIndex); break; } lineIndex++; } // reset the index of the next difference.. diffIndex = -1; // raise the ExternalStyleNeeded event if it's subscribed.. ExternalStyleNeeded?.Invoke(this, new StyleRefreshEventArgs {Scintilla = LeftScintilla}); // sort the jump locations.. DiffLocations.Sort(); } } /// /// Saves the line location to the property. /// /// The index of the row which location to save. private void SaveLineLocation(int lineIndex) { if (DiffLocations.Contains(lineIndex)) { return; } DiffLocations.Add(lineIndex); } private KeyValuePair SpanLeftRightChars(DiffResult diffResult, int line, string oldLine, string newLine, ref List charChangedList) { if (diffResult == null || diffResult.DiffBlocks.Count == 0) { return new KeyValuePair(oldLine, newLine); } oldLine = oldLine ?? string.Empty; newLine = newLine ?? string.Empty; if (oldLine.Length == newLine.Length) { for (int i = 0; i < oldLine.Length; i++) { if (oldLine[i] != newLine[i]) { charChangedList.Add(new CharacterChangeType {ChangeType = CharChangedType.Modified, Length = 1, LineIndex = line, Position = i}); } } return new KeyValuePair(oldLine, newLine); } foreach (var diffBlock in diffResult.DiffBlocks) { if (diffBlock.DeleteCountA == diffBlock.InsertCountB && diffBlock.DeleteStartA == diffBlock.InsertStartB) { charChangedList.Add(new CharacterChangeType { ChangeType = CharChangedType.Modified, Length = diffBlock.DeleteCountA, LineIndex = line, Position = diffBlock.DeleteStartA }); continue; } if (diffBlock.DeleteCountA > 0) { if (CharacterComparisonMarkAddRemove) { oldLine = oldLine.Insert(diffBlock.DeleteStartA, new string(AddedCharacterSymbol, diffBlock.DeleteCountA)); charChangedList.Add(new CharacterChangeType { ChangeType = CharChangedType.Added, Length = diffBlock.DeleteCountA, LineIndex = line, Position = diffBlock.DeleteStartA }); } } if (diffBlock.InsertCountB > 0) { if (CharacterComparisonMarkAddRemove) { newLine = newLine.Insert(diffBlock.InsertStartB, new string(RemovedCharacterSymbol, diffBlock.InsertCountB)); charChangedList.Add(new CharacterChangeType { ChangeType = CharChangedType.Removed, Length = diffBlock.InsertCountB, LineIndex = line, Position = diffBlock.InsertStartB }); } } } return new KeyValuePair(oldLine, newLine); } /// /// Compares to contents of the two texts if both are assigned in a side by side style view. /// private void DiffTextsSideBySide() { // un-collapse the right panel of the split control.. scMain.Panel2Collapsed = false; // recalculate the position of the split control's // splitter.. RecalculateSize(); // validate that there is text to compare.. if (!string.IsNullOrEmpty(TextLeft) || !string.IsNullOrEmpty(TextRight)) { scintillaOne.ReadOnly = false; scintillaTwo.ReadOnly = false; // clear the two Scintilla control contents.. scintillaOne.Text = string.Empty; scintillaTwo.Text = string.Empty; scintillaOne.ClearAll(); scintillaTwo.ClearAll(); // clear the two StringBuilder instance contents.. builderLeft.Clear(); builderRight.Clear(); // initialize a new instance of the Differ class.. var differ = new Differ(); // create a diff for a side by side text comparison.. var diffBuilder = new SideBySideDiffBuilder(differ); // compare the two texts.. var diff = diffBuilder.BuildDiffModel(TextLeft, TextRight); List changedCharacters = new List(); // output the diff data to the left and to the right side Scintilla controls; // first the rows so the style can be appended afterwards.. for (int i = 0; i < diff.OldText.Lines.Count; i++) { if (CharacterComparison) { var diffResult = Differ.Instance.CreateCharacterDiffs(diff.NewText.Lines[i].Text ?? string.Empty, diff.OldText.Lines[i].Text ?? string.Empty, false, false); var span = SpanLeftRightChars(diffResult, i, diff.OldText.Lines[i].Text, diff.NewText.Lines[i].Text, ref changedCharacters); AppendRow(span.Key, span.Value); } else // no character comparison.. { AppendRow(diff.OldText.Lines[i].Text, diff.NewText.Lines[i].Text); } } if (CharacterComparison) { diff = diffBuilder.BuildDiffModel(builderLeft.ToString(), builderRight.ToString()); } scintillaOne.Text = builderLeft.ToString(); scintillaTwo.Text = builderRight.ToString(); scintillaOne.ReadOnly = scintillaTwo.ReadOnly= true; // clear the list of difference locations.. DiffLocations.Clear(); if (IsEntireLineHighlighted) { for (int i = 0; i < diff.OldText.Lines.Count; i++) { if (diff.OldText.Lines[i].Type == ChangeType.Modified || diff.NewText.Lines[i].Type == ChangeType.Modified) { SetLineBackgroundColor(i, ChangeType.Modified); HandleDiffSubPieces(diff.NewText.Lines[i].SubPieces, i, false); HandleDiffSubPieces(diff.OldText.Lines[i].SubPieces, i, true); } else if (diff.OldText.Lines[i].Type == ChangeType.Deleted) { SetLineBackgroundColor(i, ChangeType.Deleted); AppendRowDeletedMarker(i, left: true); } else if (diff.OldText.Lines[i].Type == ChangeType.Imaginary && diff.NewText.Lines[i].Type == ChangeType.Inserted) { SetLineBackgroundColor(i, ChangeType.Inserted); } } } else { // loop through the meta-data of the diff result and set the styling // for the Scintilla controls accordingly.. for (int i = 0; i < diff.OldText.Lines.Count; i++) { switch (diff.OldText.Lines[i].Type) { case ChangeType.Inserted: AppendRowAddedMarker(i, true); // save the line location.. SaveLineLocation(i); break; case ChangeType.Deleted: AppendRowDeletedMarker(i, true); // save the line location.. SaveLineLocation(i); break; case ChangeType.Unchanged: AppendRowOkMarker(i, true); break; case ChangeType.Modified: // save the line location.. SaveLineLocation(i); AppendRowDiffMarker(i, false); MarkLineWithColor(i, DiffColorChangeBackground, true); HandleDiffSubPieces(diff.OldText.Lines[i].SubPieces, i, true); break; } switch (diff.NewText.Lines[i].Type) { case ChangeType.Inserted: AppendRowAddedMarker(i, false); // save the line location.. SaveLineLocation(i); break; case ChangeType.Deleted: AppendRowDeletedMarker(i, false); // save the line location.. SaveLineLocation(i); break; case ChangeType.Unchanged: AppendRowOkMarker(i, false); break; case ChangeType.Modified: // save the line location.. SaveLineLocation(i); AppendRowDiffMarker(i, true); MarkLineWithColor(i, DiffColorChangeBackground, false); HandleDiffSubPieces(diff.NewText.Lines[i].SubPieces, i, false); break; } } } MarkWithBackgroundColor(changedCharacters, diffColorCharAdded, diffColorCharDeleted); // reset the index of the next difference.. diffIndex = -1; // raise the ExternalStyleNeeded event if it's subscribed for both of the Scintilla controls.. ExternalStyleNeeded?.Invoke(this, new StyleRefreshEventArgs {Scintilla = LeftScintilla}); ExternalStyleNeeded?.Invoke(this, new StyleRefreshEventArgs {Scintilla = RightScintilla}); // sort the jump locations.. DiffLocations.Sort(); } else { scintillaOne.ReadOnly = false; scintillaTwo.ReadOnly = false; // clear the two Scintilla control contents.. scintillaOne.Text = string.Empty; scintillaTwo.Text = string.Empty; scintillaOne.ClearAll(); scintillaTwo.ClearAll(); scintillaOne.ReadOnly = true; scintillaTwo.ReadOnly = true; } } /// /// Marks a given position of a row with a given background color. /// /// The index of the line to mark with the control. /// The position in the line to mark with the given color. /// A value indicating whether to use the left or the right side control. /// A class instance to get the length of the text. /// A to use with the marking. private void MarkWithBackgroundColor(int lineIndex, int subPosition, bool left, DiffPiece diffPiece, Color color) { if (diffPiece.Position == null) { return; } int start = left ? scintillaOne.Lines[lineIndex].Position + subPosition : scintillaTwo.Lines[lineIndex].Position + subPosition; Highlight.HighlightRange(left ? scintillaOne : scintillaTwo, MarkColorIndexModifiedBackground, start, diffPiece.Text.Length, color); } private void ClearStylesArea(Scintilla scintilla, int line, int position, int length) { for (int i = markColorCharacterChanged; i <= markColorIndexModifiedBackground; i++) { Highlight.ClearStyleArea(scintilla, line, position, length, i); } } private void MarkWithBackgroundColor(List changedCharacters, Color colorAdd, Color colorDeleted) { foreach (var changedCharacter in changedCharacters) { int startOne = scintillaOne.Lines[changedCharacter.LineIndex].Position + changedCharacter.Position; int startTwo = scintillaTwo.Lines[changedCharacter.LineIndex].Position + changedCharacter.Position; ClearStylesArea(scintillaOne, changedCharacter.LineIndex, changedCharacter.Position, changedCharacter.Length); ClearStylesArea(scintillaTwo, changedCharacter.LineIndex, changedCharacter.Position, changedCharacter.Length); switch (changedCharacter.ChangeType) { case CharChangedType.Modified: Highlight.HighlightRange(scintillaOne, markColorCharacterChanged, startOne, changedCharacter.Length, colorDeleted); Highlight.HighlightRange(scintillaTwo, markColorCharacterChanged, startTwo, changedCharacter.Length, colorAdd); break; case CharChangedType.Added: Highlight.HighlightRange(scintillaOne, markColorCharacterAdded, startOne, changedCharacter.Length, colorDeleted); Highlight.HighlightRange(scintillaTwo, markColorCharacterRemoved, startTwo, changedCharacter.Length, colorAdd); break; case CharChangedType.Removed: Highlight.HighlightRange(scintillaOne, markColorCharacterRemoved, startOne, changedCharacter.Length, colorDeleted); Highlight.HighlightRange(scintillaTwo, markColorCharacterAdded, startTwo, changedCharacter.Length, colorAdd); break; } } } /// /// Marks a line of a control with a given . /// /// The index of the line to mark with the control. /// A to use with the marking. /// A value indicating whether to use the left or the right side control. private void MarkLineWithColor(int lineIndex, Color color, bool left) { int start = left ? scintillaOne.Lines[lineIndex].Position : scintillaTwo.Lines[lineIndex].Position; int length = left ? scintillaOne.Lines[lineIndex].Length : scintillaTwo.Lines[lineIndex].Length; Highlight.HighlightRange(left ? scintillaOne : scintillaTwo, MarkColorIndexRemovedOrAdded, start, length, color); } /// /// Sets the background color for the entire line of a control. /// /// The index of the line to highlight. /// The type of change that was made to the line, indicating which color to set the background. private void SetLineBackgroundColor(int lineIndex, ChangeType changeType) { Highlight.HighlightLine(scintillaOne, lineIndex, (int)changeType); Highlight.HighlightLine(scintillaTwo, lineIndex, (int)changeType); } /// /// Handles the difference sub-pieces in a side-by-side comparison to set a color for a word. /// /// A list of class instances to be marked. /// The index of the line to mark with the control. /// A value indicating whether to use the left or the right side control. private void HandleDiffSubPieces(List subPieces, int lineIndex, bool left) { int calcPosition = 0; foreach (var subPiece in subPieces) { switch (subPiece.Type) { case ChangeType.Deleted: MarkWithBackgroundColor(lineIndex, calcPosition, left, subPiece, DiffColorDeleted); break; case ChangeType.Inserted: MarkWithBackgroundColor(lineIndex, calcPosition, left, subPiece, DiffColorAdded); break; } if (subPiece.Position != null) { calcPosition += subPiece.Text.Length; } } } /// /// Jumps the view into a given line index. /// /// Index of the line to jump the view to. /// if set to true the call was made from the method. /// true if the line index was valid and the view was scrolled, false otherwise. private bool JumpToLine(int lineIndex, bool backwards) { if (lineIndex < 0 || lineIndex >= DiffLocations.Count) { return false; } if (diffStyle == DiffStyle.DiffList) { int linePos1 = scintillaOne.Lines[DiffLocations[lineIndex]].Position; scintillaOne.GotoPosition(linePos1); scintillaOne.ScrollCaret(); } else if (diffStyle == DiffStyle.DiffSideBySide) { int linePos1 = scintillaOne.Lines[DiffLocations[lineIndex]].Position; scintillaOne.GotoPosition(linePos1); int linePos2 = scintillaTwo.Lines[DiffLocations[lineIndex]].Position; scintillaTwo.GotoPosition(linePos2); scintillaOne.ScrollCaret(); scintillaTwo.ScrollCaret(); } if (!backwards && lineIndex + 1 >= DiffLocations.Count) { return false; } return true; } #endregion #region PublicMethods /// /// Compares to contents of the two texts based on the property value. /// public void DiffTexts() { if (diffStyle == DiffStyle.DiffList) { DiffTextsList(); } else { DiffTextsSideBySide(); } } /// /// Swaps the texts to compare. /// // ReSharper disable once UnusedMember.Global public void SwapDiff() { string temp = textLeft; textLeft = textRight; textRight = temp; DiffTexts(); } /// /// Jumps to the next difference within the diff view. /// /// true if the navigation to the next position was possible, false otherwise. public bool Next() { if (diffIndex >= DiffLocations.Count) { return false; } diffIndex++; return JumpToLine(diffIndex, false); } /// /// Jumps to the previous difference within the diff view. /// /// true if the navigation to the previous position was possible, false otherwise. public bool Previous() { if (diffIndex < 0) { return false; } diffIndex--; return JumpToLine(diffIndex, true); } #endregion private void ScintillaDiffControl_Load(object sender, EventArgs e) { ryCommon.Auto.LeftClick(scintillaOne.Handle, 0, 0); ryCommon.Auto.LeftClick(scintillaTwo.Handle, 0, 0); } } }