diff --git a/doc/진행상황.md b/doc/진행상황.md index 032f3eb..238a72a 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -127,10 +127,10 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘 | 상태 | 항목 | |---|---| | 대기 | D-1 주석만 다른 파일을 다른 색으로 | -| 대기 | D-2 C++ 문법 강조 | +| **완료** | D-2 C++ 문법 강조 (이미 되고 있었음 · 전처리 지시문만 추가) | | 대기 | D-3 폴더 안 전체 검색 | | ~~취소~~ | ~~D-4 비교 창 여러 개~~ (2026-09-09 사용자 판단으로 뺌) | -| 대기 | D-5 덩어리 단위 반영 (2-Way 는 이미 됨 · 3-Way 확인 필요) | +| **완료** | D-5 덩어리 단위 반영 (3-Way 에서 BASE 를 가져오던 것을 REMOTE 로) | | 대기 | D-6 비교 결과 텍스트 내보내기 | | 대기 | 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` 를 두어 C 계열에서만 켠다. 자바·자바스크립트는 `#` 이 지시문이 아니다. + +**검사**: `LangTest` 12건 추가(40 → 52건). + +#### D-5. 덩어리 단위 반영 + +**2-Way 는 이미 됐다**(`이 덩어리 반영` · `Alt+Shift+→`). 확인했다. + +**3-Way 가 잘못 동작하고 있었다.** 덩어리를 BASE 에서 가져오고 있었는데, 3-Way 에서 BASE 는 +공통 조상이다. 저쪽(REMOTE)을 가져오는 것이 `Take Theirs` 이고 덩어리 반영은 그것을 +줄 묶음으로 하는 것인데, BASE 를 가져오면 조상으로 되돌리는 셈이라 뜻이 전혀 달라진다. +덩어리를 묶는 기준도 같이 바꿨다 - 2-Way 는 "BASE 를 가져올 수 있는 줄", 3-Way 는 +"저쪽을 가져올 수 있는 줄"이 이어진 구간이다. + +**확인**: 충돌 6개짜리 C++ 파일에서 `다음 충돌` → `이 덩어리 반영` 한 번에 +네 줄이 REMOTE 값으로 바뀌고 `Conflict 6 → 2`, `머지 반영함 4`. +단축키(`Alt+Shift+→`)도 같은 명령을 부르므로 함께 고쳐졌다. +--- + ### C. 잔손질 — **완료** (2026-09-09) #### C-1. 코드 위 툴팁 제거 diff --git a/src/MergeHub/Services/CodeTokenizer.cs b/src/MergeHub/Services/CodeTokenizer.cs index 2f4b8e8..0b95a9c 100644 --- a/src/MergeHub/Services/CodeTokenizer.cs +++ b/src/MergeHub/Services/CodeTokenizer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using MergeHub.Views.Controls; @@ -46,6 +46,23 @@ namespace MergeHub.Services stillInBlockComment = false; } + // ---- 전처리 지시문 ---- + // + // 줄 첫머리가 # 이면 그 줄 전체가 지시문이다(#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) { var start = i; diff --git a/src/MergeHub/Services/LanguageProfile.cs b/src/MergeHub/Services/LanguageProfile.cs index 2580ff9..9f9a868 100644 --- a/src/MergeHub/Services/LanguageProfile.cs +++ b/src/MergeHub/Services/LanguageProfile.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; @@ -29,7 +29,8 @@ namespace MergeHub.Services bool caseSensitive = true, bool isCode = true, bool hasBraces = false, - bool useCSharpTokenizer = false) + bool useCSharpTokenizer = false, + bool hasPreprocessor = false) { Name = name; Extensions = extensions; @@ -41,6 +42,7 @@ namespace MergeHub.Services IsCode = isCode; HasBraces = hasBraces; UseCSharpTokenizer = useCSharpTokenizer; + HasPreprocessor = hasPreprocessor; var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; Keywords = new HashSet(keywords ?? Array.Empty(), comparer); @@ -56,6 +58,12 @@ namespace MergeHub.Services public char[] Quotes { get; } public bool CaseSensitive { get; } + /// + /// #include · #define 처럼 줄 첫머리의 # 지시문을 쓰는 언어인지. + /// C 계열이 그렇다. 켜 두면 그 줄 전체를 지시문 색으로 칠한다 - C# 과 같은 규칙이다. + /// + public bool HasPreprocessor { get; } + /// 문법 색을 입힐 대상인지. 평문·표 형식 문서는 false 다. public bool IsCode { get; } @@ -111,7 +119,8 @@ namespace MergeHub.Services "inline", "operator", "new", "delete", "this", "nullptr", "true", "false", "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( "Java", new[] { ".java" }, diff --git a/src/MergeHub/ViewModels/HomeViewModel.cs b/src/MergeHub/ViewModels/HomeViewModel.cs index 97c1657..c4fa5ca 100644 --- a/src/MergeHub/ViewModels/HomeViewModel.cs +++ b/src/MergeHub/ViewModels/HomeViewModel.cs @@ -2193,24 +2193,40 @@ namespace MergeHub.ViewModels var block = BlockAround(CaretRow); 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(); MessageUtil.ShowSuccessToast($"{block.Count}줄 반영"); } - /// 그 줄이 속한 차이 덩어리. 차이가 아니면 빈 목록이다. + /// + /// 그 줄이 속한 차이 덩어리. 차이가 아니면 빈 목록이다. + /// + /// 무엇을 가져오는지가 모드마다 다르다. + /// 2-Way : BASE 를 결과로 가져온다. 덩어리는 "BASE 를 가져올 수 있는 줄"이 이어진 구간. + /// 3-Way : BASE 는 공통 조상이라 가져올 것이 아니다. 저쪽(REMOTE)을 가져오는 것이 + /// Take Theirs 이고, 덩어리 반영은 그것을 줄 묶음으로 하는 것이다. + /// 여기서 BASE 를 가져오면 조상으로 되돌리는 셈이라 뜻이 전혀 달라진다. + /// private List BlockAround(int row) { var found = new List(); 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; - while (first > 0 && Lines[first - 1].CanTakeBase) first--; + while (first > 0 && Takeable(Lines[first - 1])) first--; 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]); return found;