// Copyright (c) 2025 TerraByte Inc. // // A modal editor window for displaying text differences (diffs) and // confirming an action with the user. using System; using System.IO; using UnityEditor; using UnityEngine; namespace Terra.Arbitrator.GUI { public class DiffWindow : EditorWindow { private string _filePath; private string _diffContent; private Action _onCloseCallback; private Vector2 _scrollPosition; private bool _callbackInvoked; private GUIStyle _diffStyle; /// /// Shows a modal window to display the diff and get user confirmation. /// /// The path of the file being diffed. /// The diff text to display. /// A callback that returns true if the user confirmed, otherwise false. public static void ShowWindow(string filePath, string diffContent, Action onCloseCallback) { var window = GetWindow(true, "Diff Viewer", true); window.titleContent = new GUIContent($"Diff: {Path.GetFileName(filePath)}"); window.minSize = new Vector2(700, 500); window._filePath = filePath; window._diffContent = diffContent; window._onCloseCallback = onCloseCallback; window._callbackInvoked = false; window.ShowModalUtility(); // Show as a blocking modal window } private void OnEnable() { // Initialize the style here to avoid doing it every OnGUI call. _diffStyle = new GUIStyle(EditorStyles.label) { richText = true, wordWrap = false, alignment = TextAnchor.UpperLeft }; } private void OnGUI() { EditorGUILayout.LabelField(_filePath, EditorStyles.boldLabel); _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, EditorStyles.helpBox); var coloredText = GetColoredDiff(_diffContent); var content = new GUIContent(coloredText); // Calculate the required height based on the window's width, accounting for scrollbar and padding. var viewWidth = EditorGUIUtility.currentViewWidth - 30; // Approximate width inside scroll view var requiredHeight = _diffStyle.CalcHeight(content, viewWidth); // Use GUILayout.Label with an explicit height to ensure the ScrollView's content area is sized correctly. EditorGUILayout.SelectableLabel(coloredText, _diffStyle, GUILayout.MinHeight(requiredHeight)); EditorGUILayout.EndScrollView(); EditorGUILayout.Space(); EditorGUILayout.HelpBox("Review the changes above. Green lines are additions, red lines are deletions. Reverting will permanently discard these changes.", MessageType.Warning); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button("Revert Changes", GUILayout.Height(30), GUILayout.Width(150))) { _callbackInvoked = true; _onCloseCallback?.Invoke(true); Close(); } if (GUILayout.Button("Cancel", GUILayout.Height(30), GUILayout.Width(120))) { Close(); } EditorGUILayout.EndHorizontal(); } private static string GetColoredDiff(string rawDiff) { var coloredDiff = ""; var lines = rawDiff.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { if (line.StartsWith("+")) { coloredDiff += $"{line}\n"; } else if (line.StartsWith("-")) { coloredDiff += $"{line}\n"; } else if (line.StartsWith("@@")) { coloredDiff += $"{line}\n"; } else { coloredDiff += $"{line}\n"; } } return coloredDiff + "\n"; } private void OnDestroy() { // This is called when the window is closed by any means. // If the callback hasn't been invoked yet (i.e., user clicked 'X' or 'Cancel'), // we invoke it with 'false' to signify cancellation. if (!_callbackInvoked) { _onCloseCallback?.Invoke(false); } } } }