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 이 되고 줄 배경이 글자 끝에서 끊긴다. 칸 폭이 고정이라 가로로 넘칠 일이 없으므로 꺼 두는 편이 맞다. --> - -