using System; using System.Collections.Generic; namespace MergeHub.Services { /// 편집기에 그려 넣을 줄들과, 그중 어느 줄이 필러인지. public sealed class EditorSnapshot { public EditorSnapshot(string[] lines, bool[] filler) { Lines = lines; Filler = filler; } /// 편집기 본문. 필러 자리는 빈 문자열이다. public string[] Lines { get; } /// 같은 인덱스의 줄이 필러(= LOCAL 에 없는 줄)인지. public bool[] Filler { get; } } /// /// LOCAL 패널을 "항상 고칠 수 있는 편집기"로 쓰기 위한 줄 계산. /// /// 화면에서 BASE 와 LOCAL 은 같은 내용이 같은 눈높이에 와야 한다. 그래서 BASE 에만 있는 /// 줄(= LOCAL 에서 지워진 줄) 자리에는 LOCAL 쪽에 빈 줄(필러)을 끼워 넣는다. /// 필러는 화면을 맞추려고 넣은 것일 뿐 파일 내용이 아니므로, 저장하거나 다시 정렬하기 전에 /// 걷어내야 한다. /// /// 문제는 사용자가 편집기에서 줄을 넣고 지운 뒤라 필러 위치가 밀려 있다는 점이다. /// 그래서 "고치기 전 편집기 내용"과 "지금 편집기 내용"을 정렬해 보고, /// 예전 필러와 그대로 대응되면서 여전히 비어 있는 줄만 필러로 판정해 걷어낸다. /// 필러 자리에 사용자가 뭔가 써 넣었다면 그건 새로 추가한 줄이므로 그대로 살린다. /// public static class EditorLayout { /// 정렬 결과를 편집기에 그릴 줄들로 바꾼다. public static EditorSnapshot Build(IReadOnlyList<(string Text, bool HasLocal)> rows) { var lines = new string[rows.Count]; var filler = new bool[rows.Count]; for (var i = 0; i < rows.Count; i++) { filler[i] = !rows[i].HasLocal; lines[i] = rows[i].HasLocal ? rows[i].Text : string.Empty; } return new EditorSnapshot(lines, filler); } /// /// 사용자가 고친 편집기 내용에서 필러를 걷어내 실제 LOCAL 줄만 남긴다. /// /// 고치기 전 편집기 줄(필러 포함) /// 그때 어느 줄이 필러였는지 /// 지금 편집기 줄 public static List StripFillers(IReadOnlyList previous, IReadOnlyList wasFiller, IReadOnlyList current) { var kept = new List(current.Count); // 고치기 전 내용이 없으면 걷어낼 필러도 없다. if (previous.Count == 0) { foreach (var line in current) kept.Add(line); return kept; } // 어느 줄이 그대로 남았는지 짚어 준다. 빈 줄은 흔해서 앵커가 되지 못하므로 // 주변의 진짜 코드 줄에 딸려 자리를 잡는다 - 그래서 정렬기를 쓰는 것이다. var map = LineAligner.Align(previous, current); foreach (var row in map) { if (row.LocalNumber is null) continue; // 사용자가 지운 줄 var text = row.LocalText ?? string.Empty; var cameFromFiller = row.BaseNumber is int oldNumber && oldNumber >= 1 && oldNumber <= wasFiller.Count && wasFiller[oldNumber - 1]; // 필러였고 여전히 비어 있으면 화면용 빈 줄일 뿐이다. if (cameFromFiller && text.Length == 0) continue; kept.Add(text); } return kept; } /// 두 줄 목록이 같은지. 편집기 글을 다시 써야 하는지 판단할 때 쓴다. public static bool SameLines(IReadOnlyList a, IReadOnlyList b) { if (a.Count != b.Count) return false; for (var i = 0; i < a.Count; i++) if (!string.Equals(a[i], b[i], StringComparison.Ordinal)) return false; return true; } } }