D-2, D-5: C++ 전처리 지시문 · 3-Way 덩어리 반영

D-2. C++ 문법 강조는 이미 되고 있었다. LanguageProfile.CFamily 가 확장자
아홉 가지를 맡고 예약어/주석/문자열도 들어 있다. 빠진 것은 전처리 지시문
하나였다. #include 가 # 과 include 로 쪼개져 보통 낱말처럼 칠해졌다. 줄
첫머리가 # 이면 그 줄 전체를 지시문 색으로 칠한다. C# 쪽과 같은 규칙이다.
언어표에 hasPreprocessor 를 두어 C 계열에서만 켠다.

D-5. 2-Way 는 이미 됐다. 3-Way 가 잘못 동작하고 있었다. 덩어리를 BASE 에서
가져오고 있었는데 3-Way 에서 BASE 는 공통 조상이다. 저쪽(REMOTE)을 가져오는
것이 Take Theirs 이고 덩어리 반영은 그것을 줄 묶음으로 하는 것인데, BASE 를
가져오면 조상으로 되돌리는 셈이라 뜻이 전혀 달라진다. 덩어리를 묶는 기준도
모드에 맞췄다.

확인: 충돌 6개짜리 C++ 파일에서 다음 충돌 -> 이 덩어리 반영 한 번에 네 줄이
REMOTE 값으로 바뀌고 Conflict 6 -> 2. LangTest 12건 추가. 전체 424건 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
김승준 2026-09-09 14:22:55 +09:00
parent 4f33cacd4f
commit 5c8c942ba5
4 changed files with 82 additions and 11 deletions

View File

