D-1: 주석만 다른 파일을 다른 색으로
폴더 비교에 "주석만 다름" 상태를 새로 두었다. 코드는 같고 주석/공백만 다른 파일이다. 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수 있는데 다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다. 판단은 주석 토큰을 걷어내고 공백을 지운 뒤 견주는 것이다. 주석 기호는 언어마다 다르므로 확장자로 고른 언어표를 따르고, 언어를 모르는 파일은 하지 않는다. 양쪽 확장자가 다르면 같은 언어로 볼 근거가 없으니 역시 하지 않는다. 줄 구분은 코드가 있는 줄에만 남긴다. 큰 파일은 건너뛴다. 곁들여 색 뜻풀이 줄을 목록 위에 붙이고, 보기 필터에 "코드가 다른 것만"을 더했다. 주석만 다른 것을 빼고 보는 쪽이 실제로 필요한 동작이다. 확인: 주석만 고친 .cs 는 회색 "주석만 다름", 값이 바뀐 .cs 와 평문 .txt 는 노란 "다름". "코드가 다른 것만"을 고르면 앞의 것이 빠진다. FolderTest 4건 추가. 전체 428건 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5c8c942ba5
commit
451b6245cb
31
doc/진행상황.md
31
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)
|
||||
|
||||
폴더 비교 결과에 `주석만 다름` 상태를 새로 두었다. 코드는 같고 주석·공백만 다른 파일이다.
|
||||
|
||||
**왜 나누나** - 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수 있는데
|
||||
다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다.
|
||||
|
||||
**판단하는 법**: 주석 토큰을 걷어내고 공백을 지운 뒤 견준다. 주석 기호는 언어마다 다르므로
|
||||
확장자로 고른 언어표를 따르고, 언어를 모르는 파일(평문·표)은 이 판단을 하지 않는다 -
|
||||
주석이라는 것이 없다. 양쪽 확장자가 다르면 같은 언어로 볼 근거가 없으니 역시 하지 않는다.
|
||||
|
||||
줄 구분은 <b>코드가 있는 줄에만</b> 남긴다. 그래야 주석 한 줄을 통째로 지운 경우도 같다고 보고,
|
||||
반대로 코드를 다른 줄로 옮긴 것은 다르다고 본다.
|
||||
|
||||
큰 파일은 이 판단을 건너뛴다. 전체 비교에서 이미 쓰는 한도(8MB)를 그대로 따른다 -
|
||||
폴더 하나에 수천 개가 있을 때 이 판단만으로 몇 분이 더 들면 안 된다.
|
||||
|
||||
**곁들여 둘**
|
||||
|
||||
- <b>색 뜻풀이 줄</b>을 목록 위에 붙였다. 색만 칠해 두면 무슨 뜻인지 아무도 모른다 -
|
||||
파일 비교 화면의 "색 구분" 줄과 같은 성격이다.
|
||||
- 보기 필터에 <b>코드가 다른 것만</b>을 더했다. 주석만 다른 것을 빼고 보는 쪽이
|
||||
실제로 필요한 동작이다.
|
||||
|
||||
**확인**: 주석만 고친 `.cs` 는 회색 `주석만 다름`, 값이 바뀐 `.cs` 와 평문 `.txt` 는 노란 `다름`.
|
||||
`코드가 다른 것만` 을 고르면 앞의 것이 목록에서 빠진다.
|
||||
`FolderTest` 4건 추가(37 → 41건).
|
||||
---
|
||||
|
||||
### D-2 · D-5 — **완료** (2026-09-09)
|
||||
|
||||
#### D-2. C++ 문법 강조
|
||||
|
||||
@ -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
|
||||
/// <summary>오른쪽에만 있다.</summary>
|
||||
RightOnly,
|
||||
/// <summary>내용이 다르지만 이진 파일이라 줄 단위로 볼 수 없다.</summary>
|
||||
Binary
|
||||
Binary,
|
||||
|
||||
/// <summary>
|
||||
/// <b>코드는 같고 주석·공백만</b> 다르다.
|
||||
///
|
||||
/// 따로 두는 이유 - 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수 있는데
|
||||
/// 다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다.
|
||||
/// </summary>
|
||||
CommentOnly
|
||||
}
|
||||
|
||||
/// <summary>폴더 비교 규칙.</summary>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>다른 파일이 이진이면 따로 표시한다 - 줄 단위 비교 화면을 열어도 볼 것이 없다.</summary>
|
||||
private static FolderEntryStatus DifferentKind(string leftPath, string rightPath) =>
|
||||
TextFile.IsBinary(leftPath) || TextFile.IsBinary(rightPath)
|
||||
? FolderEntryStatus.Binary
|
||||
/// <summary>
|
||||
/// 다른 파일을 어떤 "다름"으로 볼지.
|
||||
///
|
||||
/// 이진이면 줄 단위 비교 화면을 열어도 볼 것이 없으니 따로 표시한다.
|
||||
/// 글 파일이면 <b>코드는 같고 주석만 다른지</b>를 한 번 더 본다.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 코드는 같고 <b>주석·공백만</b> 다른가.
|
||||
///
|
||||
/// 판단은 주석 토큰을 걷어내고 공백을 지운 뒤 견주는 것이다. 주석 기호는 언어마다 다르므로
|
||||
/// 확장자로 고른 언어표를 따른다. 언어를 모르는 파일(평문·표 등)은 이 판단을 하지 않는다 -
|
||||
/// 주석이라는 것이 없다.
|
||||
///
|
||||
/// 줄 구분은 <b>코드가 있는 줄에만</b> 남긴다. 그래야 주석 한 줄을 통째로 지운 경우에도
|
||||
/// 같다고 볼 수 있고, 반대로 코드를 다른 줄로 옮긴 것은 다르다고 본다.
|
||||
///
|
||||
/// 큰 파일은 하지 않는다. 전체 비교에서 이미 걸러 둔 한도를 그대로 쓴다 -
|
||||
/// 폴더 하나에 수천 개가 있을 때 이 판단만으로 몇 분이 더 들면 안 된다.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>주석과 공백을 걷어낸 글. 읽지 못하면 null.</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>내용을 앞에서부터 덩어리째 견준다. 다른 곳을 만나면 바로 멈춘다.</summary>
|
||||
private static bool SameContent(string leftPath, string rightPath)
|
||||
|
||||
@ -17,6 +17,10 @@ namespace MergeHub.ViewModels
|
||||
public enum FolderFilter
|
||||
{
|
||||
Different,
|
||||
|
||||
/// <summary>주석만 다른 것은 빼고 <b>코드가 다른 것</b>만. 볼 것이 많을 때 쓴다.</summary>
|
||||
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<FolderFilter> 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,
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<UserControl x:Class="MergeHub.Views.FolderView"
|
||||
<UserControl x:Class="MergeHub.Views.FolderView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:MergeHub.ViewModels"
|
||||
xmlns:svc="clr-namespace:MergeHub.Services"
|
||||
xmlns:ctl="clr-namespace:MergeHub.Views.Controls">
|
||||
|
||||
<UserControl.Resources>
|
||||
@ -80,6 +81,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
@ -158,8 +160,50 @@
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
④ 색 뜻풀이. 색만 칠해 두면 무슨 뜻인지 아무도 모른다 -
|
||||
파일 비교 화면의 "색 구분" 줄과 같은 성격이다.
|
||||
-->
|
||||
<Border Grid.Row="2" Padding="8,4" BorderThickness="1,1,1,0" Margin="0,0,0,0"
|
||||
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
<TextBlock Text="색 구분" Style="{StaticResource Muted}" FontSize="11" Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<Border Width="12" Height="12" Margin="0,0,4,0" VerticalAlignment="Center"
|
||||
Background="{Binding Source={x:Static svc:FolderEntryStatus.Different},
|
||||
Converter={StaticResource StatusBrush}}"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"/>
|
||||
<TextBlock Text="다름" FontSize="11" Margin="0,0,14,0" VerticalAlignment="Center"/>
|
||||
|
||||
<Border Width="12" Height="12" Margin="0,0,4,0" VerticalAlignment="Center"
|
||||
Background="{Binding Source={x:Static svc:FolderEntryStatus.CommentOnly},
|
||||
Converter={StaticResource StatusBrush}}"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"/>
|
||||
<TextBlock Text="주석만 다름" FontSize="11" Margin="0,0,14,0" VerticalAlignment="Center"
|
||||
ToolTip="코드는 같고 주석·공백만 다른 파일입니다"/>
|
||||
|
||||
<Border Width="12" Height="12" Margin="0,0,4,0" VerticalAlignment="Center"
|
||||
Background="{Binding Source={x:Static svc:FolderEntryStatus.LeftOnly},
|
||||
Converter={StaticResource StatusBrush}}"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"/>
|
||||
<TextBlock Text="왼쪽에만" FontSize="11" Margin="0,0,14,0" VerticalAlignment="Center"/>
|
||||
|
||||
<Border Width="12" Height="12" Margin="0,0,4,0" VerticalAlignment="Center"
|
||||
Background="{Binding Source={x:Static svc:FolderEntryStatus.RightOnly},
|
||||
Converter={StaticResource StatusBrush}}"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"/>
|
||||
<TextBlock Text="오른쪽에만" FontSize="11" Margin="0,0,14,0" VerticalAlignment="Center"/>
|
||||
|
||||
<Border Width="12" Height="12" Margin="0,0,4,0" VerticalAlignment="Center"
|
||||
Background="{Binding Source={x:Static svc:FolderEntryStatus.Binary},
|
||||
Converter={StaticResource StatusBrush}}"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"/>
|
||||
<TextBlock Text="다름(이진)" FontSize="11" VerticalAlignment="Center"/>
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ③ 목록 머리글. 트리 항목의 칸 폭과 같은 값을 쓴다. -->
|
||||
<Border Grid.Row="2" Padding="8,4" BorderThickness="1,1,1,0"
|
||||
<Border Grid.Row="3" Padding="8,4" BorderThickness="1,1,1,0"
|
||||
Background="{DynamicResource Ubisam.Brush.Sidebar.Background}"
|
||||
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
|
||||
<Grid Margin="26,0,0,0">
|
||||
@ -182,7 +226,7 @@
|
||||
채움 칸(*)이 0 이 되고 줄 배경이 글자 끝에서 끊긴다. 칸 폭이 고정이라
|
||||
가로로 넘칠 일이 없으므로 꺼 두는 편이 맞다.
|
||||
-->
|
||||
<TreeView Grid.Row="3" x:Name="Tree" BorderThickness="1"
|
||||
<TreeView Grid.Row="4" x:Name="Tree" BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
|
||||
ItemsSource="{Binding Nodes}"
|
||||
ItemTemplate="{StaticResource NodeTemplate}"
|
||||
@ -196,7 +240,7 @@
|
||||
VirtualizingPanel.VirtualizationMode="Recycling"/>
|
||||
|
||||
<!-- ⑤ 요약 · 동작 -->
|
||||
<Border Grid.Row="4" Padding="8" BorderThickness="1,0,1,1" Margin="0,0,0,0"
|
||||
<Border Grid.Row="5" Padding="8" BorderThickness="1,0,1,1" Margin="0,0,0,0"
|
||||
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user