Ubisam-MergeTool/doc/작업지시-머지툴보강.md
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

264 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MergeHub 확장 — 머지툴 기본기 보강
> 작성 2026-09-07 · 상태: 착수 전 · 선행 확인 3건 있음(맨 아래)
>
> 새 세션에서 이어서 작업할 때는 이 문서를 읽고 시작하면 된다.
## 배경
MergeHub는 사내 Git 머지/비교 도구다. 지금은 **Git 저장소 안의 파일만**, **C# 소스만**,
**BASE ↔ LOCAL 2분할로만** 비교할 수 있다. WinMerge와 비교하면 머지툴이면 당연히 있어야 할
조작(차이 탐색·무시 규칙·폴더 비교)이 빠져 있어 실무에서 쓰기 어렵다.
**방향을 다시 잡는다: 머지툴이 본체이고 Git은 그 위에 얹는 기능이다.**
Git 저장소가 아니어도, 소스 코드가 아니어도 비교가 되어야 한다.
## 프로젝트 현황
| 항목 | 내용 |
|---|---|
| 소비 프로젝트 | `D:\Sorce\MergeTool\MergeHub\src\MergeHub` (net48, OutputType=Library) |
| 플랫폼 | UbisamBase 호스트+플러그인. `D:\UbisamPlatform\bin` 을 `Reference`+`Private=false` 로 참조, `CopyPlatformRuntime` 타겟이 런타임 복사 |
| 플랫폼 소스 | `D:\Sorce\MergeTool\UbisamPlatform` — 가이드는 `doc\platform-guide.md` |
| 실행 | `bin\Release\net48\UbisamBase.Shell.exe` |
| MVVM | CommunityToolkit.Mvvm 8.3.2 (`[ObservableProperty]`, `[RelayCommand]`) |
| Git | LibGit2Sharp 0.32.0 |
### 관련 파일
```
Views/HomeView.xaml(.cs) 머지 화면. 현재 [커밋트리 | BASE | LOCAL | ACTIONS] 4열
ViewModels/HomeViewModel.cs ~900줄. 정렬·적용·Undo/Redo·전체편집
Views/GitView.xaml(.cs) Git 조작 탭
Services/LineAligner.cs LCS 줄 정렬. Align(baseLines, localLines) → AlignedRow 목록
Services/GitService.cs Git API 60여 개. GetThreeWayContent(path) 이미 있음
Services/GitWorkspace.cs DI 싱글턴. 저장소 상태·세마포어 게이트
Services/CodeSanityChecker.cs 코드 이상 검사
Services/MergeNote.cs 머지 주석 생성
Views/Controls/CSharpTokenizer.cs C# 전용 토크나이저. CodeTokenKind 15종
Views/Controls/CodeTextBlock.cs TextBlock 파생. Code/Words/InBlockComment/HighlightSyntax DP
Views/Controls/ChangeMiniMap.cs 미니맵
Models/MergeLine.cs 한 행. LineHighlight { None,Changed,Applied,Edited,Added,Removed }
MergeHubSettings.cs IgnoreWhitespace, WordLevelDiff, SyntaxHighlight 등
```
---
## 참고자료와 라이선스 — 먼저 읽을 것
**WinMerge 소스 코드를 받거나 읽지 말 것.** GPLv2+ 라 코드를 참고해 옮기면
MergeHub 전체가 GPL 의무에 걸린다. C++/MFC 라 어차피 C#/WPF 로 옮길 수도 없다.
대신 이것들을 쓴다.
| 목적 | 참고할 것 |
|---|---|
| 조작 감각·UI 관례·단축키 | **WinMerge 설치본을 직접 써 볼 것** (UI 관례를 따르는 건 자유) |
| 기능 정의·필터 파일 형식 | https://manual.winmerge.org/en/ |
| 줄 정렬 알고리즘 | **Histogram diff** (git 기본 알고리즘) · Myers diff 논문 *An O(ND) Difference Algorithm* |
| 단어 단위 diff | **DiffPlex** (MIT) — 필요하면 참조 가능 |
---
## 작업 1. 머지툴 기본 조작 · UI
WinMerge 화면에 가깝게 만든다. 세 갈래다.
### 1-1. 파일 비교
- **차이 탐색** — 첫 / 이전 / 다음 / 마지막 차이로 점프.
단축키 `Alt+↑` `Alt+↓` `Ctrl+Home` `Ctrl+End`, 툴바 버튼도 같이.
현재 몇 번째 차이인지 상태바에 `차이 3 / 15` 로 표시.
- **Go to** — 줄 번호 또는 차이 번호로 이동 (`Ctrl+G`).
- **워드랩 토글** — 긴 줄을 접어서 표시. 현재는 가로 스크롤뿐.
접힌 줄은 여백에 화살표 표시. BASE/LOCAL 양쪽 줄 높이가 어긋나지 않아야 한다.
> **완료 (2026-09-07).** 여백(줄번호·`[>]`)을 행 안쪽으로 옮겨 높이가 저절로 맞게 했고,
> 가로 스크롤 때는 글자만 `TranslateTransform` 으로 밀어 여백을 고정한다.
> 접히는 폭을 맞추려고 편집기 세로 스크롤바를 항상 띄우고 표시층에 같은 폭의 여백을 줬다.
>
> 한 가지 더 필요했다 - 한쪽만 접힌 행에서 아래가 통째로 밀리는 문제.
> `MergeLine.RowHeight` 에 두 패널이 각자 글 높이를 알려 주고 **큰 쪽을 함께 쓰도록** 해서 맞췄다.
- **상태바** — 줄/열 위치, 감지된 **인코딩**, **줄바꿈 형식(CRLF/LF)** 표시.
- **북마크** — 여백 더블클릭으로 표시/해제, `F2` 로 다음 북마크.
- **읽기 전용 토글** — LOCAL 편집 잠금.
#### 1-1-A. 정렬 알고리즘 교체 — LCS → Histogram diff
현재 `LineAligner` 는 순수 LCS 다. LCS 는 `{`, `}`, 빈 줄처럼 **짧고 자주 반복되는 줄**을
엉뚱하게 짝지어 정렬이 지저분해진다. git 이 LCS 대신 histogram 을 기본으로 쓰는 이유가 그것이다.
- **Histogram diff 로 교체한다.** 원리: 양쪽에서 **드물게 나오는 줄**(빈도가 낮은 줄)을
먼저 앵커로 잡고, 그 앵커를 기준으로 앞뒤 구간을 재귀적으로 다시 정렬한다.
흔한 줄로 짝을 맞추지 않으므로 사람이 보기에 자연스러운 정렬이 나온다.
- 기존 최적화는 유지한다 — 앞뒤 공통 구간 잘라내기, 크기 상한(`MaxCells`) 넘으면 포기.
- **기존 `AlignTest` 26건이 전부 통과해야 한다.** 실패하면 정렬이 나빠진 것이므로 원인을 밝힌다.
- 새 알고리즘 전용 테스트를 추가한다. 최소한 이 두 가지:
- 같은 코드 블록이 여러 번 반복되는 파일에서 올바른 쪽에 붙는지
- 중괄호·빈 줄만 있는 구간이 엉키지 않는지
#### 1-1-B. 이동된 블록 감지
- 정렬 결과에서 **삭제 구간과 추가 구간의 내용이 같으면**
"삭제+추가" 가 아니라 **"이동"** 으로 표시한다 (`LineHighlight` 에 `Moved` 추가, 별도 색).
- 메서드 순서만 바꾼 경우가 흔한데, 지금은 전부 삭제+추가로 보인다.
- 양쪽 블록을 선으로 잇거나 "→ 42행으로 이동" 같은 힌트를 준다.
- 미니맵과 범례에도 반영한다.
### 1-2. 무시 규칙
지금 `MergeHubSettings.IgnoreWhitespace` 는 **`GitService` 의 patch 파싱에만** 걸려 있고
홈 화면의 `LineAligner` 비교에는 적용되지 않는다. 이걸 바로잡고 확장한다.
- **공백 3단계** — 전부 비교 / 개수 변화만 무시 / 완전 무시
- **대소문자 무시**
- **줄 필터** — 정규식에 맞는 줄은 차이로 치지 않는다.
타임스탬프·빌드번호·`$Id$` 같은 것. 규칙 여러 개를 이름 붙여 저장/재사용.
- 무시 규칙은 **툴바에서 즉시 토글**되고, 바꾸면 화면이 바로 다시 계산되어야 한다.
현재 켜진 규칙을 상태바에 요약 표시.
- 무시로 처리된 줄은 "차이 없음" 과 구분되는 옅은 색으로 표시.
- 구현 위치 주의 — 무시 규칙은 **정렬 전에 비교용 키를 만드는 단계**에 넣는다.
화면에 보이는 원문은 그대로 두고, 비교할 때만 정규화한 문자열을 쓴다.
### 1-3. 폴더 비교
새 화면. 파일 하나가 아니라 **폴더 두 개**를 비교한다.
- 비교 방식: **전체 내용 / 빠른 내용(크기+수정일)** 선택. 큰 파일은 자동으로 빠른 비교.
- **트리 뷰** (하위 폴더 재귀) + 목록 뷰
- 각 항목 상태: 같음 / 다름 / 왼쪽만 / 오른쪽만 / 이진파일
- 파일 필터 — 정규식 포함·제외. 규칙 세트 저장
- 항목 더블클릭 → 기존 파일 비교 화면으로
- 좌↔우 복사, 삭제. **파괴적 동작은 확인 대화상자 필수**
- 결과 요약: 전체 N개 · 다름 N · 왼쪽만 N · 오른쪽만 N
---
## 작업 2. Git 유무에 따라 화면 바꾸기
- 연 경로에서 **`.git` 을 상위로 거슬러 탐색**한다 (`GitService.Discover` 이미 있음).
- **있으면** — 지금처럼 커밋 이력 패널을 보여주고, BASE를 커밋에서 불러온다.
- **없으면** — 커밋 이력 패널을 **숨기고**, Git 탭도 숨기거나 비활성화한다.
대신 **"파일 두 개를 직접 골라 비교"** 모드로 동작한다 (왼쪽/오른쪽 파일 선택).
- 전환은 자동이며, 사용자에게 현재 어느 모드인지 명확히 보여야 한다.
- 폴더 비교도 마찬가지 — Git 없이 동작해야 한다.
**판정 기준**: Git이 아닌 아무 폴더의 텍스트 파일 두 개를 열어 비교·편집·저장까지 된다.
---
## 작업 3. 진짜 3-Way 비교
기획안에는 "3-Way Merge" 라고 적혀 있으나 **실제는 2분할**이다.
1번 칸은 커밋 트리이고 BASE ↔ LOCAL만 비교한다.
- 패널을 **BASE(공통 조상) / LOCAL(내 작업) / REMOTE(상대 변경)** 3분할로 만든다.
- `GitService.GetThreeWayContent(path)` 가 이미 세 버전을 돌려준다 — 이걸 쓴다.
- `LineAligner` 를 **3열 정렬**로 확장한다. 지금은 2열이다.
권장 방식: **BASE 를 축으로 BASE↔LOCAL, BASE↔REMOTE 를 각각 정렬한 뒤 BASE 행 기준으로 합친다.**
세 쪽 모두에 대응되는 행, 두 쪽만 있는 행, 한 쪽만 있는 행이 한 행에 정렬되어야 한다.
- 각 줄에서 **LOCAL로 가져오기 / REMOTE로 가져오기 / 양쪽 다** 를 고를 수 있어야 한다.
- **충돌 판정**: BASE 대비 LOCAL과 REMOTE가 서로 다르게 바뀐 줄을 별도 색으로.
- 커밋 트리 패널은 접거나 폭을 줄일 수 있게 한다 — 3분할이면 가로가 부족하다.
- Git이 없거나 3-way 대상이 아니면 **2분할로 자동 전환**.
---
## 작업 4. 소스 아닌 파일 · 다른 언어
지금 구문 강조는 `CSharpTokenizer` 하나뿐이고, `CodeSanityChecker.AppliesTo` 도
`.cs .cpp .c .h .hpp .java .js .ts` 만 받는다.
- 토크나이저를 **언어별로 갈아끼울 수 있는 구조**로 바꾼다.
`CSharpTokenizer` 를 `ITokenizer` 뒤로 넣고 확장자로 고른다.
기존 `Tokenize(line, inBlockComment, out stillInBlockComment)` 시그니처(줄 넘어가는 블록 주석 상태)는 유지한다.
- 최소 지원: **C 계열**(C/C++/C#/Java/JS/TS) · **XML/HTML** · **JSON** · **SQL** ·
**Python** · **INI/설정** · **Markdown** · **일반 텍스트(강조 없음)**
- **일반 텍스트가 기본값**이다. 모르는 확장자는 강조 없이 비교만 되면 된다.
`.txt .md .csv .log .xml .json .yml` 등 문서 파일이 정상 동작해야 한다.
- **이진 파일 감지** — NUL 바이트가 있으면 텍스트로 열지 않고
"이진 파일 · 같음/다름" 만 알린다. 열어서 깨뜨리면 안 된다.
- `CodeSanityChecker` 는 **코드 파일에서만** 돌린다. 문서에서는 조용해야 한다.
- 인코딩 판별(BOM → UTF-8 엄격 → CP949)은 지금 로직을 그대로 쓴다.
- 기존 `TokenTest` 63건은 C# 토크나이저용이다 — **그대로 통과해야 한다.**
---
## 지켜야 할 것
- **주석은 한글**로, 기존 코드와 같은 밀도로. "무엇을" 이 아니라 **"왜"** 를 적는다.
- **빌드 경고 0 · 오류 0** 을 유지한다.
- 새 알고리즘(histogram 정렬, 3열 정렬, 이동 블록 감지, 무시 필터)은
**스크래치패드에 테스트 하네스**를 만들어 검증하고 결과 건수를 보고한다.
기존 하네스 방식(`AlignTest`, `SanityTest`, `TokenTest`)을 따른다.
- **기존 테스트를 깨뜨리지 말 것.** 깨지면 원인을 밝히고 보고한다.
- **PowerShell `Get-Content`/`Set-Content` 로 프로젝트 파일을 읽고 쓰지 말 것.**
PS 5.1이 UTF-8을 ANSI로 읽어 한글 주석을 손상시킨다. Read/Write/Edit 도구를 쓴다.
- 플랫폼(`UbisamPlatform`)을 고쳐야 하면 **먼저 알리고**, 고친 뒤에는
Core → Shell 빌드 → `scripts\publish-platform.ps1` → MergeHub 재빌드 순서를 지키고
`doc\history\changelog.md` 에 기록한다.
- **커밋·푸시하지 않는다.** 필요하면 먼저 확인받는다.
## 하지 말 것
- **WinMerge 소스 코드를 받거나 읽지 말 것** (GPLv2+ — 위 라이선스 항목 참조).
- WinMerge를 따라가되 **우리 목적과 무관한 것은 넣지 않는다** —
이미지 비교, CSV 표 형식 비교, 압축파일 내부 비교, 플러그인 시스템(ActiveX).
## 진행 방식
권장 순서:
```
1-1-A 정렬 교체(histogram) ← 다른 모든 것의 품질이 여기에 달림
1-1 차이 탐색 · Go to · 워드랩 · 상태바 · 북마크
1-1-B 이동 블록 감지
1-2 무시 규칙
2 Git 유무 분기
4 다국어 · 문서 · 이진파일
3 3-Way
1-3 폴더 비교
```
정렬 교체를 맨 앞에 두는 이유 — 이동 블록 감지와 3-Way 정렬이 모두 그 위에 얹히므로
나중에 바꾸면 두 번 일한다.
**각 항목을 끝낼 때마다 빌드하고 실제로 실행해 화면을 확인한 뒤 보고할 것.**
전부 끝내고 한 번에 보고하지 말 것.
---
## 시작 전에 확인할 것 (미해결)
1. **3-Way의 REMOTE는 무엇인가** — 병합 충돌 중일 때의 `MERGE_HEAD` 인가,
아니면 사용자가 고른 임의의 브랜치·커밋인가? 후자면 브랜치 선택 UI가 필요하다.
2. **폴더 비교에서 Git 커밋 두 개 사이 비교**도 필요한가, 아니면 파일시스템 폴더 두 개만인가?
3. **가로 공간** — 3분할 + 커밋트리 + ACTIONS 를 한 화면에 넣을지,
커밋 트리를 접는 방식으로 갈지.
---
## 진행 기록
| 날짜 | 항목 | 결과 |
|---|---|---|
| 2026-09-07 | 문서 작성 | 착수 전 |
| 2026-09-07 | **패널 이름·머리 정렬 · C#/C++ 데모 · 탭 인디케이터** | **완료.** ① 패널 이름을 모드와 상관없이 **BASE / LOCAL** 로 통일(`기준 파일`·`내 파일` 삭제). ② **경로 줄 높이를 맞췄다** — LOCAL 머리에만 있던 `전체 반영`·`전체 되돌리기` 를 ACTIONS 로, BASE 머리에만 있던 파일 목록 콤보를 상단 툴바로 옮겨 두 머리를 `제목 + 부제 + 경로` 로 똑같이 만들었다. 옮긴 버튼들은 줄 단위 반영과 성격이 같아 ACTIONS 가 원래 자리이기도 하다. ③ **C# · C++ 데모 파일** 을 모두 만들었다 — 파일 비교 기본값은 `WeldController_기준.cs ↔ _내작업.cs`, 같은 폴더에 `.cpp` 쌍도 있고, 폴더 비교 데모(`folder-demo`)에는 `.cs`·`.cpp`·`.h`·문서가 섞여 있다. Git 데모 저장소에는 `WeldController.cpp` 를 새 커밋으로 넣고 거기서 갈라지는 `feature/cpp-tuning` 을 만들어, **C++ 파일로도 제대로 된 3-Way**(merge base 가 그 파일을 갖고 있음)가 된다. ④ **탭 인디케이터 결함**: 폴더 비교에서 파일을 더블클릭하면 내용과 강조는 파일 비교로 넘어가는데 **아래 파란 막대만 폴더 비교에 남았다.** 코드로 탭을 옮기면 그 탭 화면이 그 자리에서 만들어지느라 배치가 밀리는데, `MoveTabIndicator` 는 그때 막대를 0 으로 만들려 했고 **진행 중인 애니메이션이 값을 붙들고 있어 그 지정이 무시**됐다 — 결국 옛 자리 그대로였다. 배치가 끝난 뒤 한 번 다시 재도록 고치고(무한 재시도 방지 포함), 선택이 아예 없을 때는 애니메이션을 먼저 끊고 지우게 했다. **곁들여 고친 것**: 파일 두 개를 직접 고른 모드에서 **코드 검사가 늘 "검사 대상 아님"** 이었다 — `SelectedFile` 은 Git 모드에서만 채워지는데 그걸 보고 있었다. `CurrentFilePath` 로 바꿔 `.cs`·`.cpp` 모두 검사된다(확인: `이상 없음`). `전체 되돌리기` 버튼 글자가 두 칸 배치에서 잘리던 것도 한 줄에 하나씩으로 폄. |
| 2026-09-07 | **폴더 비교 → 파일 비교 더블클릭이 안 먹던 것** | **완료.** 기능은 이미 있었는데 **탭 순서를 바꾸면서 깨졌다**. 탭 내용은 그 탭을 <b>처음 열 때</b> 만들어진다. 폴더 비교가 첫 화면이 되면서, 파일을 처음 더블클릭하는 순간에는 파일 비교 화면이 아직 없어 요청 이벤트를 받을 사람이 없었다. 요청은 허공에 사라지고, 뒤이어 만들어진 화면은 기본 데모 파일을 열었다 - 겉보기에 "더블클릭이 안 먹는다". `GitWorkspace` 가 요청을 **남겨 두고**, 화면이 만들어질 때 스스로 집어 가도록 고쳤다(`HasPendingPlainCompare` / `DeliverPendingPlainCompare`). **곁들여 고친 것**: ① 한쪽에만 있는 파일(왼쪽만·오른쪽만)은 열리지 않았다 — 오히려 그런 파일이야말로 "뭐가 들었길래 새로 생겼나"를 보고 싶은 대상이다. 없는 쪽을 빈 내용으로 열어 전부 추가(또는 삭제)로 보이게 했다. ② 열 수 없는 항목(폴더·이진 파일)을 눌러도 **아무 일도 일어나지 않아** 기능이 없는 줄 알게 됐다. 왜 못 여는지 토스트로 알린다. ③ Enter 로도 열린다. ④ 클릭한 자리에서 항목을 직접 집는다 — 펼침 화살표처럼 선택을 바꾸지 않는 자리를 두 번 누르면 엉뚱한 항목이 열렸다. ⑤ 안내 문구가 없어진 탭 이름("홈 화면")을 가리키고 있던 것을 고쳤다. **실행 확인**: 프로그램을 새로 켠 뒤 **첫 더블클릭**부터 바로 열림 — 양쪽 다 있는 `Weld.cs`(총 5 라인·차이 2 라인), 왼쪽에만 있는 `Legacy.cs`(총 1 라인·차이 1 라인) 모두 정상. 폴더는 접기/펼치기만 된다. |
| 2026-09-07 | **탭 순서·용어 정리 · 데모 전환 · 모니터 2 확인** | **완료.** ① **탭 순서를 실제 작업 순서로**: `폴더 비교 → 파일 비교 → Git`. 사용자가 짚은 대로 "폴더를 먼저 보고 고칠 파일을 골라 들어가는" 흐름이 맞다. `홈` 이라는 이름도 하는 일과 맞지 않아 `파일 비교` 로 바꿨다. 폴더 비교에서 더블클릭하면 파일 비교 탭으로 넘어가므로 흐름이 이어진다. ② **용어를 머지툴 표준으로**: `LOCAL (Mine)` · `BASE` · `REMOTE (Theirs)` · `MERGED`, 버튼은 `Take Mine` / `Take Theirs`, 범례는 `Theirs only` / `Conflict`. 한글 풀이(내 작업 원본·공통 조상·고른 브랜치·최종 결과)와 곁들인 설명 줄은 지웠다 — 이 용어는 Git 과 다른 머지툴에서 그대로 쓰이므로 여기서만 다르게 부르면 옮겨 다닐 때 오히려 헷갈린다. ③ **데모 전환 버튼**(발표용): 상단의 `데모 → .git 있음/없음` 하나로 Git 저장소 화면과 파일 비교 화면을 즉시 오간다. 설정에 `DemoGitPath`·`DemoPlainPath` 를 넣었고 둘 다 실재할 때만 버튼이 뜬다. **여기서 결함 둘을 잡았다** — (1) 전환 중 3-Way 를 끄면서 부른 재로드가 **작업 게이트를 먼저 잡아** 뒤따르는 저장소 닫기·파일 비교가 "다른 작업 진행 중"으로 통째로 건너뛰어져 화면이 빈 채 남았다(`_suspendReload` 로 차단). (2) 늦게 끝난 Git 본문 로드가 이미 바뀐 화면을 덮어써 **경로는 새 파일인데 내용은 옛 파일**이 됐다(양쪽 로드의 UI 반영 직전에 모드가 그대로인지 확인). **실행 확인**: 21줄 레시피 ↔ 34줄 StartWeld.cs 를 세 번 왕복해도 매번 정확. ④ **3-Way 워드랩 재검증**: LOCAL 4줄 접힘 · REMOTE 3줄 접힘 · BASE 빈칸인데도 `void StartWeld()` 가 세 칸 **정확히 같은 y**. 아래 MERGED 는 폭이 넓어 같은 줄이 한 줄에 들어가고 빈칸이 안 생긴다. ⑤ **모니터 2 확인**(1920×1080 @100%, 모니터 1 은 2560×1600 @150%): 4분할·워드랩·용어 모두 정상, 배율 때문에 깨지는 곳 없음. 상단 툴바만 폭에 맞춰 두 줄로 접힌다(정상 동작). |
| 2026-09-07 | **워드랩 4분할 대응 · 전체화면 점검** | **완료.** ① 3-Way 로 패널이 넷이 되면서 행 높이 계산이 어긋나 있었다 — `MergeLine.ReportTextHeight` 가 BASE·LOCAL **둘만** 보고 있어 REMOTE·LOCAL원본이 접혀도 반영되지 않았다. 더 중요한 것은 **맞춰야 할 묶음이 하나가 아니라는 점**이다. 3-Way 에서 위 원본 세 칸은 폭이 같아 서로 맞춰야 하지만, 아래 MERGED 는 폭이 가로 전체라 접히는 자리가 애초에 다르다. 같은 높이를 강요하면 MERGED 는 안 접힌 줄에 빈칸이 생기고 그 위에 겹친 편집기와도 어긋난다. 그래서 높이를 **`RowHeight`(MERGED·편집기)** 와 **`SourceRowHeight`(원본 세 칸)** 둘로 나눴다(2-Way 에서는 같은 값). ② 같은 이유로 글자 폭 보정(`MatchTextWidths`)도 모드를 타게 했다 — 2-Way 는 BASE 가 미니맵 폭만큼 물러나야 LOCAL 과 맞지만, 3-Way 에서 그러면 **BASE 만 좁아져** 위 세 칸의 접히는 자리가 어긋난다. 모드가 바뀌면 다시 재도록 `IsThreeWay` 변경을 구독한다. ③ 폴더 비교의 줄 배경이 **글자 끝에서 끊겨** 넓은 화면에서 색 띠가 중간에 잘려 보였다. TreeViewItem 기본 템플릿이 머리를 `Auto` 폭으로 잡아 `HorizontalContentAlignment=Stretch` 가 듣지 않는다 — 템플릿을 갈아끼우면 플랫폼 테마를 잃으므로 배경 띠에 트리 폭만큼 `MinWidth` 를 주고 넘치는 부분은 잘리게 뒀다. ④ `줄바꿈 끄기` 버튼 글자가 칸을 넘어 잘리던 것을 `줄바꿈 켬/끔` 으로 맞춤(`필터 켬/끔` 과 같은 꼴). **실행 확인**(2560×1600 최대화): 2-Way 는 BASE 의 빈 블록이 LOCAL 의 접힌 높이를 그대로 따라가 `void StartWeld()` 가 같은 눈높이. 3-Way 는 LOCAL 4줄 · REMOTE 3줄로 접힘이 다른데도 위 세 칸이 같은 높이로 맞물리고, 아래 MERGED 는 폭이 넓어 **한 줄에 다 들어가며 빈칸이 안 생긴다**. 접힌 줄에서 캐럿도 논리 행/열을 정확히 보고한다(`End` → `5행 67열`, 시각 줄이 아니라 실제 줄 기준). 폴더 비교도 실제 프로젝트 폴더(`Views` ↔ `Services`, 27개)로 전체화면 확인. |
| 2026-09-07 | **1-3 폴더 비교 (별도 탭)** | **완료.** 새 파일 `Services/FolderComparer.cs` · `ViewModels/FolderViewModel.cs` · `Views/FolderView.xaml` · 테스트 **27/27**. 홈에 넣지 않고 **탭을 따로** 뒀다 — 홈은 3-Way 에서 이미 네 칸을 쓰고 있어 트리를 얹을 자리가 없다. 담은 것: 트리 뷰(하위 폴더 재귀) · 상태 5종(같음 / 다름 / 왼쪽만 / 오른쪽만 / **다름(이진)**) · **전체 내용 ↔ 빠른 비교(크기+수정일)** 전환 · 한도(기본 8MB)를 넘는 파일은 전체 비교를 골라도 자동으로 빠른 비교 · 정규식 **포함·제외** 필터(기본 제외에 `.git` · `bin` · `obj` · `.dll` · `.pdb`) · 같은 파일 숨기기 · 요약(`전체 N개 · 다름 N · 왼쪽만 N · 오른쪽만 N · 같음 N`) · **좌↔우 복사와 삭제(원본·대상 경로를 그대로 보여 주는 확인 대화상자 필수, 기본 선택은 취소)** · 항목 더블클릭 → 홈 탭에서 줄 단위 비교. 비용이 드는 것은 파일을 여는 일뿐이라 **열기 전에 거를 수 있는 것을 먼저 거른다** — 크기가 다르면 내용을 안 보고, 내용 비교도 64KB 씩 읽다가 다른 곳을 만나면 즉시 멈춘다. 실수로 `C:\` 를 고른 경우를 대비해 5만 항목에서 끊고 요약에 그렇다고 적는다. **실행 확인**: 데모 폴더 두 개 → `절차서.txt`·`Weld.cs` 다름, `Legacy.cs` 왼쪽만, `New.cs` 오른쪽만, `bin/tool.dll` 은 기본 제외 규칙으로 빠짐(`전체 5개 · 다름 2 · 왼쪽만 1 · 오른쪽만 1 · 같음 1`). `Weld.cs` 를 더블클릭하니 홈 탭으로 넘어가 두 파일이 줄 단위로 열렸다(`1800→2000`, `Emit→EmitFast` 단어 단위 강조). **곁들여 고친 플랫폼 결함**: 코드로 탭을 옮기면(새 API `TabManager.Activate`) **내용만 바뀌고 네비게이션 강조는 그대로 남아** 두 곳이 어긋났고, 그 상태에서 강조된 탭을 눌러도 이미 선택된 항목이라 아무 일도 일어나지 않았다. |
| 2026-09-07 | **3-Way 를 4분할로 재구성 · 캐럿 가로 어긋남** | **완료.** ① 사용자 지적대로 **일반적인 3-Way 도구 구성(원본 3 + 결과 1)**으로 바꿨다. 위에 **LOCAL(내 작업 원본) · BASE(공통 조상) · REMOTE(고른 브랜치)** 를 읽기 전용으로 나란히, 아래에 **MERGED(최종 결과)** 를 폭 전체로 두고 여기서만 고친다. 원본을 넷째 칸으로 붙이지 않은 이유 — 한 칸이 40자도 안 돼 코드를 읽을 수 없다. 세로를 쓰면 위는 68자, 아래는 200자 넘게 나온다. 2-Way 는 아래 두 줄이 높이 0 이라 지금까지의 좌우 배치 그대로다. `MergeLine.OriginalLocalText` 를 새로 붙들어 둔다 — **이게 없으면 REMOTE 를 한 번 가져오는 순간 내 원래 내용이 화면에서 사라져** 정작 충돌을 판단할 근거를 잃는다. LOCAL 패널의 `[<]` 와 ACTIONS 의 `내 것으로` 가 Accept Mine, REMOTE 쪽 `[<]` 와 `저쪽 것으로` 가 Accept Theirs 다. 위 세 칸은 3-Way 에서 머리를 74px 로 줄이고 바닥을 감춰(같은 정보가 아래 상태바에 있다) 코드를 더 보여 준다. ② **캐럿이 글자 가운데에 박히던 것**: LOCAL 표시층의 본문 테두리(문제 줄 표시용 2px)가 레이아웃 폭을 먹어 **BASE·REMOTE 및 그 위에 겹친 편집기보다 글자가 2 DIP 오른쪽에서 시작**하고 있었다. 테두리와 안여백의 합을 세 패널 모두 2 로 맞춰 해결. ③ **상태바 열 번호가 틀리던 것**: `TextBox.GetLineIndexFromCharacterIndex`·`GetCharacterIndexFromLineIndex` 는 "화면에 보이는 줄" 기준이라 글을 갈아 끼운 직후 `CaretIndex` 와 어긋난다(11글자 줄에서 Home 을 눌렀는데 `10열`). 글에서 직접 세도록 바꿔 Home=1열·End=길이+1 로 정확해졌다. |
| 2026-09-07 | **작업 4 다른 언어·문서 지원** | **완료.** 새 파일 `Services/LanguageProfile.cs`(언어 표 + `LanguageCatalog`) · `Services/CodeTokenizer.cs`(표를 보고 도는 범용 토크나이저) · 테스트 **40/40**, 기존 하네스 전부 유지(Align 37 · Move 11 · Editor 14 · Rules 21 · Sanity 20 · Token 15 · Note 16 · ThreeWay 20). 언어마다 토크나이저를 만들지 않았다 — 색칠에 필요한 것은 "주석은 어디서 시작하나 · 문자열은 무엇으로 묶나 · 예약어는 무엇인가" 셋뿐이고 나머지는 어느 언어나 같아서, **표 하나에 적고 토크나이저 하나가 그 표를 본다**. C# 만 전용 토크나이저를 그대로 쓴다(축자·보간 문자열 `@"" $""` 과 접근제한자 구분은 표로 담기지 않고 이미 테스트로 굳어 있다). 담은 것: C# · C/C++ · Java · JS/TS · Python · SQL(대소문자 무시) · 셸/배치(`rem` 포함) · XML/HTML/XAML · JSON · YAML · INI/설정 · Markdown · **평문(기본값)**. 모르는 확장자는 평문이라 **색을 잘못 입히는 일이 없다**. `CodeSanityChecker.AppliesTo` 의 확장자 switch 를 걷어내고 언어 표의 `HasBraces` 하나로 판단하게 바꿔, 괄호로 블록을 여닫지 않는 형식(YAML·INI·평문)에는 검사를 걸지 않는다. 상태바에 `UTF-8 · LF · Python` 처럼 고른 언어를 띄운다. **이진 파일**은 앞 8KB 에 NUL 이 있으면 이진으로 보고(UTF-16 BOM 은 예외) 비교하지 않고 그렇다고 알린다 — 깨진 글자로 화면을 채우지 않기 위해서다. **실행 확인**: `.py` 두 개를 비교하니 `#` 주석·`def`/`if`·독스트링·숫자가 파이썬 규칙대로 칠해지고, 상태바가 `Python`, 코드 검사는 `검사 대상 아님`(파이썬은 중괄호를 쓰지 않는다)으로 나왔다. |
| 2026-09-07 | **작업 3 3-Way 비교** | **완료.** REMOTE 는 **사용자가 고르는 브랜치**다(상단 `3-Way 상대` 드롭다운). 고르면 BASE 가 `merge-base(HEAD, 고른 브랜치)`, LOCAL 은 워킹트리, REMOTE 는 그 브랜치의 파일이 된다 — BASE 를 HEAD 로 두면 "저쪽이 고친 것"과 "내가 고친 것"을 가를 수 없어서 반드시 공통 조상이어야 한다. 병합 중이면 브랜치를 고를 것도 없이 Git 이 들고 있는 세 벌(`GetThreeWayContent`)을 그대로 쓴다. 새 파일 `Services/ThreeWayAligner.cs` · 테스트 **20/20**. 세 글을 한꺼번에 정렬하지 않고 **BASE↔LOCAL · BASE↔REMOTE 두 번 정렬한 뒤 BASE 줄 번호로 맞물린다** — 검증된 2-way histogram 정렬기를 그대로 쓰기 위해서다. 양쪽이 같은 자리에 넣은 줄은 한 번 더 정렬해 짝지어 한 행으로 합친다(안 합치면 "둘 다 같은 줄 추가"가 충돌로 보인다). 판정은 `MergeLine.ThreeWay` — 같음 / 나만 고침 / 저쪽만 고침 / 둘 다 같게 / **충돌**. 색은 저쪽만 고침=남색, 충돌=자홍(가장 눈에 띄게, 이동·단순 차이보다 앞세움), 범례에도 3-Way 일 때만 두 항목이 붙는다. REMOTE 줄의 `[<]` 버튼과 ACTIONS 의 `저쪽 것으로` 로 그 줄을 가져오고, 되돌리기 스택은 BASE 반영과 같은 것을 쓴다. 3-Way 를 안 쓰면 REMOTE 칸은 **폭 0 으로 접힌다**(Visibility 만 숨기면 star 열이 자리를 그대로 차지한다). **실행 확인**: 데모 저장소에 공통 조상에서 갈라진 `feature/cooling` 을 만들어 붙였더니 `laser.Power`·`Wait()` 두 줄이 **충돌 2**, `fan.Speed = 70;` 이 **저쪽만 고침 1** 로 잡히고, `[<]` 로 가져오니 머지 주석까지 붙어 LOCAL 에 들어왔다(편집 1건). 세 패널의 같은 행이 정확히 같은 눈높이. **곁들여 고친 것**: `LoadFileAsync` 도 `RunAsync` 실패 시 화면을 갈아엎지 않게 막았다(파일 비교에서 겪은 것과 같은 결함). |
| 2026-09-07 | **캐럿 어긋남 · 입력 지연 · 화면 배율 · 데모 기본값** | **완료.** ① **캐럿이 글자에 안 맞던 원인**: 편집기와 표시층을 맞출 때 기준이 서로 다른 두 좌표를 견줬다. 별도 WPF 프로브(`scratchpad/LineProbe`)로 직접 재 보니 Consolas 12·줄높이 20 에서 `TextBox.GetRectFromCharacterIndex(0).Top = 4.67`(줄상자 위 여백을 지난 **글자 상자** 위쪽), `TextBlock` 의 `(0,0) = 0`(**줄상자** 위쪽). 이 여백만큼 표시층이 아래로 밀려 선택 영역이 글자보다 반 줄 위에 있는 것처럼 보였다. 여백을 `BaselineOffset − FontFamily.Baseline×FontSize` 로 글꼴에서 직접 구해 빼도록 고침(상수를 안 써서 배율이 바뀌어도 따라간다). 덧붙여 **BASE 패널에도 같은 이동값**을 걸었다 — LOCAL 만 밀면 두 패널의 줄이 6px 어긋난다(UIA 로 확인: 어긋남 0). ② **입력 지연의 남은 절반**: 화면 글자는 편집기가 아니라 표시층이 그리는데(편집기 글자는 투명) 표시층을 600ms 재정렬 타이머에만 맡겨 **친 글자가 그때까지 안 보였다**. `ShowTypedLineNow` 로 캐럿이 있는 줄만 즉시 반영(줄 수가 달라지면 재정렬에 맡김). **실행 확인**: 타이핑 120ms 뒤 촬영해 `-RC7` 이 이미 보이고 캐럿이 글자 끝에 정확히 붙음. ③ **화면 배율**: 지금까지 상·하단 바만 커지고 정작 작업 화면은 그대로였다. 플랫폼 `ShellWindow` 의 콘텐츠에 `LayoutTransform` 을 걸고 `UiSettingsService.ContentScale`(= ChromeScale ÷ 기준 1.2, 즉 "100% = 지금 그대로") 추가. **실행 확인**: 80% 에서 코드 행 높이 30px → 24px(UIA 측정), 같은 화면에 더 많은 줄. ④ **데모를 .git 없는 폴더로**: `plain-compare` 의 용접 레시피 두 개(`WeldRecipe_기준.txt` ↔ `WeldRecipe_내작업.txt`)를 기본으로 열게 함(`DefaultLeftFile`/`DefaultRightFile` 설정 추가). **곁들여 고친 실제 결함**: 저장소가 아닌 폴더에 자동 열기를 걸면 실패할 뿐 아니라 **작업 게이트를 쥐고 있어 뒤따르는 파일 비교가 통째로 취소**돼 화면이 `차이 없음` 으로 비었다. `GitWorkspace.LooksLikeRepository` 로 저장소일 때만 자동 열기를 시도하고, `LoadPlainFilesAsync` 도 `RunAsync` 실패 시 화면을 갈아엎지 않게 함. |
| 2026-09-07 | **작업 2 Git 유무 분기** | **완료.** `.git` 이 없으면 커밋 이력 칸을 접고(폭 0) Git 상태·새로고침·파일 목록·커밋 상세·커밋 메시지를 숨긴다. 대신 상단에 **기준 파일 / 내 파일** 고르기가 나오고, 패널 제목이 `기준 파일 / 내 파일` 로 바뀌며(Git 용어 안 씀), 저장 버튼이 `Save & Commit` → `저장`(커밋 없이 오른쪽 파일에 쓰기)이 된다. 모드는 상단 배지로 항상 보인다. 새 파일 `Services/TextFile.cs` — 인코딩·줄바꿈 판별 규칙을 `GitService` 와 동일하게 맞춰, 같은 파일을 어느 경로로 열든 결과가 같고 저장할 때 형식이 바뀌지 않는다. **실행 확인**: Git 아닌 폴더의 `left.txt`↔`right.txt` 비교 → 다름 1 · 추가 1, 전체 반영 후 저장하니 파일이 정확히 갱신됨(머지 주석 포함). Git 모드 복귀도 이상 없음. |
| 2026-09-07 | **설정 반영·패널 높이·ACTIONS 잘림 수정** | 플랫폼 `SettingsService` 인스턴스 분리 버그를 고쳐(아래 플랫폼 변경 이력) **설정 → 저장 → 홈 복귀만으로 재비교**되게 함(`RecompareIfSettingsChanged`, 홈이 다시 보일 때 설정 서명이 바뀌었으면 한 번만). BASE/LOCAL 하단 요약 칸을 공통 높이(`PaneFooterHeight` 54)로 맞춰 두 패널의 코드 영역 크기를 같게 함 — LOCAL 쪽 3줄을 2줄로 합침. ACTIONS 패널에 스크롤을 주고 폭을 196 → 214 로 넓혀 잘리지 않게 함. |
| 2026-09-07 | **타이핑 지연 해소 · 버튼 재배치 · 검사 팝업** | **완료.** ① **지연의 진짜 원인**은 겹친 레이어가 아니라 글자마다 도는 두 가지였다 — 문서 전체를 문자열로 복사(`TextBox.Text` 양방향 바인딩)와 **전체 줄을 훑는 차이 판정**(무시 규칙이 켜져 있으면 양쪽 줄을 정규화). 15,005줄 파일 기준 각각 0.12ms / **2.19ms** 한 타. 바인딩을 걷어내 편집기가 글을 직접 들고(ViewModel 은 필요할 때만 읽음), 차이 덩어리 목록을 캐시하고, 캐럿이 같은 줄 안에서 움직이면 명령 상태 갱신을 건너뛰게 했다. 캐시 조회는 0.000ms. ② 차이 탐색·비교 규칙 버튼을 코드 패널 위에서 **ACTIONS 의 커밋 메시지 아래**로 옮김(읽는 자리와 조작하는 자리를 분리). 머리 높이도 150 → 124 로 복귀. ③ 코드 검사 요약을 눌러 **팝업으로 문제 목록**을 보고, 항목을 누르면 그 줄로 이동. **곁들여**: 화살표 글리프(⤒↑↓⤓)가 이 서체에 없어 빈 버튼으로 나오던 것을 글자로 교체, 파일 로드 시 차이 캐시가 갱신되지 않아 `차이 없음` 으로 잘못 나오던 것 수정. |
| 2026-09-07 | **1-2 무시 규칙** | **완료.** `Services/CompareRules.cs` · 테스트 21/21, 기존 정렬 37/37 유지. 공백 3단계 · 대소문자 · 정규식 줄 필터. 툴바에서 즉시 토글되고 상태바에 `무시: 공백 개수 · 대소문자 · 줄필터 1` 로 요약된다. **구현 갈래가 둘**이다 — 공백·대소문자는 정렬 단계의 *비교용 키*(`LineAligner.Align` 에 keys 인자 추가, 원문은 보존), 줄 필터는 정렬 뒤 행 표시(`MergeLine.IsIgnored`). 필터를 정렬 키에 섞으면 걸린 줄끼리 같은 것으로 묶여 엉뚱하게 짝지어진다. **곁들여 고친 것**: ① `MergeLine.State` 가 원문끼리만 비교해 규칙이 안 먹던 문제 → 정규화 함수를 넘겨 해결 ② 무시된 줄이 차이 탐색에 걸리던 문제. **실행 확인**: 다름 3 → (공백) 1 → (대소문자) 0, 추가 3 → (줄필터) 1 + 무시 2, `차이 - / 2` → `- / 1`. |
| 2026-09-07 | **1-1-B 이동 블록 감지** | **완료.** `Services/MoveDetector.cs` · 테스트 11/11. 지워진 구간과 추가된 구간에서 **줄이 이어지는 최장 공통 부분**을 찾아 이동으로 묶는다(구간 전체를 견주면 정렬기가 `}`·빈 줄을 한쪽에 붙여 놓아 어긋난다). 2줄 미만이거나 중괄호·빈 줄만인 구간은 제외. 색은 청록(삭제/추가와 계열 구분), 범례에 `자리 옮김` 추가, 툴팁에 `24행으로 옮김`. **실행 확인**: 데모 파일의 `Cooldown()` 을 옮겨 두니 범례가 `자리 옮김 6` 으로 잡힘. |
| 2026-09-07 | **LOCAL 행 구조 개편 (워드랩 준비)** | 여백(줄번호·`[>]`)을 별도 컨트롤에서 **행 안쪽으로** 옮겨 높이가 저절로 맞게 함. 가로 스크롤 때는 글자만 `TranslateTransform` 으로 밀고 열에서 클리핑. BASE 여백도 54로 맞추고, 세 곳의 글자 폭을 스크롤바·미니맵 폭만큼 보정해 접히는 자리를 일치시킴. 워드랩 토글 버튼 추가. **화면 확인 완료** — 가로 스크롤 시 여백 고정, 줄 번호가 두 패널에서 정확히 같은 눈높이. 워드랩도 양쪽이 같은 자리에서 접히고 행이 맞물림(`MergeLine.RowHeight` 로 큰 쪽 높이를 공유). |
| 2026-09-07 | **1-1 파일 비교 (워드랩 제외)** | **완료.** 차이 탐색(첫/이전/다음/마지막 · `Alt+↑↓` `Ctrl+Home/End`, 끝에서 순환) · 상태바(`12행 5열` · `차이 2 / 3` · 인코딩 · 줄바꿈) · `Ctrl+G` 줄 이동 · 북마크(여백 더블클릭, `F2`) · 편집 잠금. 차이는 **덩어리 단위**로 센다(12줄 삭제 = 차이 1개). 인코딩·줄바꿈 판별은 `GitService.DescribeWorkingFile` 로 노출. **워드랩은 남김** — 아래 참고. |
| 2026-09-07 | **LOCAL 패널을 상시 편집기로 전환** | **완료.** "전체 편집" 모드 버튼 삭제. LOCAL 칸이 항상 편집 가능(여러 줄 선택·붙여넣기·삭제). 화면은 세 겹 — 왼쪽 여백(줄번호·`[>]`) / 색·문법 표시층 / 투명 편집기. BASE 와 눈높이를 맞추려고 지워진 줄 자리에 빈 줄(필러)을 넣고, 저장·재정렬 전에 `EditorLayout.StripFillers` 로 걷어낸다(테스트 14/14). 타이핑이 멈추면(600ms) 다시 정렬. **곁들여 고친 것**: 편집 후 원래부터 BASE 와 달랐던 줄까지 전부 "직접 수정"으로 칠해지던 문제 → 파일을 연 시점 LOCAL 과 견줘 실제로 손댄 줄만 표시. |
| 2026-09-07 | **1-1-A 정렬 교체 (LCS → Histogram)** | **완료.** `AlignTest` 26건 유지 + 신규 11건 = **37/37 통과**. 빌드 경고 0. 데모 파일에서 중복된 `fan.Off();` 가 엉뚱한 짝에 붙던 문제가 실제로 해소됨. 안전선을 표 크기(`MaxCells` 400만 칸)에서 줄 수(`MaxRegionLines` 2만 줄)로 바꿈 — 히스토그램은 표를 만들지 않으므로 예전보다 훨씬 큰 구간도 정확히 정렬한다. |