@ -127,10 +127,10 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
| 상태 | 항목 | | 상태 | 항목 |
|---|---| |---|---|
| 대기 | D-1 주석만 다른 파일을 다른 색으로 | | 대기 | D-1 주석만 다른 파일을 다른 색으로 |
| 대기 | D-2 C++ 문법 강조 | | **완료** | D-2 C++ 문법 강조 (이미 되고 있었음 · 전처리 지시문만 추가) |
| 대기 | D-3 폴더 안 전체 검색 | | 대기 | D-3 폴더 안 전체 검색 |
| ~~취소~~ | ~~D-4 비교 창 여러 개~~ (2026-09-09 사용자 판단으로 뺌) | | ~~취소~~ | ~~D-4 비교 창 여러 개~~ (2026-09-09 사용자 판단으로 뺌) |
| 대기 | D-5 덩어리 단위 반영 (2-Way 는 이미 됨 · 3-Way 확인 필요) | | **완료** | D-5 덩어리 단위 반영 (3-Way 에서 BASE 를 가져오던 것을 REMOTE 로) |
| 대기 | D-6 비교 결과 텍스트 내보내기 | | 대기 | D-6 비교 결과 텍스트 내보내기 |
| 대기 | D-7 드래그 앤 드롭으로 열기 | | 대기 | D-7 드래그 앤 드롭으로 열기 |
@ -147,6 +147,35 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
--- ---
### D-2 · D-5 — **완료** (2026-09-09)
#### D-2. C++ 문법 강조
**이미 되고 있었다.** `LanguageProfile.CFamily` 가 `.c .h .cpp .hpp .cc .hh .cxx .hxx .inl` 을
모두 맡고 있고 예약어·주석·문자열도 들어 있다. 지시서에서 확인만 하라고 적어 둔 대로 확인했다.
**빠진 것은 전처리 지시문 하나였다.** `#include` 는 `#` 과 `include` 로 쪼개져 보통 낱말처럼
칠해졌다. 줄 첫머리가 `#` 이면 그 줄 전체를 지시문 색으로 칠하게 했다 - C# 쪽과 같은 규칙이다.
같은 모양인데 언어마다 다르게 보이면 오히려 헷갈린다.
언어표에 `hasPreprocessor` 를 두어 <b>C 계열에서만</b> 켠다. 자바·자바스크립트는 `#` 이 지시문이 아니다.
**검사**: `LangTest` 12건 추가(40 → 52건).
#### D-5. 덩어리 단위 반영
**2-Way 는 이미 됐다**(`이 덩어리 반영` · `Alt+Shift+→`). 확인했다.
**3-Way 가 잘못 동작하고 있었다.** 덩어리를 BASE 에서 가져오고 있었는데, 3-Way 에서 BASE 는
<b>공통 조상</b>이다. 저쪽(REMOTE)을 가져오는 것이 `Take Theirs` 이고 덩어리 반영은 그것을
줄 묶음으로 하는 것인데, BASE 를 가져오면 <b>조상으로 되돌리는 셈</b>이라 뜻이 전혀 달라진다.
덩어리를 묶는 기준도 같이 바꿨다 - 2-Way 는 "BASE 를 가져올 수 있는 줄", 3-Way 는
"저쪽을 가져올 수 있는 줄"이 이어진 구간이다.
**확인**: 충돌 6개짜리 C++ 파일에서 `다음 충돌` → `이 덩어리 반영` 한 번에
네 줄이 REMOTE 값으로 바뀌고 `Conflict 6 → 2`, `머지 반영함 4`.
단축키(`Alt+Shift+→`)도 같은 명령을 부르므로 함께 고쳐졌다.
---
### C. 잔손질 — **완료** (2026-09-09) ### C. 잔손질 — **완료** (2026-09-09)
#### C-1. 코드 위 툴팁 제거 #### C-1. 코드 위 툴팁 제거

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using MergeHub.Views.Controls; using MergeHub.Views.Controls;
@ -46,6 +46,23 @@ namespace MergeHub.Services
stillInBlockComment = false; stillInBlockComment = false;
} }
// ---- 전처리 지시문 ----
//
// 줄 첫머리가 <c>#</c> 이면 그 줄 전체가 지시문이다(#include · #define · #ifdef).
// C# 쪽과 같은 규칙으로 칠한다 - 같은 모양인데 언어마다 다르게 보이면 더 헷갈린다.
// 블록 주석이 이미 열려 있던 줄은 해당 없다(i 가 0 이 아니다).
if (profile.HasPreprocessor && i == 0)
{
var firstVisible = 0;
while (firstVisible < line.Length && char.IsWhiteSpace(line[firstVisible])) firstVisible++;
if (firstVisible < line.Length && line[firstVisible] == '#')
{
tokens.Add(new CodeToken(firstVisible, line.Length - firstVisible, CodeTokenKind.Preprocessor));
return tokens;
}
}
while (i < line.Length) while (i < line.Length)
{ {
var start = i; var start = i;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -29,7 +29,8 @@ namespace MergeHub.Services
bool caseSensitive = true, bool caseSensitive = true,
bool isCode = true, bool isCode = true,
bool hasBraces = false, bool hasBraces = false,
bool useCSharpTokenizer = false) bool useCSharpTokenizer = false,
bool hasPreprocessor = false)
{ {
Name = name; Name = name;
Extensions = extensions; Extensions = extensions;
@ -41,6 +42,7 @@ namespace MergeHub.Services
IsCode = isCode; IsCode = isCode;
HasBraces = hasBraces; HasBraces = hasBraces;
UseCSharpTokenizer = useCSharpTokenizer; UseCSharpTokenizer = useCSharpTokenizer;
HasPreprocessor = hasPreprocessor;
var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
Keywords = new HashSet<string>(keywords ?? Array.Empty<string>(), comparer); Keywords = new HashSet<string>(keywords ?? Array.Empty<string>(), comparer);
@ -56,6 +58,12 @@ namespace MergeHub.Services
public char[] Quotes { get; } public char[] Quotes { get; }
public bool CaseSensitive { get; } public bool CaseSensitive { get; }
/// <summary>
/// <c>#include</c> · <c>#define</c> 처럼 <b>줄 첫머리의 # 지시문</b>을 쓰는 언어인지.
/// C 계열이 그렇다. 켜 두면 그 줄 전체를 지시문 색으로 칠한다 - C# 과 같은 규칙이다.
/// </summary>
public bool HasPreprocessor { get; }
/// <summary>문법 색을 입힐 대상인지. 평문·표 형식 문서는 false 다.</summary> /// <summary>문법 색을 입힐 대상인지. 평문·표 형식 문서는 false 다.</summary>
public bool IsCode { get; } public bool IsCode { get; }
@ -111,7 +119,8 @@ namespace MergeHub.Services
"inline", "operator", "new", "delete", "this", "nullptr", "true", "false", "inline", "operator", "new", "delete", "this", "nullptr", "true", "false",
"using", "typedef", "sizeof", "friend", "explicit", "mutable", "constexpr" "using", "typedef", "sizeof", "friend", "explicit", "mutable", "constexpr"
}, },
controlKeywords: CFamilyControl, typeKeywords: CFamilyTypes, hasBraces: true); controlKeywords: CFamilyControl, typeKeywords: CFamilyTypes, hasBraces: true,
hasPreprocessor: true);
public static readonly LanguageProfile Java = new LanguageProfile( public static readonly LanguageProfile Java = new LanguageProfile(
"Java", new[] { ".java" }, "Java", new[] { ".java" },

View File

@ -2193,24 +2193,40 @@ namespace MergeHub.ViewModels
var block = BlockAround(CaretRow); var block = BlockAround(CaretRow);
if (block.Count == 0) return; if (block.Count == 0) return;
foreach (var line in block) Apply(line, requery: false); // 2-Way 는 BASE 를, 3-Way 는 저쪽(REMOTE)을 가져온다. BlockAround 주석 참고.
foreach (var line in block)
{
if (IsThreeWay) ApplyRemote(line, requery: false);
else Apply(line, requery: false);
}
AfterEdit(); AfterEdit();
MessageUtil.ShowSuccessToast($"{block.Count}줄 반영"); MessageUtil.ShowSuccessToast($"{block.Count}줄 반영");
} }
/// <summary>그 줄이 속한 차이 덩어리. 차이가 아니면 빈 목록이다.</summary> /// <summary>
/// 그 줄이 속한 차이 덩어리. 차이가 아니면 빈 목록이다.
///
/// <b>무엇을 가져오는지가 모드마다 다르다.</b>
/// 2-Way : BASE 를 결과로 가져온다. 덩어리는 "BASE 를 가져올 수 있는 줄"이 이어진 구간.
/// 3-Way : BASE 는 공통 조상이라 가져올 것이 아니다. 저쪽(REMOTE)을 가져오는 것이
/// <c>Take Theirs</c> 이고, 덩어리 반영은 그것을 줄 묶음으로 하는 것이다.
/// 여기서 BASE 를 가져오면 <b>조상으로 되돌리는 셈</b>이라 뜻이 전혀 달라진다.
/// </summary>
private List<MergeLine> BlockAround(int row) private List<MergeLine> BlockAround(int row)
{ {
var found = new List<MergeLine>(); var found = new List<MergeLine>();
if (row < 0 || row >= Lines.Count) return found; if (row < 0 || row >= Lines.Count) return found;
if (!Lines[row].CanTakeBase) return found;
bool Takeable(MergeLine line) => IsThreeWay ? line.CanTakeRemote : line.CanTakeBase;
if (!Takeable(Lines[row])) return found;
var first = row; var first = row;
while (first > 0 && Lines[first - 1].CanTakeBase) first--; while (first > 0 && Takeable(Lines[first - 1])) first--;
var last = row; var last = row;
while (last + 1 < Lines.Count && Lines[last + 1].CanTakeBase) last++; while (last + 1 < Lines.Count && Takeable(Lines[last + 1])) last++;
for (var i = first; i <= last; i++) found.Add(Lines[i]); for (var i = first; i <= last; i++) found.Add(Lines[i]);
return found; return found;