ksj09406 7719094edf 최초 커밋: MergeHub (사내 Git Merge/Diff 도구)
UbisamBase 플랫폼 위에서 도는 플러그인. 지금까지 만든 것을 그대로 담는다.
- 폴더 비교 / 파일 비교(부분 수용·워드랩·찾기) / 3-Way 병합 / Git 조작
- 비교 엔진: histogram 정렬 · 이동 블록 감지 · 무시 규칙 · 13개 언어 토크나이저
- doc/ 에 작업 지시서와 진행 기록

빌드 산출물(bin/obj)은 추적하지 않는다 - 플랫폼 런타임이 빌드 때마다
D:\UbisamPlatform\bin 에서 복사되어 들어오므로 저장소에 담을 이유가 없다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:13:27 +09:00

106 lines
4.4 KiB
C#

using System;
using System.Collections.Generic;
namespace MergeHub.Services
{
/// <summary>편집기에 그려 넣을 줄들과, 그중 어느 줄이 필러인지.</summary>
public sealed class EditorSnapshot
{
public EditorSnapshot(string[] lines, bool[] filler)
{
Lines = lines;
Filler = filler;
}
/// <summary>편집기 본문. 필러 자리는 빈 문자열이다.</summary>
public string[] Lines { get; }
/// <summary>같은 인덱스의 줄이 필러(= LOCAL 에 없는 줄)인지.</summary>
public bool[] Filler { get; }
}
/// <summary>
/// LOCAL 패널을 "항상 고칠 수 있는 편집기"로 쓰기 위한 줄 계산.
///
/// 화면에서 BASE 와 LOCAL 은 같은 내용이 같은 눈높이에 와야 한다. 그래서 BASE 에만 있는
/// 줄(= LOCAL 에서 지워진 줄) 자리에는 LOCAL 쪽에 <b>빈 줄(필러)</b>을 끼워 넣는다.
/// 필러는 화면을 맞추려고 넣은 것일 뿐 파일 내용이 아니므로, 저장하거나 다시 정렬하기 전에
/// 걷어내야 한다.
///
/// 문제는 사용자가 편집기에서 줄을 넣고 지운 뒤라 필러 위치가 밀려 있다는 점이다.
/// 그래서 "고치기 전 편집기 내용"과 "지금 편집기 내용"을 정렬해 보고,
/// <b>예전 필러와 그대로 대응되면서 여전히 비어 있는 줄</b>만 필러로 판정해 걷어낸다.
/// 필러 자리에 사용자가 뭔가 써 넣었다면 그건 새로 추가한 줄이므로 그대로 살린다.
/// </summary>
public static class EditorLayout
{
/// <summary>정렬 결과를 편집기에 그릴 줄들로 바꾼다.</summary>
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);
}
/// <summary>
/// 사용자가 고친 편집기 내용에서 필러를 걷어내 실제 LOCAL 줄만 남긴다.
/// </summary>
/// <param name="previous">고치기 전 편집기 줄(필러 포함)</param>
/// <param name="wasFiller">그때 어느 줄이 필러였는지</param>
/// <param name="current">지금 편집기 줄</param>
public static List<string> StripFillers(IReadOnlyList<string> previous,
IReadOnlyList<bool> wasFiller,
IReadOnlyList<string> current)
{
var kept = new List<string>(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;
}
/// <summary>두 줄 목록이 같은지. 편집기 글을 다시 써야 하는지 판단할 때 쓴다.</summary>
public static bool SameLines(IReadOnlyList<string> a, IReadOnlyList<string> 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;
}
}
}