diff --git a/doc/진행상황.md b/doc/진행상황.md
index 238a72a..ef85c60 100644
--- a/doc/진행상황.md
+++ b/doc/진행상황.md
@@ -126,7 +126,7 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
| 상태 | 항목 |
|---|---|
-| 대기 | D-1 주석만 다른 파일을 다른 색으로 |
+| **완료** | D-1 주석만 다른 파일을 다른 색으로 |
| **완료** | D-2 C++ 문법 강조 (이미 되고 있었음 · 전처리 지시문만 추가) |
| 대기 | D-3 폴더 안 전체 검색 |
| ~~취소~~ | ~~D-4 비교 창 여러 개~~ (2026-09-09 사용자 판단으로 뺌) |
@@ -147,6 +147,35 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
---
+### D-1. 주석만 다른 파일을 다른 색으로 — **완료** (2026-09-09)
+
+폴더 비교 결과에 `주석만 다름` 상태를 새로 두었다. 코드는 같고 주석·공백만 다른 파일이다.
+
+**왜 나누나** - 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수 있는데
+다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다.
+
+**판단하는 법**: 주석 토큰을 걷어내고 공백을 지운 뒤 견준다. 주석 기호는 언어마다 다르므로
+확장자로 고른 언어표를 따르고, 언어를 모르는 파일(평문·표)은 이 판단을 하지 않는다 -
+주석이라는 것이 없다. 양쪽 확장자가 다르면 같은 언어로 볼 근거가 없으니 역시 하지 않는다.
+
+줄 구분은 코드가 있는 줄에만 남긴다. 그래야 주석 한 줄을 통째로 지운 경우도 같다고 보고,
+반대로 코드를 다른 줄로 옮긴 것은 다르다고 본다.
+
+큰 파일은 이 판단을 건너뛴다. 전체 비교에서 이미 쓰는 한도(8MB)를 그대로 따른다 -
+폴더 하나에 수천 개가 있을 때 이 판단만으로 몇 분이 더 들면 안 된다.
+
+**곁들여 둘**
+
+- 색 뜻풀이 줄을 목록 위에 붙였다. 색만 칠해 두면 무슨 뜻인지 아무도 모른다 -
+ 파일 비교 화면의 "색 구분" 줄과 같은 성격이다.
+- 보기 필터에 코드가 다른 것만을 더했다. 주석만 다른 것을 빼고 보는 쪽이
+ 실제로 필요한 동작이다.
+
+**확인**: 주석만 고친 `.cs` 는 회색 `주석만 다름`, 값이 바뀐 `.cs` 와 평문 `.txt` 는 노란 `다름`.
+`코드가 다른 것만` 을 고르면 앞의 것이 목록에서 빠진다.
+`FolderTest` 4건 추가(37 → 41건).
+---
+
### D-2 · D-5 — **완료** (2026-09-09)
#### D-2. C++ 문법 강조
diff --git a/src/MergeHub/Services/FolderComparer.cs b/src/MergeHub/Services/FolderComparer.cs
index a6469cc..7651f77 100644
--- a/src/MergeHub/Services/FolderComparer.cs
+++ b/src/MergeHub/Services/FolderComparer.cs
@@ -1,7 +1,8 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
+using MergeHub.Views.Controls;
using System.Threading;
namespace MergeHub.Services
@@ -18,7 +19,15 @@ namespace MergeHub.Services
/// 오른쪽에만 있다.
RightOnly,
/// 내용이 다르지만 이진 파일이라 줄 단위로 볼 수 없다.
- Binary
+ Binary,
+
+ ///
+ /// 코드는 같고 주석·공백만 다르다.
+ ///
+ /// 따로 두는 이유 - 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수 있는데
+ /// 다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다.
+ ///
+ CommentOnly
}
/// 폴더 비교 규칙.
@@ -250,7 +259,7 @@ namespace MergeHub.Services
if (!item.LeftExists) return FolderEntryStatus.RightOnly;
// 크기가 다르면 내용을 볼 필요가 없다.
- if (item.LeftSize != item.RightSize) return DifferentKind(leftPath, rightPath);
+ if (item.LeftSize != item.RightSize) return DifferentKind(leftPath, rightPath, Math.Max(item.LeftSize, item.RightSize), options);
var quick = options.QuickCompare || item.LeftSize > options.FullCompareLimitBytes;
@@ -262,19 +271,108 @@ namespace MergeHub.Services
var gap = left > right ? left - right : right - left;
return gap <= options.TimeTolerance
? FolderEntryStatus.Same
- : DifferentKind(leftPath, rightPath);
+ : DifferentKind(leftPath, rightPath, Math.Max(item.LeftSize, item.RightSize), options);
}
return SameContent(leftPath, rightPath)
? FolderEntryStatus.Same
- : DifferentKind(leftPath, rightPath);
+ : DifferentKind(leftPath, rightPath, Math.Max(item.LeftSize, item.RightSize), options);
}
- /// 다른 파일이 이진이면 따로 표시한다 - 줄 단위 비교 화면을 열어도 볼 것이 없다.
- private static FolderEntryStatus DifferentKind(string leftPath, string rightPath) =>
- TextFile.IsBinary(leftPath) || TextFile.IsBinary(rightPath)
- ? FolderEntryStatus.Binary
+ ///
+ /// 다른 파일을 어떤 "다름"으로 볼지.
+ ///
+ /// 이진이면 줄 단위 비교 화면을 열어도 볼 것이 없으니 따로 표시한다.
+ /// 글 파일이면 코드는 같고 주석만 다른지를 한 번 더 본다.
+ ///
+ private static FolderEntryStatus DifferentKind(string leftPath, string rightPath, long size,
+ FolderCompareOptions options)
+ {
+ if (TextFile.IsBinary(leftPath) || TextFile.IsBinary(rightPath)) return FolderEntryStatus.Binary;
+
+ return OnlyCommentsDiffer(leftPath, rightPath, size, options)
+ ? FolderEntryStatus.CommentOnly
: FolderEntryStatus.Different;
+ }
+
+ ///
+ /// 코드는 같고 주석·공백만 다른가.
+ ///
+ /// 판단은 주석 토큰을 걷어내고 공백을 지운 뒤 견주는 것이다. 주석 기호는 언어마다 다르므로
+ /// 확장자로 고른 언어표를 따른다. 언어를 모르는 파일(평문·표 등)은 이 판단을 하지 않는다 -
+ /// 주석이라는 것이 없다.
+ ///
+ /// 줄 구분은 코드가 있는 줄에만 남긴다. 그래야 주석 한 줄을 통째로 지운 경우에도
+ /// 같다고 볼 수 있고, 반대로 코드를 다른 줄로 옮긴 것은 다르다고 본다.
+ ///
+ /// 큰 파일은 하지 않는다. 전체 비교에서 이미 걸러 둔 한도를 그대로 쓴다 -
+ /// 폴더 하나에 수천 개가 있을 때 이 판단만으로 몇 분이 더 들면 안 된다.
+ ///
+ private static bool OnlyCommentsDiffer(string leftPath, string rightPath, long size,
+ FolderCompareOptions options)
+ {
+ if (size > options.FullCompareLimitBytes) return false;
+
+ var profile = LanguageCatalog.ForPath(leftPath);
+ if (!profile.IsCode) return false;
+
+ // 확장자가 서로 다르면 같은 언어로 볼 근거가 없다.
+ if (!ReferenceEquals(profile, LanguageCatalog.ForPath(rightPath))) return false;
+
+ var left = CodeOnly(leftPath, profile);
+ if (left is null) return false;
+
+ var right = CodeOnly(rightPath, profile);
+ if (right is null) return false;
+
+ return string.Equals(left, right, StringComparison.Ordinal);
+ }
+
+ /// 주석과 공백을 걷어낸 글. 읽지 못하면 null.
+ private static string? CodeOnly(string path, LanguageProfile profile)
+ {
+ try
+ {
+ var text = TextFile.Read(path);
+ var builder = new System.Text.StringBuilder(text.Length);
+ var inBlockComment = false;
+
+ foreach (var line in text.Split('\n'))
+ {
+ var body = line.TrimEnd('\r');
+ var tokens = CodeTokenizer.Tokenize(body, profile, inBlockComment, out inBlockComment);
+
+ var isComment = new bool[body.Length];
+ foreach (var token in tokens)
+ {
+ if (token.Kind != CodeTokenKind.Comment && token.Kind != CodeTokenKind.XmlDoc) continue;
+
+ var end = Math.Min(body.Length, token.Start + token.Length);
+ for (var i = Math.Max(0, token.Start); i < end; i++) isComment[i] = true;
+ }
+
+ var before = builder.Length;
+ for (var i = 0; i < body.Length; i++)
+ {
+ if (isComment[i] || char.IsWhiteSpace(body[i])) continue;
+ builder.Append(body[i]);
+ }
+
+ // 코드가 한 글자라도 있던 줄에만 줄 구분을 남긴다.
+ if (builder.Length > before) builder.Append('\n');
+ }
+
+ return builder.ToString();
+ }
+ catch (IOException)
+ {
+ return null;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ return null;
+ }
+ }
/// 내용을 앞에서부터 덩어리째 견준다. 다른 곳을 만나면 바로 멈춘다.
private static bool SameContent(string leftPath, string rightPath)
diff --git a/src/MergeHub/ViewModels/FolderViewModel.cs b/src/MergeHub/ViewModels/FolderViewModel.cs
index 619bc72..d1701c6 100644
--- a/src/MergeHub/ViewModels/FolderViewModel.cs
+++ b/src/MergeHub/ViewModels/FolderViewModel.cs
@@ -17,6 +17,10 @@ namespace MergeHub.ViewModels
public enum FolderFilter
{
Different,
+
+ /// 주석만 다른 것은 빼고 코드가 다른 것만. 볼 것이 많을 때 쓴다.
+ CodeDifferent,
+
All,
LeftOnly,
RightOnly,
@@ -64,6 +68,7 @@ namespace MergeHub.ViewModels
FolderEntryStatus.LeftOnly => "왼쪽만",
FolderEntryStatus.RightOnly => "오른쪽만",
FolderEntryStatus.Binary => "다름(이진)",
+ FolderEntryStatus.CommentOnly => "주석만 다름",
_ => string.Empty
};
@@ -167,12 +172,13 @@ namespace MergeHub.ViewModels
public IReadOnlyList Filters { get; } = new[]
{
- FolderFilter.Different, FolderFilter.All,
+ FolderFilter.Different, FolderFilter.CodeDifferent, FolderFilter.All,
FolderFilter.LeftOnly, FolderFilter.RightOnly, FolderFilter.Same
};
public static string FilterName(FolderFilter filter) => filter switch
{
+ FolderFilter.CodeDifferent => "코드가 다른 것만",
FolderFilter.All => "전체",
FolderFilter.LeftOnly => "왼쪽에만",
FolderFilter.RightOnly => "오른쪽에만",
@@ -318,6 +324,10 @@ namespace MergeHub.ViewModels
FolderFilter.LeftOnly => status == FolderEntryStatus.LeftOnly,
FolderFilter.RightOnly => status == FolderEntryStatus.RightOnly,
FolderFilter.Same => status == FolderEntryStatus.Same,
+
+ // 주석만 다른 것은 뺀다. 볼 것이 많을 때 "진짜 다른 것"만 남기려는 필터다.
+ FolderFilter.CodeDifferent =>
+ status != FolderEntryStatus.Same && status != FolderEntryStatus.CommentOnly,
_ => status != FolderEntryStatus.Same,
};
diff --git a/src/MergeHub/Views/Controls/LineHighlightBrushes.cs b/src/MergeHub/Views/Controls/LineHighlightBrushes.cs
index f23c841..189c763 100644
--- a/src/MergeHub/Views/Controls/LineHighlightBrushes.cs
+++ b/src/MergeHub/Views/Controls/LineHighlightBrushes.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
@@ -100,6 +100,7 @@ namespace MergeHub.Views.Controls
{
Services.FolderEntryStatus.Different => LineHighlightBrushes.Changed,
Services.FolderEntryStatus.Binary => LineHighlightBrushes.Moved,
+ Services.FolderEntryStatus.CommentOnly => LineHighlightBrushes.Ignored,
Services.FolderEntryStatus.LeftOnly => LineHighlightBrushes.Removed,
Services.FolderEntryStatus.RightOnly => LineHighlightBrushes.Added,
_ => LineHighlightBrushes.None
diff --git a/src/MergeHub/Views/FolderView.xaml b/src/MergeHub/Views/FolderView.xaml
index 8a32649..26d355e 100644
--- a/src/MergeHub/Views/FolderView.xaml
+++ b/src/MergeHub/Views/FolderView.xaml
@@ -1,7 +1,8 @@
-
@@ -80,6 +81,7 @@
+
@@ -158,8 +160,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -182,7 +226,7 @@
채움 칸(*)이 0 이 되고 줄 배경이 글자 끝에서 끊긴다. 칸 폭이 고정이라
가로로 넘칠 일이 없으므로 꺼 두는 편이 맞다.
-->
-
-