using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace UnityVersionControl
{
///
/// Advanced File Diff Viewer for Unity Assets
/// Supports text diffs, visual comparisons, and property changes
///
public class FileDiffViewer : EditorWindow
{
#region Fields
private Vector2 scrollPosition;
private int selectedViewMode = 0;
private readonly string[] viewModeNames = { "Split View", "Unified Diff", "Visual Compare" };
private string currentFilePath = "";
private DiffData currentDiff;
private Texture2D leftTexture;
private Texture2D rightTexture;
// UI state
private bool showLineNumbers = true;
private bool showWhitespace = false;
private bool wordWrap = false;
private int contextLines = 3;
private string searchText = "";
// Visual constants
private static readonly Color backgroundColor = new Color(0.22f, 0.22f, 0.22f);
private static readonly Color cardColor = new Color(0.28f, 0.28f, 0.28f);
private static readonly Color addedColor = new Color(0.2f, 0.6f, 0.2f, 0.3f);
private static readonly Color removedColor = new Color(0.6f, 0.2f, 0.2f, 0.3f);
private static readonly Color modifiedColor = new Color(0.6f, 0.4f, 0.2f, 0.3f);
private static readonly Color lineNumberColor = new Color(0.5f, 0.5f, 0.5f);
private static readonly Color contextColor = new Color(0.8f, 0.8f, 0.8f);
// Caches
private readonly Dictionary textureCache = new Dictionary();
private readonly Dictionary diffCache = new Dictionary();
#endregion
#region Unity Lifecycle
[MenuItem("Window/Version Control/File Diff Viewer", false, 3)]
public static void ShowWindow()
{
var window = GetWindow();
window.titleContent = new GUIContent("File Diff");
window.minSize = new Vector2(800, 600);
}
public static void ShowDiff(string filePath)
{
var window = GetWindow();
window.titleContent = new GUIContent($"Diff: {System.IO.Path.GetFileName(filePath)}");
window.minSize = new Vector2(800, 600);
window.LoadFile(filePath);
}
private void OnEnable()
{
LoadTestData();
}
private void OnDisable()
{
CleanupTextures();
}
private void OnGUI()
{
DrawBackground();
DrawToolbar();
DrawDiffContent();
}
#endregion
#region File Loading
public void LoadFile(string filePath)
{
currentFilePath = filePath;
if (diffCache.TryGetValue(filePath, out var cachedDiff))
{
currentDiff = cachedDiff;
}
else
{
currentDiff = GenerateDiff(filePath);
diffCache[filePath] = currentDiff;
}
LoadAssetTextures(filePath);
Repaint();
}
private void LoadTestData()
{
// Load a test diff for demonstration
LoadFile("Assets/Scripts/PlayerController.cs");
}
private DiffData GenerateDiff(string filePath)
{
var extension = System.IO.Path.GetExtension(filePath).ToLower();
switch (extension)
{
case ".cs":
case ".js":
case ".shader":
case ".hlsl":
case ".cginc":
return GenerateCodeDiff(filePath);
case ".unity":
case ".prefab":
case ".asset":
case ".mat":
return GenerateAssetDiff(filePath);
case ".png":
case ".jpg":
case ".jpeg":
case ".tga":
return GenerateImageDiff(filePath);
default:
return GenerateTextDiff(filePath);
}
}
private DiffData GenerateCodeDiff(string filePath)
{
// Mock C# code diff
var diff = new DiffData
{
filePath = filePath,
diffType = DiffType.Code,
leftContent = GetMockCodeContent(true),
rightContent = GetMockCodeContent(false),
lines = new List()
};
diff.lines = GenerateDiffLines(diff.leftContent, diff.rightContent);
return diff;
}
private DiffData GenerateAssetDiff(string filePath)
{
// Mock Unity asset property diff
var diff = new DiffData
{
filePath = filePath,
diffType = DiffType.Properties,
leftContent = GetMockAssetContent(true),
rightContent = GetMockAssetContent(false),
propertyChanges = GeneratePropertyChanges()
};
return diff;
}
private DiffData GenerateImageDiff(string filePath)
{
// Mock image diff
var diff = new DiffData
{
filePath = filePath,
diffType = DiffType.Image,
leftImagePath = "Assets/Textures/PlayerTexture_Old.png",
rightImagePath = "Assets/Textures/PlayerTexture_New.png"
};
return diff;
}
private DiffData GenerateTextDiff(string filePath)
{
// Generic text diff
var diff = new DiffData
{
filePath = filePath,
diffType = DiffType.Text,
leftContent = "Original content line 1\nOriginal content line 2\nShared line 3\nOriginal line 4",
rightContent = "Modified content line 1\nNew line 2\nShared line 3\nModified line 4\nNew line 5"
};
diff.lines = GenerateDiffLines(diff.leftContent, diff.rightContent);
return diff;
}
private void LoadAssetTextures(string filePath)
{
if (currentDiff?.diffType == DiffType.Image)
{
leftTexture = LoadTextureFromPath(currentDiff.leftImagePath);
rightTexture = LoadTextureFromPath(currentDiff.rightImagePath);
}
}
private Texture2D LoadTextureFromPath(string path)
{
try
{
return AssetDatabase.LoadAssetAtPath(path);
}
catch
{
// Return a placeholder texture
return CreateSolidColorTexture(Color.gray, 256, 256);
}
}
#endregion
#region Mock Data Generation
private string GetMockCodeContent(bool isOld)
{
if (isOld)
{
return @"using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 5.0f;
public float jumpForce = 8.0f;
private Rigidbody rb;
void Start()
{
rb = GetComponent();
}
void Update()
{
float horizontal = Input.GetAxis(""Horizontal"");
float vertical = Input.GetAxis(""Vertical"");
Vector3 movement = new Vector3(horizontal, 0, vertical);
transform.Translate(movement * speed * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.Space))
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
}";
}
else
{
return @"using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header(""Movement Settings"")]
public float speed = 7.0f;
public float jumpForce = 10.0f;
public float groundCheckDistance = 0.1f;
private Rigidbody rb;
private bool isGrounded;
void Start()
{
rb = GetComponent();
}
void Update()
{
CheckGrounded();
HandleMovement();
HandleJumping();
}
void CheckGrounded()
{
isGrounded = Physics.Raycast(transform.position, Vector3.down, groundCheckDistance);
}
void HandleMovement()
{
float horizontal = Input.GetAxis(""Horizontal"");
float vertical = Input.GetAxis(""Vertical"");
Vector3 movement = new Vector3(horizontal, 0, vertical).normalized;
rb.velocity = new Vector3(movement.x * speed, rb.velocity.y, movement.z * speed);
}
void HandleJumping()
{
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
}";
}
}
private string GetMockAssetContent(bool isOld)
{
if (isOld)
{
return @"Transform:
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
scale: {x: 1, y: 1, z: 1}
MeshRenderer:
material: PlayerMaterial
receiveShadows: 1
BoxCollider:
size: {x: 1, y: 1, z: 1}
center: {x: 0, y: 0, z: 0}";
}
else
{
return @"Transform:
position: {x: 0, y: 0.5, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
scale: {x: 1.2, y: 1.2, z: 1.2}
MeshRenderer:
material: PlayerMaterial_Updated
receiveShadows: 1
shadowCastingMode: 1
BoxCollider:
size: {x: 1.1, y: 1.1, z: 1.1}
center: {x: 0, y: 0, z: 0}
isTrigger: 0
Rigidbody:
mass: 1
drag: 0.5";
}
}
private List GeneratePropertyChanges()
{
return new List
{
new PropertyChange
{
propertyName = "Transform.position.y",
oldValue = "0",
newValue = "0.5",
changeType = PropertyChangeType.Modified
},
new PropertyChange
{
propertyName = "Transform.scale",
oldValue = "{x: 1, y: 1, z: 1}",
newValue = "{x: 1.2, y: 1.2, z: 1.2}",
changeType = PropertyChangeType.Modified
},
new PropertyChange
{
propertyName = "MeshRenderer.material",
oldValue = "PlayerMaterial",
newValue = "PlayerMaterial_Updated",
changeType = PropertyChangeType.Modified
},
new PropertyChange
{
propertyName = "MeshRenderer.shadowCastingMode",
oldValue = "",
newValue = "1",
changeType = PropertyChangeType.Added
},
new PropertyChange
{
propertyName = "BoxCollider.size",
oldValue = "{x: 1, y: 1, z: 1}",
newValue = "{x: 1.1, y: 1.1, z: 1.1}",
changeType = PropertyChangeType.Modified
},
new PropertyChange
{
propertyName = "Rigidbody",
oldValue = "",
newValue = "Component Added",
changeType = PropertyChangeType.Added
}
};
}
#endregion
#region GUI Drawing
private void DrawBackground()
{
var rect = new Rect(0, 0, position.width, position.height);
EditorGUI.DrawRect(rect, backgroundColor);
}
private void DrawToolbar()
{
DrawCard(() =>
{
using (new EditorGUILayout.HorizontalScope())
{
// File info
using (new EditorGUILayout.VerticalScope())
{
var titleStyle = new GUIStyle(EditorStyles.boldLabel);
titleStyle.fontSize = 14;
titleStyle.normal.textColor = Color.white;
EditorGUILayout.LabelField(System.IO.Path.GetFileName(currentFilePath), titleStyle);
var pathStyle = new GUIStyle(EditorStyles.label);
pathStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f);
pathStyle.fontSize = 11;
EditorGUILayout.LabelField(currentFilePath, pathStyle);
}
GUILayout.FlexibleSpace();
// View mode selector
EditorGUILayout.LabelField("View:", GUILayout.Width(40));
selectedViewMode = EditorGUILayout.Popup(selectedViewMode, viewModeNames, GUILayout.Width(120));
GUILayout.Space(8);
// Options
showLineNumbers = EditorGUILayout.Toggle("Lines", showLineNumbers, GUILayout.Width(60));
showWhitespace = EditorGUILayout.Toggle("Space", showWhitespace, GUILayout.Width(60));
wordWrap = EditorGUILayout.Toggle("Wrap", wordWrap, GUILayout.Width(60));
}
EditorGUILayout.Space(4);
// Search bar
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Search:", GUILayout.Width(50));
searchText = EditorGUILayout.TextField(searchText);
if (GUILayout.Button("Clear", GUILayout.Width(50)))
searchText = "";
}
}, 12);
}
private void DrawDiffContent()
{
if (currentDiff == null)
{
DrawEmptyState();
return;
}
EditorGUILayout.Space(8);
switch (selectedViewMode)
{
case 0: DrawSplitView(); break;
case 1: DrawUnifiedView(); break;
case 2: DrawVisualCompare(); break;
}
}
private void DrawEmptyState()
{
DrawCard(() =>
{
var iconRect = GUILayoutUtility.GetRect(48, 48);
var emptyTexture = CreateSolidColorTexture(new Color(0.5f, 0.5f, 0.5f, 0.3f), 48, 48);
GUI.DrawTexture(iconRect, emptyTexture);
EditorGUILayout.Space(8);
var titleStyle = new GUIStyle(EditorStyles.boldLabel);
titleStyle.alignment = TextAnchor.MiddleCenter;
titleStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f);
EditorGUILayout.LabelField("No File Selected", titleStyle);
var subtitleStyle = new GUIStyle(EditorStyles.label);
subtitleStyle.alignment = TextAnchor.MiddleCenter;
subtitleStyle.normal.textColor = new Color(0.6f, 0.6f, 0.6f);
subtitleStyle.wordWrap = true;
EditorGUILayout.LabelField("Select a modified file to view its diff.", subtitleStyle);
}, 32);
}
private void DrawSplitView()
{
using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
{
scrollPosition = scrollView.scrollPosition;
switch (currentDiff.diffType)
{
case DiffType.Code:
case DiffType.Text:
DrawCodeSplitView();
break;
case DiffType.Properties:
DrawPropertySplitView();
break;
case DiffType.Image:
DrawImageSplitView();
break;
}
}
}
private void DrawCodeSplitView()
{
DrawCard(() =>
{
using (new EditorGUILayout.HorizontalScope())
{
// Left panel header
using (new EditorGUILayout.VerticalScope())
{
var headerStyle = new GUIStyle(EditorStyles.boldLabel);
headerStyle.normal.textColor = removedColor;
headerStyle.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.LabelField("BEFORE", headerStyle);
DrawCodePanel(currentDiff.leftContent, true);
}
GUILayout.Space(8);
// Right panel header
using (new EditorGUILayout.VerticalScope())
{
var headerStyle = new GUIStyle(EditorStyles.boldLabel);
headerStyle.normal.textColor = addedColor;
headerStyle.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.LabelField("AFTER", headerStyle);
DrawCodePanel(currentDiff.rightContent, false);
}
}
}, 12);
}
private void DrawCodePanel(string content, bool isLeft)
{
var lines = content.Split('\n');
var codeStyle = new GUIStyle(EditorStyles.textArea);
codeStyle.wordWrap = wordWrap;
codeStyle.richText = true;
codeStyle.font = EditorGUIUtility.Load("Consolas") as Font ?? GUI.skin.font;
for (int i = 0; i < lines.Length; i++)
{
using (new EditorGUILayout.HorizontalScope())
{
if (showLineNumbers)
{
var lineNumStyle = new GUIStyle(EditorStyles.label);
lineNumStyle.normal.textColor = lineNumberColor;
lineNumStyle.fontSize = 10;
lineNumStyle.alignment = TextAnchor.MiddleRight;
EditorGUILayout.LabelField((i + 1).ToString(), lineNumStyle, GUILayout.Width(30));
}
var line = lines[i];
if (!string.IsNullOrEmpty(searchText) && line.Contains(searchText))
{
line = line.Replace(searchText, $"{searchText}");
}
var lineRect = EditorGUILayout.GetControlRect();
// Highlight changed lines
var diffLine = GetDiffLineForIndex(i, isLeft);
if (diffLine != null)
{
Color bgColor = diffLine.type switch
{
DiffLineType.Added => addedColor,
DiffLineType.Removed => removedColor,
DiffLineType.Modified => modifiedColor,
_ => Color.clear
};
if (bgColor != Color.clear)
{
EditorGUI.DrawRect(lineRect, bgColor);
}
}
EditorGUI.LabelField(lineRect, line, codeStyle);
}
}
}
private void DrawPropertySplitView()
{
DrawCard(() =>
{
using (new EditorGUILayout.HorizontalScope())
{
// Properties comparison
using (new EditorGUILayout.VerticalScope())
{
var headerStyle = new GUIStyle(EditorStyles.boldLabel);
headerStyle.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.LabelField("PROPERTY CHANGES", headerStyle);
EditorGUILayout.Space(8);
foreach (var change in currentDiff.propertyChanges)
{
DrawPropertyChange(change);
EditorGUILayout.Space(4);
}
}
}
}, 12);
}
private void DrawPropertyChange(PropertyChange change)
{
var changeRect = EditorGUILayout.BeginHorizontal();
Color bgColor = change.changeType switch
{
PropertyChangeType.Added => addedColor,
PropertyChangeType.Removed => removedColor,
PropertyChangeType.Modified => modifiedColor,
_ => Color.clear
};
if (bgColor != Color.clear)
{
EditorGUI.DrawRect(changeRect, bgColor);
}
GUILayout.Space(8);
// Property name
var nameStyle = new GUIStyle(EditorStyles.boldLabel);
nameStyle.normal.textColor = Color.white;
nameStyle.fontSize = 11;
EditorGUILayout.LabelField(change.propertyName, nameStyle, GUILayout.Width(200));
// Change indicator
var changeIcon = change.changeType switch
{
PropertyChangeType.Added => "+",
PropertyChangeType.Removed => "-",
PropertyChangeType.Modified => "⟳",
_ => "?"
};
var iconStyle = new GUIStyle(EditorStyles.label);
iconStyle.normal.textColor = change.changeType switch
{
PropertyChangeType.Added => Color.green,
PropertyChangeType.Removed => Color.red,
PropertyChangeType.Modified => Color.yellow,
_ => Color.white
};
EditorGUILayout.LabelField(changeIcon, iconStyle, GUILayout.Width(20));
// Values
using (new EditorGUILayout.VerticalScope())
{
if (!string.IsNullOrEmpty(change.oldValue))
{
var oldStyle = new GUIStyle(EditorStyles.label);
oldStyle.normal.textColor = new Color(1f, 0.7f, 0.7f);
oldStyle.fontSize = 10;
EditorGUILayout.LabelField($"- {change.oldValue}", oldStyle);
}
if (!string.IsNullOrEmpty(change.newValue))
{
var newStyle = new GUIStyle(EditorStyles.label);
newStyle.normal.textColor = new Color(0.7f, 1f, 0.7f);
newStyle.fontSize = 10;
EditorGUILayout.LabelField($"+ {change.newValue}", newStyle);
}
}
GUILayout.Space(8);
EditorGUILayout.EndHorizontal();
}
private void DrawImageSplitView()
{
DrawCard(() =>
{
using (new EditorGUILayout.HorizontalScope())
{
// Left image
using (new EditorGUILayout.VerticalScope())
{
var headerStyle = new GUIStyle(EditorStyles.boldLabel);
headerStyle.normal.textColor = removedColor;
headerStyle.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.LabelField("BEFORE", headerStyle);
DrawImagePanel(leftTexture, "Old Version");
}
GUILayout.Space(8);
// Right image
using (new EditorGUILayout.VerticalScope())
{
var headerStyle = new GUIStyle(EditorStyles.boldLabel);
headerStyle.normal.textColor = addedColor;
headerStyle.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.LabelField("AFTER", headerStyle);
DrawImagePanel(rightTexture, "New Version");
}
}
}, 12);
}
private void DrawImagePanel(Texture2D texture, string label)
{
if (texture != null)
{
var maxSize = 300f;
var aspect = (float)texture.width / texture.height;
var displayWidth = aspect > 1 ? maxSize : maxSize * aspect;
var displayHeight = aspect > 1 ? maxSize / aspect : maxSize;
var imageRect = GUILayoutUtility.GetRect(displayWidth, displayHeight);
EditorGUI.DrawRect(imageRect, Color.black);
GUI.DrawTexture(imageRect, texture, ScaleMode.ScaleToFit);
// Image info
var infoStyle = new GUIStyle(EditorStyles.label);
infoStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f);
infoStyle.fontSize = 10;
infoStyle.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.LabelField($"{texture.width}x{texture.height}", infoStyle);
}
else
{
var placeholderRect = GUILayoutUtility.GetRect(300, 200);
EditorGUI.DrawRect(placeholderRect, new Color(0.3f, 0.3f, 0.3f));
var labelStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
labelStyle.normal.textColor = Color.white;
GUI.Label(placeholderRect, label, labelStyle);
}
}
private void DrawUnifiedView()
{
using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
{
scrollPosition = scrollView.scrollPosition;
DrawCard(() =>
{
if (currentDiff.lines != null && currentDiff.lines.Count > 0)
{
DrawUnifiedDiff();
}
else
{
var noChangesStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
noChangesStyle.normal.textColor = Color.white;
EditorGUILayout.LabelField("No textual changes to display", noChangesStyle);
}
}, 12);
}
}
private void DrawUnifiedDiff()
{
var codeStyle = new GUIStyle(EditorStyles.textArea);
codeStyle.wordWrap = wordWrap;
codeStyle.richText = true;
codeStyle.font = EditorGUIUtility.Load("Consolas") as Font ?? GUI.skin.font;
foreach (var line in currentDiff.lines)
{
using (new EditorGUILayout.HorizontalScope())
{
if (showLineNumbers)
{
var lineNumStyle = new GUIStyle(EditorStyles.label);
lineNumStyle.normal.textColor = lineNumberColor;
lineNumStyle.fontSize = 10;
lineNumStyle.alignment = TextAnchor.MiddleRight;
var lineNumber = line.type == DiffLineType.Removed ? line.leftLineNumber : line.rightLineNumber;
EditorGUILayout.LabelField(lineNumber > 0 ? lineNumber.ToString() : "", lineNumStyle, GUILayout.Width(30));
}
var lineRect = EditorGUILayout.GetControlRect();
// Background color based on line type
Color bgColor = line.type switch
{
DiffLineType.Added => addedColor,
DiffLineType.Removed => removedColor,
DiffLineType.Modified => modifiedColor,
_ => Color.clear
};
if (bgColor != Color.clear)
{
EditorGUI.DrawRect(lineRect, bgColor);
}
// Line prefix
var prefix = line.type switch
{
DiffLineType.Added => "+ ",
DiffLineType.Removed => "- ",
DiffLineType.Modified => "~ ",
_ => " "
};
var displayText = prefix + line.content;
if (!string.IsNullOrEmpty(searchText) && line.content.Contains(searchText))
{
displayText = displayText.Replace(searchText, $"{searchText}");
}
EditorGUI.LabelField(lineRect, displayText, codeStyle);
}
}
}
private void DrawVisualCompare()
{
using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
{
scrollPosition = scrollView.scrollPosition;
if (currentDiff.diffType == DiffType.Image)
{
DrawImageOverlay();
}
else
{
DrawSideBySideComparison();
}
}
}
private void DrawImageOverlay()
{
DrawCard(() =>
{
EditorGUILayout.LabelField("Image Overlay Comparison", EditorStyles.boldLabel);
EditorGUILayout.Space(8);
// TODO: Implement image overlay/blend comparison
if (leftTexture != null && rightTexture != null)
{
var maxSize = 400f;
var imageRect = GUILayoutUtility.GetRect(maxSize, maxSize);
// For now, just show both images side by side
var leftRect = new Rect(imageRect.x, imageRect.y, imageRect.width * 0.5f, imageRect.height);
var rightRect = new Rect(imageRect.x + imageRect.width * 0.5f, imageRect.y, imageRect.width * 0.5f, imageRect.height);
GUI.DrawTexture(leftRect, leftTexture, ScaleMode.ScaleToFit);
GUI.DrawTexture(rightRect, rightTexture, ScaleMode.ScaleToFit);
}
else
{
EditorGUILayout.LabelField("Image overlay comparison not available", EditorStyles.centeredGreyMiniLabel);
}
}, 12);
}
private void DrawSideBySideComparison()
{
DrawSplitView(); // Fallback to split view for non-image assets
}
#endregion
#region Helper Methods
private void DrawCard(System.Action content, int padding = 12)
{
var rect = EditorGUILayout.BeginVertical();
EditorGUI.DrawRect(rect, cardColor);
GUILayout.Space(padding);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(padding);
EditorGUILayout.BeginVertical();
content?.Invoke();
EditorGUILayout.EndVertical();
GUILayout.Space(padding);
EditorGUILayout.EndHorizontal();
GUILayout.Space(padding);
EditorGUILayout.EndVertical();
}
private List GenerateDiffLines(string leftContent, string rightContent)
{
var leftLines = leftContent.Split('\n');
var rightLines = rightContent.Split('\n');
var result = new List();
// Simple diff algorithm - in production, use a proper diff library
var maxLines = Mathf.Max(leftLines.Length, rightLines.Length);
for (int i = 0; i < maxLines; i++)
{
var leftLine = i < leftLines.Length ? leftLines[i] : null;
var rightLine = i < rightLines.Length ? rightLines[i] : null;
if (leftLine == null)
{
result.Add(new DiffLine
{
content = rightLine,
type = DiffLineType.Added,
leftLineNumber = 0,
rightLineNumber = i + 1
});
}
else if (rightLine == null)
{
result.Add(new DiffLine
{
content = leftLine,
type = DiffLineType.Removed,
leftLineNumber = i + 1,
rightLineNumber = 0
});
}
else if (leftLine == rightLine)
{
result.Add(new DiffLine
{
content = leftLine,
type = DiffLineType.Context,
leftLineNumber = i + 1,
rightLineNumber = i + 1
});
}
else
{
result.Add(new DiffLine
{
content = rightLine,
type = DiffLineType.Modified,
leftLineNumber = i + 1,
rightLineNumber = i + 1
});
}
}
return result;
}
private DiffLine GetDiffLineForIndex(int index, bool isLeft)
{
if (currentDiff?.lines == null) return null;
return currentDiff.lines.FirstOrDefault(line =>
isLeft ? line.leftLineNumber == index + 1 : line.rightLineNumber == index + 1);
}
private Texture2D CreateSolidColorTexture(Color color, int width, int height)
{
var texture = new Texture2D(width, height);
var pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
texture.SetPixels(pixels);
texture.Apply();
return texture;
}
private void CleanupTextures()
{
foreach (var texture in textureCache.Values)
{
if (texture != null) DestroyImmediate(texture);
}
textureCache.Clear();
}
#endregion
}
#region Data Models
[Serializable]
public class DiffData
{
public string filePath;
public DiffType diffType;
public string leftContent;
public string rightContent;
public string leftImagePath;
public string rightImagePath;
public List lines;
public List propertyChanges;
}
[Serializable]
public class DiffLine
{
public string content;
public DiffLineType type;
public int leftLineNumber;
public int rightLineNumber;
}
[Serializable]
public class PropertyChange
{
public string propertyName;
public string oldValue;
public string newValue;
public PropertyChangeType changeType;
}
public enum DiffType
{
Text,
Code,
Properties,
Image,
Binary
}
public enum DiffLineType
{
Context,
Added,
Removed,
Modified
}
public enum PropertyChangeType
{
Added,
Removed,
Modified
}
#endregion
}