diff --git a/doc/history/2026-09-11-5차.md b/doc/history/2026-09-11-5차.md
index 72e801f..399a5b0 100644
--- a/doc/history/2026-09-11-5차.md
+++ b/doc/history/2026-09-11-5차.md
@@ -153,3 +153,99 @@ if (HasChanges && !threeWay && string.Equals(localText, _originalLocalText, Stri
검사를 `GitOpTest` 36건으로 고쳤다. 전체 **623건 통과**(하네스 19벌) · 빌드 경고 0 · 오류 0.
**실제 저장소는 건드리지 않았다** — 데모 저장소(`mergehub-demo`) 사본으로만 했다.
+
+
+---
+
+## B-1. XAML 색칠이 사실상 없다 (2026-09-11, 완료)
+
+### 무엇이 문제였나
+
+실제 소스에 XAML 이 400개 · 42,477줄이다. C# 다음으로 많다.
+그런데 지금까지 XAML 은 `LanguageProfile.Markup` 으로 열렸고, 그것이 아는 것은 **주석과 따옴표뿐**이었다.
+태그 이름 · 속성 이름 · 속성 값이 전부 같은 색이라 사실상 평문이었다.
+
+### 왜 기존 토크나이저에 못 얹었나
+
+`CodeTokenizer` 는 **낱말을 읽어 예약어 표와 맞춰 보는** 방식이다. 마크업은 규칙이 다르다.
+같은 `Content` 가 꺾쇠 안에서는 속성 이름이고 밖에서는 그냥 글자다.
+낱말이 무엇이냐가 아니라 **어디에 있느냐**로 갈린다. 그래서 마크업만 따로 뒀다(`MarkupTokenizer`).
+
+### 줄을 넘어가는 상태를 넓혀야 했다
+
+토크나이저는 한 줄씩 따로 훑는다. 앞 줄에서 넘어오는 것은 여태 `bool inBlockComment` 하나였다.
+그런데 XAML 은 **여는 태그가 여러 줄에 걸치는 것이 예외가 아니라 보통**이다.
+시험에 쓴 실제 파일(`UCManualRCCOperation.xaml`)은 **첫 태그 하나가 14줄**이다.
+
+```xml
+
+```
+
+둘째·셋째 줄은 `<` 로 시작하지 않는다. "지금 꺾쇠 안이다" 를 넘겨주지 않으면
+**속성 이름이 그냥 글자로 보인다** - XAML 은 이런 줄이 대부분이라 색칠이 없는 것과 같다.
+
+그래서 `bool` 을 깃발 묶음(`CarryOver`)으로 넓혔다. `None` · `BlockComment` · `Tag` 셋이다.
+부르는 자리 다섯(검사기 · 폴더 비교 · 화면 VM · 화면 컨트롤 · 행 모델)을 모두 바꿨다.
+
+### 무엇을 무슨 색으로 보는가
+
+| 무엇 | 색 | 까닭 |
+|---|---|---|
+| 태그 이름 | 예약어 (파랑) | 구조를 이루는 것이라 가장 먼저 눈에 들어와야 한다 |
+| 속성 이름 | 메서드 (황토/연노랑) | 태그와 값 사이에서 따로 보여야 한다 |
+| 속성 값 | 문자열 (빨강/주황) | 다른 언어의 문자열과 같은 색 - 같은 것이다 |
+| 앞가지(`x:` · `local:`) | 타입 예약어 (청록) | XAML 에서 "어느 네임스페이스냐"는 정보다. 이름에 묻으면 `x:Name` 과 `Name` 이 같아 보인다 |
+| `< > / :` | 구두점 (회색) | 뒤로 물러나야 한다 |
+| `=` | 연산자 (회색) | 위와 같다 |
+| `` · `` · `-->`) | 초록 한 덩어리 |
+| C# 파일 색칠 | 달라지지 않았다 (문서 주석 초록 · `public` 굵은 자홍 · 타입 청록) |
+
+**검사를 새로 만들었다**(`MarkupTest` 65건). 여러 줄 태그 · 주석 이어짐 · 앞가지 · CDATA 와 함께,
+**C# · C++ · JSON 색칠이 안 망가졌는지**도 같이 본다. 토큰이 겹치거나 거꾸로 가지 않는지도 본다.
+
+전체 **688건 통과**(하네스 20벌) · 빌드 경고 0 · 오류 0.
+**실제 저장소는 읽기만 했다** - 사본을 떠서 시험했다.
diff --git a/doc/history/changelog.md b/doc/history/changelog.md
index 6fc28a7..b7e6ecc 100644
--- a/doc/history/changelog.md
+++ b/doc/history/changelog.md
@@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 |
|---|---|---|
+| 2026-09-11 | **5차 B-1** — XAML 태그·속성·값을 갈라 칠함. 여러 줄에 걸친 여는 태그도 이어진다. 줄 넘김 상태를 깃발 묶음으로 넓혔다 | 아래 |
| 2026-09-11 | **5차 A-2** — Git 작업을 시작하는 자리에서 묻게 함. 가려서 묻는 것은 접고 전부 묻는다. 덤으로 4차 A-1 이 새던 자리 셋을 막음 | 아래 |
| 2026-09-11 | **5차 A-1** — 실제 저장소 2,091개로 인코딩을 확인(깨짐 0). 판별 코드가 두 군데 복사돼 있던 것을 한 곳으로 모으고, CP949 를 한 이름으로 부르게 함 | 아래 |
| 2026-09-10 | **4차 C-5** — 한 번 정하는 것들을 한자리에 모아 접고, 차이 이동을 개수 옆으로 | 아래 |
diff --git a/doc/진행상황.md b/doc/진행상황.md
index 4f6159b..1a86d90 100644
--- a/doc/진행상황.md
+++ b/doc/진행상황.md
@@ -70,7 +70,7 @@
| 상태 | 항목 |
|---|---|
-| 대기 | B-1 XAML 색칠이 사실상 없다 (400개 · 42,477줄) |
+| **완료** | B-1 XAML 색칠이 사실상 없다 (400개 · 42,477줄) |
| 대기 | B-2 폴더 비교가 실제 저장소에서 12초 걸린다 |
### C. 실전 투입
diff --git a/src/MergeHub/Models/MergeLine.cs b/src/MergeHub/Models/MergeLine.cs
index 9065cd4..c5e17ed 100644
--- a/src/MergeHub/Models/MergeLine.cs
+++ b/src/MergeHub/Models/MergeLine.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -259,8 +259,8 @@ namespace MergeHub.Models
private bool _isMergeNote;
/// 앞 줄에서 블록 주석이 열린 채 넘어왔는지 (BASE / LOCAL 각각).
- [ObservableProperty] private bool _baseInComment;
- [ObservableProperty] private bool _localInComment;
+ [ObservableProperty] private Services.CarryOver _baseInComment;
+ [ObservableProperty] private Services.CarryOver _localInComment;
/// BASE 쪽에 이 줄이 있는지. 정렬 결과라 바뀌지 않는다.
public bool HasBase { get; }
@@ -298,7 +298,7 @@ namespace MergeHub.Models
/// REMOTE 쪽 한 줄 안에서 바뀐 구간.
[ObservableProperty] private IReadOnlyList? _remoteWords;
- [ObservableProperty] private bool _remoteInComment;
+ [ObservableProperty] private Services.CarryOver _remoteInComment;
// ---- 3-Way 에서의 LOCAL 원본 ----
//
diff --git a/src/MergeHub/Services/CarryOver.cs b/src/MergeHub/Services/CarryOver.cs
new file mode 100644
index 0000000..3b24c13
--- /dev/null
+++ b/src/MergeHub/Services/CarryOver.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace MergeHub.Services
+{
+ ///
+ /// 한 줄을 칠하고 난 뒤 다음 줄로 넘겨야 하는 상태.
+ ///
+ /// 토크나이저는 한 줄씩 따로 훑는다. 그런데 어떤 것은 줄을 넘어간다 -
+ /// /* ... */ 는 여러 줄에 걸치고, XAML 의 여는 태그도 흔히 여러 줄에 걸친다.
+ ///
+ ///
+ /// <Button Grid.Row="1"
+ /// Content="비교"
+ /// Click="OnCompare" />
+ ///
+ ///
+ /// 둘째·셋째 줄은 < 로 시작하지 않는다. "지금 꺾쇠 안이다" 를 넘겨주지 않으면
+ /// 속성 이름이 그냥 글자로 보인다 - XAML 은 이런 줄이 대부분이라 색칠이 사실상 없는 것과 같다.
+ ///
+ /// 예전에는 bool inBlockComment 하나였다. 넘길 것이 하나 더 생겨서 깃발 묶음으로 넓혔다.
+ ///
+ [Flags]
+ public enum CarryOver
+ {
+ /// 넘길 것이 없다. 줄이 스스로 완결됐다.
+ None = 0,
+
+ /// /* ... */ 또는 <!-- ... --> 가 열린 채로 줄이 끝났다.
+ BlockComment = 1,
+
+ /// 여는 태그의 꺾쇠가 아직 닫히지 않았다. 마크업에서만 쓴다.
+ Tag = 2,
+ }
+}
diff --git a/src/MergeHub/Services/CodeSanityChecker.cs b/src/MergeHub/Services/CodeSanityChecker.cs
index e4c52f6..590dd3b 100644
--- a/src/MergeHub/Services/CodeSanityChecker.cs
+++ b/src/MergeHub/Services/CodeSanityChecker.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Text;
using MergeHub.Views.Controls;
@@ -54,13 +54,13 @@ namespace MergeHub.Services
{
var problems = new List();
- var inBlockComment = false;
+ var carry = CarryOver.None;
int round = 0, curly = 0, square = 0;
for (var i = 0; i < lines.Count; i++)
{
var line = lines[i];
- var tokens = CodeTokenizer.Tokenize(line, profile, inBlockComment, out inBlockComment);
+ var tokens = CodeTokenizer.Tokenize(line, profile, carry, out carry);
// 각 글자가 어떤 종류 안에 있는지 표시해 둔다(토큰이 안 잡힌 자리는 코드로 본다).
var free = new bool[line.Length];
@@ -90,7 +90,7 @@ namespace MergeHub.Services
CountBrackets(line, free, tokens, ref round, ref curly, ref square);
}
- if (inBlockComment)
+ if ((carry & CarryOver.BlockComment) != 0)
problems.Add(new CodeProblem(Math.Max(0, lines.Count - 1), "블록 주석 /* 가 닫히지 않았습니다."));
AddBalanceProblem(problems, lines.Count, round, '(', ')');
diff --git a/src/MergeHub/Services/CodeTokenizer.cs b/src/MergeHub/Services/CodeTokenizer.cs
index 0b95a9c..d324be6 100644
--- a/src/MergeHub/Services/CodeTokenizer.cs
+++ b/src/MergeHub/Services/CodeTokenizer.cs
@@ -9,6 +9,8 @@ namespace MergeHub.Services
///
/// C# 은 전용 토크나이저로 넘긴다 - 축자·보간 문자열과 접근제한자 구분이 표로 담기지 않고,
/// 이미 테스트로 굳어 있어 굳이 다시 만들 이유가 없다.
+ /// 마크업(XML · XAML · HTML)도 따로 보낸다 - 꺾쇠 안이냐 밖이냐로 갈리는 규칙이라
+ /// 여기의 단어 단위 훑기에 얹을 수가 없다. 를 보라.
/// 평문·Markdown 처럼 코드가 아닌 형식은 토큰을 만들지 않는다(그냥 검은 글씨로 나온다).
///
public static class CodeTokenizer
@@ -19,12 +21,22 @@ namespace MergeHub.Services
private static readonly IReadOnlyList Empty = Array.Empty();
public static IReadOnlyList Tokenize(string line, LanguageProfile profile,
- bool inBlockComment, out bool stillInBlockComment)
+ CarryOver carry, out CarryOver next)
{
if (profile.UseCSharpTokenizer)
- return CSharpTokenizer.Tokenize(line, inBlockComment, out stillInBlockComment);
+ {
+ var wasInComment = (carry & CarryOver.BlockComment) != 0;
+ var result = CSharpTokenizer.Tokenize(line, wasInComment, out var stillInComment);
+ next = stillInComment ? CarryOver.BlockComment : CarryOver.None;
+ return result;
+ }
- stillInBlockComment = inBlockComment;
+ if (profile.UseMarkupTokenizer)
+ return MarkupTokenizer.Tokenize(line, profile, carry, out next);
+
+ // 아래로는 꺾쇠 상태를 쓰지 않는다. 블록 주석만 넘긴다.
+ var inBlockComment = (carry & CarryOver.BlockComment) != 0;
+ next = carry & CarryOver.BlockComment;
if (!profile.IsCode || string.IsNullOrEmpty(line)) return Empty;
@@ -43,7 +55,7 @@ namespace MergeHub.Services
i = close + profile.BlockEnd!.Length;
tokens.Add(new CodeToken(0, i, CodeTokenKind.Comment));
- stillInBlockComment = false;
+ next = CarryOver.None;
}
// ---- 전처리 지시문 ----
@@ -83,7 +95,7 @@ namespace MergeHub.Services
if (end < 0)
{
tokens.Add(new CodeToken(start, line.Length - start, CodeTokenKind.Comment));
- stillInBlockComment = true;
+ next = CarryOver.BlockComment;
break;
}
diff --git a/src/MergeHub/Services/FolderComparer.cs b/src/MergeHub/Services/FolderComparer.cs
index 7b5a0af..9f493b5 100644
--- a/src/MergeHub/Services/FolderComparer.cs
+++ b/src/MergeHub/Services/FolderComparer.cs
@@ -345,12 +345,12 @@ namespace MergeHub.Services
{
var text = TextFile.Read(path);
var builder = new System.Text.StringBuilder(text.Length);
- var inBlockComment = false;
+ var carry = CarryOver.None;
foreach (var line in text.Split('\n'))
{
var body = line.TrimEnd('\r');
- var tokens = CodeTokenizer.Tokenize(body, profile, inBlockComment, out inBlockComment);
+ var tokens = CodeTokenizer.Tokenize(body, profile, carry, out carry);
var isComment = new bool[body.Length];
foreach (var token in tokens)
diff --git a/src/MergeHub/Services/LanguageProfile.cs b/src/MergeHub/Services/LanguageProfile.cs
index 9f9a868..58f6d5e 100644
--- a/src/MergeHub/Services/LanguageProfile.cs
+++ b/src/MergeHub/Services/LanguageProfile.cs
@@ -30,6 +30,7 @@ namespace MergeHub.Services
bool isCode = true,
bool hasBraces = false,
bool useCSharpTokenizer = false,
+ bool useMarkupTokenizer = false,
bool hasPreprocessor = false)
{
Name = name;
@@ -42,6 +43,7 @@ namespace MergeHub.Services
IsCode = isCode;
HasBraces = hasBraces;
UseCSharpTokenizer = useCSharpTokenizer;
+ UseMarkupTokenizer = useMarkupTokenizer;
HasPreprocessor = hasPreprocessor;
var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
@@ -73,6 +75,12 @@ namespace MergeHub.Services
/// C# 전용 토크나이저를 쓸지.
public bool UseCSharpTokenizer { get; }
+ ///
+ /// 마크업 전용 토크나이저를 쓸지. 마크업은 단어가 아니라 꺾쇠 안이냐 밖이냐로 갈려서
+ /// 예약어 표로는 담기지 않는다. 까닭은 에 적었다.
+ ///
+ public bool UseMarkupTokenizer { get; }
+
public HashSet Keywords { get; }
public HashSet ControlKeywords { get; }
public HashSet TypeKeywords { get; }
@@ -187,10 +195,10 @@ namespace MergeHub.Services
caseSensitive: false);
public static readonly LanguageProfile Markup = new LanguageProfile(
- "XML / HTML", new[] { ".xml", ".xaml", ".html", ".htm", ".svg", ".xsd", ".xsl", ".csproj", ".vbproj", ".config", ".props", ".targets", ".resx" },
+ "XML / XAML / HTML", new[] { ".xml", ".xaml", ".html", ".htm", ".svg", ".xsd", ".xsl", ".csproj", ".vbproj", ".config", ".props", ".targets", ".resx" },
blockStart: "",
quotes: new[] { '"', '\'' },
- caseSensitive: false);
+ caseSensitive: false, useMarkupTokenizer: true);
public static readonly LanguageProfile Json = new LanguageProfile(
"JSON", new[] { ".json", ".jsonc" },
diff --git a/src/MergeHub/Services/MarkupTokenizer.cs b/src/MergeHub/Services/MarkupTokenizer.cs
new file mode 100644
index 0000000..3b4b173
--- /dev/null
+++ b/src/MergeHub/Services/MarkupTokenizer.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using MergeHub.Views.Controls;
+
+namespace MergeHub.Services
+{
+ ///
+ /// XML · XAML · HTML 한 줄을 토큰으로 쪼갠다.
+ ///
+ /// 마크업은 단어가 무엇이냐가 아니라 꺾쇠 안이냐 밖이냐로 갈린다.
+ /// 같은 Content 가 꺾쇠 안에서는 속성 이름이고 밖에서는 그냥 글자다.
+ /// 예약어 목록으로 갈리는 다른 언어와 규칙이 아예 달라서 의
+ /// 단어 단위 훑기에 얹을 수가 없었다. 그래서 마크업만 따로 둔다.
+ ///
+ /// 무엇을 무슨 색으로 보는지:
+ ///
+ /// 태그 이름 → 예약어 색 (파랑)
+ /// 속성 이름 → 메서드 색 (황토/연노랑)
+ /// 속성 값 → 문자열 색 (빨강/주황)
+ /// 앞가지(x: · local:) → 타입 예약어 색 (청록)
+ /// < > / : → 구두점 색, = → 연산자 색
+ /// <?xml ?> · <!DOCTYPE> · <![CDATA[ → 지시문 색 (회색)
+ ///
+ ///
+ /// 태그 밖의 글은 칠하지 않는다. 물려받은 색 그대로 둔다 - 그래야 본문이 눈에 띈다.
+ ///
+ /// 못 하는 것: 속성 값이 줄을 넘어가면(닫는 따옴표가 다음 줄에 있으면) 그 줄까지만 문자열로 보고
+ /// 다음 줄은 속성 자리로 돌아간다. XAML 에서 드물고, 틀려도 색만 어긋난다.
+ ///
+ public static class MarkupTokenizer
+ {
+ private static readonly IReadOnlyList Empty = Array.Empty();
+
+ public static IReadOnlyList Tokenize(string line, LanguageProfile profile,
+ CarryOver carry, out CarryOver next)
+ {
+ next = carry;
+ if (string.IsNullOrEmpty(line)) return Empty;
+
+ var commentStart = profile.BlockStart ?? "";
+
+ var tokens = new List();
+ var inTag = (carry & CarryOver.Tag) != 0;
+ var i = 0;
+
+ // 앞 줄에서 넘어온 주석을 먼저 닫는다. 닫히지 않으면 이 줄도 통째로 주석이다.
+ if ((carry & CarryOver.BlockComment) != 0)
+ {
+ var close = line.IndexOf(commentEnd, StringComparison.Ordinal);
+ if (close < 0)
+ {
+ tokens.Add(new CodeToken(0, line.Length, CodeTokenKind.Comment));
+ return tokens;
+ }
+
+ i = close + commentEnd.Length;
+ tokens.Add(new CodeToken(0, i, CodeTokenKind.Comment));
+ next &= ~CarryOver.BlockComment;
+ }
+
+ while (i < line.Length)
+ {
+ var c = line[i];
+
+ if (char.IsWhiteSpace(c)) { i++; continue; }
+
+ if (!inTag)
+ {
+ // 태그 밖의 글. 다음 꺾쇠까지 건너뛴다 - 칠하지 않는다.
+ if (c != '<')
+ {
+ var lt = line.IndexOf('<', i);
+ i = lt < 0 ? line.Length : lt;
+ continue;
+ }
+
+ //
+ if (Matches(line, i, commentStart))
+ {
+ var close = line.IndexOf(commentEnd, i + commentStart.Length, StringComparison.Ordinal);
+ if (close < 0)
+ {
+ tokens.Add(new CodeToken(i, line.Length - i, CodeTokenKind.Comment));
+ next |= CarryOver.BlockComment;
+ return tokens;
+ }
+
+ var closeEnd = close + commentEnd.Length;
+ tokens.Add(new CodeToken(i, closeEnd - i, CodeTokenKind.Comment));
+ i = closeEnd;
+ continue;
+ }
+
+ // · ·
+ // 안을 더 쪼개지 않는다. 파일마다 한두 줄이고, 고칠 일도 거의 없다.
+ if (Matches(line, i, "") || Matches(line, i, "', i);
+ var stop = gt < 0 ? line.Length : gt + 1;
+ tokens.Add(new CodeToken(i, stop - i, CodeTokenKind.Preprocessor));
+ i = stop;
+ continue;
+ }
+
+ // <이름 · 이름
+ var open = i;
+ i++;
+ if (i < line.Length && line[i] == '/') i++;
+ tokens.Add(new CodeToken(open, i - open, CodeTokenKind.Punctuation));
+
+ i = ReadName(line, i, tokens, CodeTokenKind.Keyword);
+ inTag = true;
+ continue;
+ }
+
+ // ---- 꺾쇠 안 ----
+
+ if (c == '>' || (c == '/' && i + 1 < line.Length && line[i + 1] == '>'))
+ {
+ var length = c == '>' ? 1 : 2;
+ tokens.Add(new CodeToken(i, length, CodeTokenKind.Punctuation));
+ i += length;
+ inTag = false;
+ continue;
+ }
+
+ if (c == '=')
+ {
+ tokens.Add(new CodeToken(i, 1, CodeTokenKind.Operator));
+ i++;
+ continue;
+ }
+
+ if (c == '"' || c == '\'')
+ {
+ var start = i;
+ i++;
+ while (i < line.Length && line[i] != c) i++;
+ if (i < line.Length) i++; // 닫는 따옴표까지 포함한다
+ tokens.Add(new CodeToken(start, i - start, CodeTokenKind.Text));
+ continue;
+ }
+
+ if (IsNameStart(c))
+ {
+ i = ReadName(line, i, tokens, CodeTokenKind.Method);
+ continue;
+ }
+
+ i++;
+ }
+
+ if (inTag) next |= CarryOver.Tag;
+ else next &= ~CarryOver.Tag;
+
+ return tokens;
+ }
+
+ ///
+ /// 이름을 읽는다. x:Name · local:MyControl 처럼 앞가지가 붙은 이름은
+ /// 앞가지를 따로 칠한다 - XAML 에서 앞가지는 "어느 네임스페이스의 것이냐"를 말해 주는데,
+ /// 이름에 묻어 두면 x:Name 과 Name 이 같아 보인다.
+ ///
+ private static int ReadName(string line, int index, List tokens, CodeTokenKind kind)
+ {
+ var start = index;
+ var i = index;
+ while (i < line.Length && IsNameChar(line[i])) i++;
+ if (i == start) return index;
+
+ if (i < line.Length && line[i] == ':')
+ {
+ tokens.Add(new CodeToken(start, i - start, CodeTokenKind.TypeKeyword));
+ tokens.Add(new CodeToken(i, 1, CodeTokenKind.Punctuation));
+ i++;
+
+ var nameStart = i;
+ while (i < line.Length && IsNameChar(line[i])) i++;
+ if (i > nameStart) tokens.Add(new CodeToken(nameStart, i - nameStart, kind));
+ return i;
+ }
+
+ tokens.Add(new CodeToken(start, i - start, kind));
+ return i;
+ }
+
+ private static bool IsNameStart(char c) => char.IsLetter(c) || c == '_';
+
+ /// Grid.Row 의 점과 data-id 의 붙임표도 이름의 일부다.
+ private static bool IsNameChar(char c) =>
+ char.IsLetterOrDigit(c) || c == '_' || c == '-' || c == '.';
+
+ private static bool Matches(string line, int index, string token) =>
+ index + token.Length <= line.Length &&
+ string.Compare(line, index, token, 0, token.Length, StringComparison.Ordinal) == 0;
+ }
+}
diff --git a/src/MergeHub/ViewModels/HomeViewModel.cs b/src/MergeHub/ViewModels/HomeViewModel.cs
index 9e07c92..a2f6851 100644
--- a/src/MergeHub/ViewModels/HomeViewModel.cs
+++ b/src/MergeHub/ViewModels/HomeViewModel.cs
@@ -1068,14 +1068,15 @@ namespace MergeHub.ViewModels
}
///
- /// 여러 줄에 걸친 /* ... */ 를 이어서 칠하려면 각 줄이 "앞에서 주석이 열린 채 왔는지"를 알아야 한다.
+ /// 줄을 넘어가는 것들(/* ... */ · XAML 의 여러 줄짜리 여는 태그)을 이어서 칠하려면
+ /// 각 줄이 "앞에서 무엇이 열린 채 왔는지"를 알아야 한다.
/// 위에서 아래로 한 번 훑으며 BASE/LOCAL 각각의 상태를 기록한다.
/// LOCAL 을 직접 고치면 그 아래 줄들의 상태가 달라지므로 편집 후에도 다시 계산한다.
///
private void RefreshBlockCommentState(IEnumerable rows)
{
- var inBase = false;
- var inLocal = false;
+ var inBase = CarryOver.None;
+ var inLocal = CarryOver.None;
foreach (var row in rows)
{
diff --git a/src/MergeHub/Views/Controls/CodeTextBlock.cs b/src/MergeHub/Views/Controls/CodeTextBlock.cs
index 57ce11d..1acf685 100644
--- a/src/MergeHub/Views/Controls/CodeTextBlock.cs
+++ b/src/MergeHub/Views/Controls/CodeTextBlock.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
@@ -57,13 +57,20 @@ namespace MergeHub.Views.Controls
}
public static readonly DependencyProperty InBlockCommentProperty = DependencyProperty.Register(
- nameof(InBlockComment), typeof(bool), typeof(CodeTextBlock),
- new FrameworkPropertyMetadata(false, OnVisualInputChanged));
+ nameof(InBlockComment), typeof(Services.CarryOver), typeof(CodeTextBlock),
+ new FrameworkPropertyMetadata(Services.CarryOver.None, OnVisualInputChanged));
- /// 앞 줄에서 /* 가 열린 채 넘어왔는지. 여러 줄 블록 주석을 이어서 칠하기 위한 값.
- public bool InBlockComment
+ ///
+ /// 앞 줄에서 무엇이 열린 채 넘어왔는지. 여러 줄 블록 주석과
+ /// 여러 줄에 걸친 XAML 여는 태그를 이어서 칠하기 위한 값이다.
+ ///
+ /// 기본값의 타입이 이 속성의 타입과 정확히 같아야 한다. 여기에 false 를 두면
+ /// 이 클래스를 처음 쓰는 순간 터지고, 그러면 파일 비교 화면이 통째로 빈 채로 뜬다 -
+ /// 빌드는 멀쩡하고 로그에도 안 남아서 어디가 잘못됐는지 알 길이 없다. 한 번 겪었다.
+ ///
+ public Services.CarryOver InBlockComment
{
- get => (bool)GetValue(InBlockCommentProperty);
+ get => (Services.CarryOver)GetValue(InBlockCommentProperty);
set => SetValue(InBlockCommentProperty, value);
}