12차 G-2 - base 칸으로 머지하는 단축키 (사용자 요청)

12차 B-1 로 왼쪽 파일로 반영하는 길([<] · TakeLineToBase)이 생겼는데 단축키가
없었다. 덩어리를 왼쪽으로 보내는 것은 명령 자체가 없었다 - AcceptBlock
(Alt+Shift+Right)은 오른쪽으로만 간다.

키를 어디에 둘지

사용자가 "Alt+Left 도 비어 있지 않냐" 고 물었다. 비어 있지 않았다 -
CopyRightToLeft 가 쓰고 있었는데, 2-Way 에서 그것이 하는 일은 이름·화살표와
달리 "이 줄 되돌리기" 였다. 그런데 이름이 가리키는 "왼쪽으로 반영" 이 B-1 로
실제로 생겼다. 그래서 감각이 맞는 쪽으로 넘겼다(사용자 결정).

  Alt+Right        한 줄 오른쪽으로        그대로
  Alt+Shift+Right  덩어리 오른쪽으로       그대로
  Alt+Left         이 줄 되돌리기      ->  한 줄 왼쪽 파일로
  Alt+Shift+Left   (없음)              ->  덩어리 왼쪽 파일로 (새로 만듦)
  이 줄 되돌리기   Alt+Left            ->  단축키 없음, Ctrl+Z 만

Alt+Right 가 이미 2-Way/3-Way 로 갈라져 있으므로 Alt+Left 도 같은 식으로
갈랐다. 3-Way(Take Theirs)는 안 건드렸다.

만든 것
- AcceptBlockToBase · ApplyGroupToBase - AcceptBlock/ApplyGroup 의 거울.
  주석은 덩어리 첫 줄 위 한 줄, 되돌리기는 한 번에(11차 F-2 와 같은 규칙).
  덩어리를 잡는 BlockAround 는 2-Way 에서 이미 CanTakeBase 로 잡으므로
  그대로 썼다 - 가져올 수 있는 줄과 보낼 수 있는 줄이 같은 줄이고 방향만 반대다.
- [이 덩어리 왼쪽으로] 단추 - [이 덩어리 반영] 옆에. 두 파일 비교에서만 뜬다.
  단축키만 두면 찾을 길이 없다.

겪은 것
- RaiseCanExecuteChanged 와 OnCaretRowChanged 에 새 명령을 안 넣어 단추가
  꺼진 채였다. CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는다.
- 덩어리 명령은 고른 줄이 아니라 CaretRow(편집기 캐럿)를 본다. 시험이 목록을
  고르기만 해서 한 줄만 갔다 - Alt+Down 으로 캐럿을 놓아야 했다.

확인
- 화면 g2-verify 21건 실패 0 (연속 세 줄이 다른 재료를 따로 만들어 씀)
  Alt+Left 는 한 줄만, Ctrl+Z 로 되돌아가고, 덩어리는 셋이 한 번에 가고
  한 번에 돌아온다
- 하네스 ShortcutTest 88 -> 101건 (키 겹침 없음, 화살표 넷의 모양이 맞음,
  충돌 이동을 안 빼앗음, Alt+Left 설명이 "되돌리기" 라고 거짓말하지 않음)
- 회귀 g1 · e2 · e3 · e4 실패 0, 빌드 경고 0 · 오류 0

값으로 확정하지 못한 것: Alt+Shift 조합을 흉내 낸 키로는 못 눌렀다. 윈도우가
입력 언어 전환으로 먼저 채간다 - SendKeys 로는 아예 안 닿고 keybd_event 로는
Shift 가 빠져 Alt+Left 처럼 동작했다. 기존 Alt+Shift+Right 로 견줘 봐도 같아
시험 도구 한계다(C-3 의 DragMove 와 같은 부류). 그래서 덩어리는 같은 명령을
부르는 단추로 확인했다. 사람 손으로 한 번 눌러 보는 것이 남았다.

Agent: main (claude-opus-5)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
김승준 2026-09-30 13:04:49 +09:00
parent b95a07d028
commit a86dc1596f
7 changed files with 170 additions and 8 deletions

View File

@ -2,6 +2,71 @@
지시서: [../작업지시/12차.md](../작업지시/12차.md) 지시서: [../작업지시/12차.md](../작업지시/12차.md)
## G-2. base 칸으로 머지하는 단축키 (2026-09-30, 사용자 요청 · 완료)
### 무엇이 없었나
12차 B-1 로 **왼쪽 파일로 반영**하는 길이 생겼는데(`[<]` 단추 · `TakeLineToBase`) **단축키가 없었다.**
덩어리를 왼쪽으로 보내는 것은 명령 자체가 없었다 — `AcceptBlock`(`Alt+Shift+→`)은 오른쪽으로만 간다.
### 키를 어디에 둘지
사용자가 "`Alt+←` 도 비어 있지 않냐" 고 물었다. **비어 있지 않았다** — `CopyRightToLeft` 가 쓰고 있었는데,
2-Way 에서 그것이 하는 일은 이름·화살표와 달리 **"이 줄 되돌리기"** 였다. 그런데 이름과 화살표가
가리키는 "왼쪽으로 반영" 이 B-1 로 **실제로 생겼다.** 그래서 감각이 맞는 쪽으로 넘겼다(사용자 결정).
| 키 | 전 | 후 |
|---|---|---|
| `Alt+→` | 한 줄 오른쪽으로 | 그대로 |
| `Alt+Shift+→` | 덩어리 오른쪽으로 | 그대로 |
| `Alt+←` | **이 줄 되돌리기**(3-Way: Take Theirs) | **한 줄 왼쪽 파일로**(3-Way 는 Take Theirs 그대로) |
| `Alt+Shift+←` | 비어 있음 | **덩어리 왼쪽 파일로**(새로 만듦) |
| 이 줄 되돌리기 | `Alt+←` | **단축키 없음 — `Ctrl+Z` 만**(사용자 결정) |
`Alt+→` 가 이미 2-Way/3-Way 로 갈라져 있으므로 `Alt+←` 도 같은 식으로 갈랐다. 3-Way 는 안 건드렸다.
### 만든 것
- `AcceptBlockToBase` · `ApplyGroupToBase` — `AcceptBlock`/`ApplyGroup` 의 거울.
주석은 덩어리 첫 줄 위 한 줄, 되돌리기는 한 번에(11차 F-2 와 같은 규칙).
덩어리를 잡는 `BlockAround` 는 2-Way 에서 이미 `CanTakeBase` 로 잡으므로 **그대로 썼다** —
"가져올 수 있는 줄" 과 "보낼 수 있는 줄" 이 같은 줄이고 방향만 반대다.
- **[이 덩어리 왼쪽으로] 단추** — `[이 덩어리 반영]` 옆에. 두 파일 비교(`BothWays`)에서만 뜬다.
단축키만 두면 찾을 길이 없다.
### 겪은 것 둘
**`RaiseCanExecuteChanged` 에 새 명령을 안 넣어 단추가 꺼진 채였다.** CommunityToolkit 의 커맨드는
`CommandManager` 를 구독하지 않아 직접 알려야 한다(파일에 이미 그 까닭이 주석으로 있었다).
`OnCaretRowChanged` 에도 넣어야 줄을 옮길 때 켜지고 꺼진다.
**덩어리 명령은 고른 줄이 아니라 `CaretRow`(편집기 캐럿)를 본다.** 시험이 왼쪽 목록을 고르기만 해서
한 줄만 갔다 — `Alt+↓`(다음 차이)로 캐럿을 놓아야 했다.
### 화면으로 확인 (값)
`g2-verify.ps1` **21건 · 실패 0**. 연속 세 줄이 다른 재료(`speed` · `power` · `delay`)를 따로 만들어 썼다.
| 확인 | 값 |
|---|---|
| `Alt+←` 한 줄만 | `speed = 10 → 11`, `power`·`delay` 는 그대로 |
| `Ctrl+Z` | 되돌아간다 |
| 덩어리 왼쪽으로 | 셋이 **한 번에** 간다 |
| 덩어리 되돌리기 | 셋이 **한 번에** 돌아온다 |
| 회귀 | `Alt+→` · `[이 덩어리 반영]` 그대로 · `g1`·`e2`·`e3`·`e4` 실패 0 |
하네스 `ShortcutTest` 88 → **101건**(G-2 +13): 키가 겹치지 않음 · 화살표 넷의 모양이 맞음
(Alt = 한 줄, Alt+Shift = 덩어리) · 충돌 이동(`Alt+Shift+↑↓`)을 안 빼앗음 ·
**`Alt+←` 설명이 "되돌리기" 라고 거짓말하지 않음**.
### 값으로 확정하지 못한 것
**`Alt+Shift+←` 와 `Alt+Shift+→` 를 흉내 낸 키로는 못 눌렀다.** 윈도우가 `Alt+Shift` 를
입력 언어 전환으로 먼저 채간다 — `SendKeys` 로는 아예 안 닿고, `keybd_event` 로는 `Shift` 가 빠져
`Alt+←` 처럼 동작했다. **기존 `Alt+Shift+→` 로 견줘 봐도 똑같았으므로 시험 도구 한계다**(C-3 의
`DragMove` 와 같은 부류). 그래서 덩어리는 **같은 명령을 부르는 단추로** 확인했다.
사람 손으로 `Alt+Shift+←` 를 한 번 눌러 보는 것이 남았다.
## G-1. undo 가 계속 이어진다 (2026-09-30, 사용자 보고 · 완료) ## G-1. undo 가 계속 이어진다 (2026-09-30, 사용자 보고 · 완료)
### 먼저 재현 (화면, 값) ### 먼저 재현 (화면, 값)

View File

@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 | | 날짜 | 항목 | 커밋 |
|---|---|---| |---|---|---|
| 2026-09-30 | **12차 G-2** — 사용자 요청 "base 영역에 머지할 수 있는 단축키". 12차 B-1 로 **왼쪽 파일로 반영**하는 길(`[<]` · `TakeLineToBase`)이 생겼는데 단축키가 없었고, 덩어리를 왼쪽으로 보내는 것은 명령 자체가 없었다(`AcceptBlock` 은 오른쪽으로만 간다). `Alt+←` 는 비어 있지 않았는데 — `CopyRightToLeft` 가 쓰고 있었고 2-Way 에서 하는 일이 이름·화살표와 달리 **"이 줄 되돌리기"** 였다. 그 이름이 가리키는 일이 B-1 로 실제로 생겼으므로 그쪽으로 넘겼다(사용자 결정). **`Alt+←` = 한 줄 왼쪽으로 · `Alt+Shift+←` = 덩어리 왼쪽으로**(새로 만듦) · 되돌리기는 `Ctrl+Z` 만. 3-Way(Take Theirs)는 안 건드렸다. 찾을 수 있게 **[이 덩어리 왼쪽으로] 단추**도 달았다 (두 파일 비교에서만 뜬다). `ApplyGroupToBase` 는 `ApplyGroup` 의 거울 — 주석은 덩어리 첫 줄 위 한 줄, 되돌리기는 한 번에. 덩어리를 잡는 `BlockAround` 는 2-Way 에서 이미 `CanTakeBase` 로 잡으므로 그대로 썼다. 겪은 것: `RaiseCanExecuteChanged`·`OnCaretRowChanged` 에 새 명령을 안 넣어 단추가 꺼진 채였고, 덩어리 명령은 고른 줄이 아니라 `CaretRow`(편집기 캐럿)를 본다. 화면 `g2-verify` 21건 실패 0 · 하네스 `ShortcutTest` 88 → 101건 · 회귀 `g1`·`e2`·`e3`·`e4` 실패 0. **`Alt+Shift` 조합은 윈도우가 입력 언어 전환으로 먼저 채가 흉내 낸 키로 못 눌렀다** — 기존 `Alt+Shift+→` 로 견줘 봐도 같아 시험 도구 한계이고(C-3 의 DragMove 와 같은 부류), 덩어리는 같은 명령을 부르는 단추로 확인했다. 사람 손으로 한 번 눌러 보는 것이 남았다 | 아래 |
| 2026-09-30 | **12차 G-1** — 사용자 보고 "undo 가 계속 이어진다". 짝을 바꾼 뒤 `Ctrl+Z` 를 누르면 **앞 파일 글(234자) ↔ 지금 파일 글(228자)을 끝없이 오갔다** — 40번을 눌러도 안 끝났고, 글자를 하나도 안 쳐도 났다. `Ctrl+Z` 는 우리 목록(`_undo`·`_textUndo`)이 비면 일부러 키를 먹지 않는데(그래야 글자 되돌리기가 산다), 그러면 키가 `TextBox` 자체 되돌리기로 넘어간다. WPF 의 그것은 한도가 없고 **프로그램이 코드로 넣은 글까지 기록**해서, 짝을 바꾸면 앞 파일 글을 편집기에 되살렸다. 되살아난 글을 `Reflow` 가 "사용자가 친 것" 으로 보고 모델을 그쪽으로 되돌리며 되돌리기에 다시 쌓고 다시하기를 지워, 두 상태에 갇혔다(기록을 심어 고리를 떴다 — `221 = 234 − 13` 으로 편집기가 쥔 것이 앞 파일임을 값으로 확인). **고침은 한 줄** — `LocalEditor` 에 `IsUndoEnabled="False"`. 되돌리기는 우리 목록만 쓴다. 화면 `g1-verify` 14건 실패 0(30번 눌러도 길이 자취가 `228` 하나), 편집 관련 다섯 벌(`e1`·`e2`·`e2-reg`·`e3`·`e4`) 회귀 실패 0. **헛짚은 것 둘도 남긴다** — "문서가 바뀔 때 TextBox 기록을 비우면 된다"(`EditorUndoGate` 를 만들어 하네스 16건까지 붙였다)와 "재정렬 타이머가 뒤늦게 터진다" 는 둘 다 증상이 안 없어져 **되돌렸다**. 검증 안 된 동작 변경은 남기지 않는다. 대신 글자 **한 자 단위** 되돌리기는 없어지고 "타이핑이 멈춘 자리" 가 한 걸음이 됐다 | 아래 | | 2026-09-30 | **12차 G-1** — 사용자 보고 "undo 가 계속 이어진다". 짝을 바꾼 뒤 `Ctrl+Z` 를 누르면 **앞 파일 글(234자) ↔ 지금 파일 글(228자)을 끝없이 오갔다** — 40번을 눌러도 안 끝났고, 글자를 하나도 안 쳐도 났다. `Ctrl+Z` 는 우리 목록(`_undo`·`_textUndo`)이 비면 일부러 키를 먹지 않는데(그래야 글자 되돌리기가 산다), 그러면 키가 `TextBox` 자체 되돌리기로 넘어간다. WPF 의 그것은 한도가 없고 **프로그램이 코드로 넣은 글까지 기록**해서, 짝을 바꾸면 앞 파일 글을 편집기에 되살렸다. 되살아난 글을 `Reflow` 가 "사용자가 친 것" 으로 보고 모델을 그쪽으로 되돌리며 되돌리기에 다시 쌓고 다시하기를 지워, 두 상태에 갇혔다(기록을 심어 고리를 떴다 — `221 = 234 − 13` 으로 편집기가 쥔 것이 앞 파일임을 값으로 확인). **고침은 한 줄** — `LocalEditor` 에 `IsUndoEnabled="False"`. 되돌리기는 우리 목록만 쓴다. 화면 `g1-verify` 14건 실패 0(30번 눌러도 길이 자취가 `228` 하나), 편집 관련 다섯 벌(`e1`·`e2`·`e2-reg`·`e3`·`e4`) 회귀 실패 0. **헛짚은 것 둘도 남긴다** — "문서가 바뀔 때 TextBox 기록을 비우면 된다"(`EditorUndoGate` 를 만들어 하네스 16건까지 붙였다)와 "재정렬 타이머가 뒤늦게 터진다" 는 둘 다 증상이 안 없어져 **되돌렸다**. 검증 안 된 동작 변경은 남기지 않는다. 대신 글자 **한 자 단위** 되돌리기는 없어지고 "타이핑이 멈춘 자리" 가 한 걸음이 됐다 | 아래 |
| 2026-09-30 | **플랫폼을 공용 저장소로 합침** — 우리가 따로 쓰던 플랫폼 소스(`D:\Sorce\ubsrepo`, 원격이 `Ubisam-MergeTool` 이었다)에 얹어 둔 두 커밋을 **진짜 플랫폼 저장소** `D:\Sorce\UbisamCommonPlatform`(사내 Gitea) 로 옮겼다. `6991dfd` 이후 양쪽이 갈라져 있었고 — 우리 2커밋(트레이 · 창 자리 기억 · 세 갈래 묻는 창 · 탭 줄 크기 · 헤더 접기) vs 공용 4커밋(우클릭 메뉴 테마 · 앱 아이콘 · 업데이트 기본값 · HANDOFF) — **소스 파일이 하나도 겹치지 않아** 부딪힌 것은 `changelog.md` 한 군데뿐이었다(양쪽 줄 다 살려 날짜순으로 합침). 공용 쪽에 빠져 있던 문서도 채웠다: 가이드 8-5 에 헤더 접기, `HANDOFF.md` 5·6절에 우리 기능 — **C-3 은 플랫폼 changelog 에도 줄이 없었다.** 빌드해 이 PC 의 `D:\UbisamPlatform\bin` 까지 반영(오류 0 · 경고 4, 배포본 해시로 확인). `dist\` 는 손대지 않았다 — 사내 다른 PC 는 그대로다. 화면 회귀 아홉 벌 **208건 실패 0 · 예외 0**, MergeHub 재빌드 경고 0 · 오류 0. **앞으로 플랫폼은 공용 저장소에서만 고친다** | 아래 | | 2026-09-30 | **플랫폼을 공용 저장소로 합침** — 우리가 따로 쓰던 플랫폼 소스(`D:\Sorce\ubsrepo`, 원격이 `Ubisam-MergeTool` 이었다)에 얹어 둔 두 커밋을 **진짜 플랫폼 저장소** `D:\Sorce\UbisamCommonPlatform`(사내 Gitea) 로 옮겼다. `6991dfd` 이후 양쪽이 갈라져 있었고 — 우리 2커밋(트레이 · 창 자리 기억 · 세 갈래 묻는 창 · 탭 줄 크기 · 헤더 접기) vs 공용 4커밋(우클릭 메뉴 테마 · 앱 아이콘 · 업데이트 기본값 · HANDOFF) — **소스 파일이 하나도 겹치지 않아** 부딪힌 것은 `changelog.md` 한 군데뿐이었다(양쪽 줄 다 살려 날짜순으로 합침). 공용 쪽에 빠져 있던 문서도 채웠다: 가이드 8-5 에 헤더 접기, `HANDOFF.md` 5·6절에 우리 기능 — **C-3 은 플랫폼 changelog 에도 줄이 없었다.** 빌드해 이 PC 의 `D:\UbisamPlatform\bin` 까지 반영(오류 0 · 경고 4, 배포본 해시로 확인). `dist\` 는 손대지 않았다 — 사내 다른 PC 는 그대로다. 화면 회귀 아홉 벌 **208건 실패 0 · 예외 0**, MergeHub 재빌드 경고 0 · 오류 0. **앞으로 플랫폼은 공용 저장소에서만 고친다** | 아래 |
| 2026-09-28 | **12차 C-3** — 플랫폼 **헤더 줄도 접을 수 있게**. 지금까지 `TopBarCollapsed` 설정은 있었지만 그것을 켜고 끄는 자리가 **설정 화면 체크박스 하나뿐**이었고, 접으면 되살릴 띠도 단축키도 없어 다시 설정 화면으로 들어가야 했다. 아래 탭 줄 접기(11차 F-4)와 같은 방식으로 맞췄다 — 창 맨 위에 16 DIP 짜리 띠(`⌄ 헤더 펴기 · Ctrl+F11`, 사용자 결정대로 화살표와 안내 글만 두고 시계 · 이름은 넣지 않았다), **Ctrl+F11** 로 접고 펴기(아래 탭 줄 F11 과 짝), 하단 동작 줄에 [헤더 접기]. 접은 상태는 그 항목만 파일에 남긴다(설정 화면의 저장하지 않은 값을 건드리지 않게). 띠는 단추가 아니라 Border 다 — 한 자리에서 끌기와 누르기를 둘 다 받아야 하는데 단추에 DragMove 를 붙이면 마우스를 놓을 때 Click 이 오지 않아 펴지지 않는다. 끌린 거리가 없으면 누른 것으로 본다. 값: 모듈 시작 **144px → 51px(93px · 62 DIP 벌었다)**, 다시 켜도 접힌 채, F11 은 그대로 아래 탭 줄만. 하네스 `PlatformCfgTest` 29 → 42건(옛 플랫폼 소스를 가리키던 것도 새 저장소로 옮김), 화면 21건 통과. **접은 띠를 끌면 창이 따라오는가는 값으로 확정하지 못했다** — 흉내 낸 마우스로는 WPF DragMove 가 움직이지 않고, 기존 헤더 끌기로 견줘 봐도 같았다(시험 도구 한계). **배포함**(사용자 확인 뒤) — 모든 사내 프로그램에 함께 간다. 기본은 펴짐이라 아무것도 안 하면 달라지는 것이 없다. 플랫폼이 바뀌었으니 **그 위에 얹혀 도는 12차 화면 시험 아홉 벌을 2026-09-30 에 전부 다시 돌렸다 — 208건 실패 0 · 예외 0**(`run-all-c3.ps1`, 한 벌마다 앞 벌이 남긴 창 · 보관함을 치우고 들어간다). 끝나고 설정 네 파일을 시작 기준본과 바이트까지 같게 되돌렸다 | 아래 | | 2026-09-28 | **12차 C-3** — 플랫폼 **헤더 줄도 접을 수 있게**. 지금까지 `TopBarCollapsed` 설정은 있었지만 그것을 켜고 끄는 자리가 **설정 화면 체크박스 하나뿐**이었고, 접으면 되살릴 띠도 단축키도 없어 다시 설정 화면으로 들어가야 했다. 아래 탭 줄 접기(11차 F-4)와 같은 방식으로 맞췄다 — 창 맨 위에 16 DIP 짜리 띠(`⌄ 헤더 펴기 · Ctrl+F11`, 사용자 결정대로 화살표와 안내 글만 두고 시계 · 이름은 넣지 않았다), **Ctrl+F11** 로 접고 펴기(아래 탭 줄 F11 과 짝), 하단 동작 줄에 [헤더 접기]. 접은 상태는 그 항목만 파일에 남긴다(설정 화면의 저장하지 않은 값을 건드리지 않게). 띠는 단추가 아니라 Border 다 — 한 자리에서 끌기와 누르기를 둘 다 받아야 하는데 단추에 DragMove 를 붙이면 마우스를 놓을 때 Click 이 오지 않아 펴지지 않는다. 끌린 거리가 없으면 누른 것으로 본다. 값: 모듈 시작 **144px → 51px(93px · 62 DIP 벌었다)**, 다시 켜도 접힌 채, F11 은 그대로 아래 탭 줄만. 하네스 `PlatformCfgTest` 29 → 42건(옛 플랫폼 소스를 가리키던 것도 새 저장소로 옮김), 화면 21건 통과. **접은 띠를 끌면 창이 따라오는가는 값으로 확정하지 못했다** — 흉내 낸 마우스로는 WPF DragMove 가 움직이지 않고, 기존 헤더 끌기로 견줘 봐도 같았다(시험 도구 한계). **배포함**(사용자 확인 뒤) — 모든 사내 프로그램에 함께 간다. 기본은 펴짐이라 아무것도 안 하면 달라지는 것이 없다. 플랫폼이 바뀌었으니 **그 위에 얹혀 도는 12차 화면 시험 아홉 벌을 2026-09-30 에 전부 다시 돌렸다 — 208건 실패 0 · 예외 0**(`run-all-c3.ps1`, 한 벌마다 앞 벌이 남긴 창 · 보관함을 치우고 들어간다). 끝나고 설정 네 파일을 시작 기준본과 바이트까지 같게 되돌렸다 | 아래 |

View File

@ -443,6 +443,7 @@
| 상태 | 항목 | | 상태 | 항목 |
|---|---| |---|---|
| **완료** | **base 칸으로 머지하는 단축키**(사용자 요청, 12차 G-2) — `Alt+←` 를 **한 줄 왼쪽 파일로**(`[<]` 와 같은 일), `Alt+Shift+←` 를 **덩어리 왼쪽으로**(명령을 새로 만듦). `Alt+←` 는 원래 2-Way 에서 "이 줄 되돌리기" 였는데 이름·화살표와 하는 일이 달랐고, 그 뜻대로 하는 일이 B-1 로 생겨 넘겼다(되돌리기는 `Ctrl+Z` 만, 사용자 결정). 3-Way 는 안 건드림. 찾을 수 있게 **[이 덩어리 왼쪽으로] 단추**도 달았다. 화면 `g2-verify` 21건 · 하네스 `ShortcutTest` 88 → 101건. **`Alt+Shift` 조합은 윈도우가 입력 언어 전환으로 채가 흉내 낸 키로 못 눌렀다** — 기존 `Alt+Shift+→` 도 같아 시험 도구 한계이고, 덩어리는 단추로 확인했다 |
| **완료** | **undo 가 계속 이어진다**(사용자 보고, 12차 G-1) — 짝을 바꾼 뒤 `Ctrl+Z` 를 누르면 앞 파일 글 ↔ 지금 파일 글을 **끝없이 오갔다**(40번을 눌러도 안 끝남, 글자를 안 쳐도 남). 우리 목록이 비면 `Ctrl+Z` 가 키를 안 먹어 `TextBox` 자체 되돌리기로 넘어가고, 그것이 **앞 파일 글을 되살리면** `Reflow` 가 그걸 "친 것" 으로 보고 모델을 되돌리며 다시 쌓았다. 고침은 한 줄 — `LocalEditor` 에 `IsUndoEnabled="False"`. 되돌리기는 우리 목록만 쓴다. 화면 `g1-verify` 14건 · 편집 관련 다섯 벌 회귀 실패 0. 글자 **한 자 단위** 되돌리기는 없어지고 "타이핑이 멈춘 자리" 가 한 걸음이 됐다 | | **완료** | **undo 가 계속 이어진다**(사용자 보고, 12차 G-1) — 짝을 바꾼 뒤 `Ctrl+Z` 를 누르면 앞 파일 글 ↔ 지금 파일 글을 **끝없이 오갔다**(40번을 눌러도 안 끝남, 글자를 안 쳐도 남). 우리 목록이 비면 `Ctrl+Z` 가 키를 안 먹어 `TextBox` 자체 되돌리기로 넘어가고, 그것이 **앞 파일 글을 되살리면** `Reflow` 가 그걸 "친 것" 으로 보고 모델을 되돌리며 다시 쌓았다. 고침은 한 줄 — `LocalEditor` 에 `IsUndoEnabled="False"`. 되돌리기는 우리 목록만 쓴다. 화면 `g1-verify` 14건 · 편집 관련 다섯 벌 회귀 실패 0. 글자 **한 자 단위** 되돌리기는 없어지고 "타이핑이 멈춘 자리" 가 한 걸음이 됐다 |
| **완료** | `[<]` 단추가 있는 줄만 글이 밀린다(사용자 보고, 그림으로 짚어 줌) — 단추 칸 폭이 `Auto` 라 단추가 보이는 줄만 뒤 칸이 밀렸다(BASE 는 번호까지, LOCAL 은 코드가). `GutterBothWaysWidth` 로 두 칸이 같은 자리를 늘 잡아 두고, 모드에 따라 0 ↔ 단추 폭으로 갈아 끼운다. 편집기 여백도 같이 민다. 값으로 확인: 한 칸의 모든 줄이 같은 x(BASE 137 · LOCAL 1283), 글자 크기 · Git 모드도 그대로 | | **완료** | `[<]` 단추가 있는 줄만 글이 밀린다(사용자 보고, 그림으로 짚어 줌) — 단추 칸 폭이 `Auto` 라 단추가 보이는 줄만 뒤 칸이 밀렸다(BASE 는 번호까지, LOCAL 은 코드가). `GutterBothWaysWidth` 로 두 칸이 같은 자리를 늘 잡아 두고, 모드에 따라 0 ↔ 단추 폭으로 갈아 끼운다. 편집기 여백도 같이 민다. 값으로 확인: 한 칸의 모든 줄이 같은 x(BASE 137 · LOCAL 1283), 글자 크기 · Git 모드도 그대로 |
| **완료** | 왼쪽 줄 번호가 밀린다(위를 쫓다 따로 찾음) — 12차 B-1 로 왼쪽에도 반영할 수 있게 됐는데 왼쪽 번호를 다시 매기는 자리가 없었다. 줄을 넣으면 그 줄은 번호가 비고 뒤 줄은 옛 번호(`01 02 · 03 04 05 · 06`), 저장하면 8줄인데 번호는 6까지. `RenumberBase` 를 두어 두 파일 비교에서만 왼쪽 줄을 1부터 다시 맨다. "머지 반영함" 표시가 BASE 번호를 열쇠로 쓰므로 그 열쇠도 함께 옮긴다. Git · 네 칸은 손대지 않는다 | | **완료** | 왼쪽 줄 번호가 밀린다(위를 쫓다 따로 찾음) — 12차 B-1 로 왼쪽에도 반영할 수 있게 됐는데 왼쪽 번호를 다시 매기는 자리가 없었다. 줄을 넣으면 그 줄은 번호가 비고 뒤 줄은 옛 번호(`01 02 · 03 04 05 · 06`), 저장하면 8줄인데 번호는 6까지. `RenumberBase` 를 두어 두 파일 비교에서만 왼쪽 줄을 1부터 다시 맨다. "머지 반영함" 표시가 BASE 번호를 열쇠로 쓰므로 그 열쇠도 함께 옮긴다. Git · 네 칸은 손대지 않는다 |

View File

@ -24,6 +24,7 @@ namespace MergeHub.Services
NextFile, NextFile,
ToggleBookmark, ToggleBookmark,
AcceptBlock, AcceptBlock,
AcceptBlockToBase, // 12차 G-2 - 덩어리를 왼쪽 파일로
NextConflict, NextConflict,
PrevConflict, PrevConflict,
AutoMerge, AutoMerge,
@ -123,7 +124,13 @@ namespace MergeHub.Services
"화살표는 WinMerge 와 같이 '어느 쪽으로 복사하는가'를 가리킨다. 3-Way 에서 고칠 수 있는 칸은 " + "화살표는 WinMerge 와 같이 '어느 쪽으로 복사하는가'를 가리킨다. 3-Way 에서 고칠 수 있는 칸은 " +
"아래 MERGED 하나뿐이므로, 왼쪽 끝인 LOCAL(Mine) 을 결과로 가져오는 것이 여기서의 '오른쪽으로'다."), "아래 MERGED 하나뿐이므로, 왼쪽 끝인 LOCAL(Mine) 을 결과로 가져오는 것이 여기서의 '오른쪽으로'다."),
new ShortcutEntry(ShortcutAction.CopyRightToLeft, Key.Left, ModifierKeys.Alt, "병합·편집", new ShortcutEntry(ShortcutAction.CopyRightToLeft, Key.Left, ModifierKeys.Alt, "병합·편집",
"오른쪽 → 왼쪽 반영 (2-Way: 이 줄 되돌리기 / 3-Way: Take Theirs)"), "오른쪽 → 왼쪽 반영 (2-Way: 이 줄을 왼쪽 파일로 / 3-Way: Take Theirs)",
"12차 G-2 에 뜻을 바로잡았다. 예전에는 2-Way 에서 '이 줄 되돌리기'였는데, 이름과 화살표가 " +
"가리키는 '왼쪽으로 반영'이 12차 B-1 로 실제로 생겼다([<] 단추). 화살표는 늘 어느 쪽으로 " +
"가는가를 가리켜야 하므로 그쪽으로 넘겼다. 되돌리기는 Ctrl+Z 가 한다."),
new ShortcutEntry(ShortcutAction.AcceptBlockToBase, Key.Left, ModifierKeys.Alt | ModifierKeys.Shift, "병합·편집",
"이 덩어리를 왼쪽 파일로 - 이어지는 구간을 한 번에 (2-Way)",
"오른쪽으로 보내는 Alt+Shift+→ 의 거울이다."),
new ShortcutEntry(ShortcutAction.NextConflict, Key.Down, ModifierKeys.Alt | ModifierKeys.Shift, "병합·편집", new ShortcutEntry(ShortcutAction.NextConflict, Key.Down, ModifierKeys.Alt | ModifierKeys.Shift, "병합·편집",
"다음 충돌 (3-Way)", "차이가 아니라 <아직 안 정한 충돌>만 딛는다. 자동 병합과 짝이다."), "다음 충돌 (3-Way)", "차이가 아니라 <아직 안 정한 충돌>만 딛는다. 자동 병합과 짝이다."),
@ -136,7 +143,8 @@ namespace MergeHub.Services
"줄 하나씩만 되면 연속 다섯 줄을 다섯 번 눌러야 한다."), "줄 하나씩만 되면 연속 다섯 줄을 다섯 번 눌러야 한다."),
new ShortcutEntry(ShortcutAction.Undo, Key.Z, ModifierKeys.Control, "병합·편집", "실행 취소", new ShortcutEntry(ShortcutAction.Undo, Key.Z, ModifierKeys.Control, "병합·편집", "실행 취소",
"머지 반영을 되돌린다. 되돌릴 머지 작업이 없으면 편집기의 글자 되돌리기가 그대로 동작한다."), "머지 반영과 편집기에 친 글을 함께 되돌린다. 한 걸음은 <타이핑이 멈춘 자리>다(12차 G-1). " +
"12차 G-2 부터 '이 줄만 되돌리기'도 이 키가 맡는다 - Alt+← 는 왼쪽으로 반영하는 자리가 됐다."),
new ShortcutEntry(ShortcutAction.Redo, Key.Y, ModifierKeys.Control, "병합·편집", "다시 실행"), new ShortcutEntry(ShortcutAction.Redo, Key.Y, ModifierKeys.Control, "병합·편집", "다시 실행"),
new ShortcutEntry(ShortcutAction.ToggleBookmark, Key.M, ModifierKeys.Control, "이동·북마크", "지금 줄 북마크 설정/해제"), new ShortcutEntry(ShortcutAction.ToggleBookmark, Key.M, ModifierKeys.Control, "이동·북마크", "지금 줄 북마크 설정/해제"),

View File

@ -912,6 +912,74 @@ namespace MergeHub.ViewModels
private bool CanTakeLineToBase(MergeLine? line) => BothWays && line is { CanTakeBase: true }; private bool CanTakeLineToBase(MergeLine? line) => BothWays && line is { CanTakeBase: true };
/// <summary>
/// 캐럿이 있는 자리에서 <b>왼쪽으로 보낼 수 있는</b> 덩어리를 잡을 수 있는가(12차 G-2).
/// 2-Way 에서 <see cref="BlockAround"/> 는 <c>CanTakeBase</c> 로 덩어리를 잡으므로 그대로 쓴다 -
/// "가져올 수 있는 줄" 과 "보낼 수 있는 줄" 이 같은 줄이다. 방향만 반대다.
/// </summary>
private bool CanAcceptBlockToBase() => BothWays && BlockAround(CaretRow).Count > 0;
/// <summary>이 덩어리를 통째로 왼쪽 파일로 반영한다(12차 G-2). 한 줄짜리 <see cref="TakeLineToBase"/> 의 덩어리판.</summary>
[RelayCommand(CanExecute = nameof(CanAcceptBlockToBase))]
private void AcceptBlockToBase()
{
var block = BlockAround(CaretRow);
if (block.Count == 0) return;
var count = ApplyGroupToBase(block, "덩어리");
if (count > 0) MessageUtil.ShowSuccessToast($"{count}줄 왼쪽에 반영");
}
/// <summary>
/// 여러 줄을 <b>한 묶음으로</b> 왼쪽 파일에 반영한다(12차 G-2).
/// 오른쪽으로 보내는 <see cref="ApplyGroup"/> 의 거울 - 주석은 덩어리 첫 줄 위 한 줄,
/// 되돌리기는 한 번에(11차 F-2 와 같은 규칙).
/// </summary>
private int ApplyGroupToBase(IReadOnlyList<MergeLine> rows, string what)
{
var group = ++_lastGroup;
var changed = 0;
int? first = null, last = null;
foreach (var line in rows.ToList())
{
if (line.IsMergeNote) continue;
var number = line.HasLocal ? line.LocalNumber : line.BaseNumber;
// group 이 0 이 아니므로 ApplyToBase 는 줄마다 주석을 달지 않는다 - 아래에서 한 줄만 단다.
if (!line.CanTakeBase || !ApplyToBase(line, requery: false, group: group)) continue;
changed++;
if (number is int n) { first ??= n; last = n; }
}
if (changed == 0)
{
AfterEdit();
return 0;
}
var where = first is null ? string.Empty : first == last ? $"{first}행 " : $"{first}~{last}행 ";
var description = $"{where}{what} 왼쪽에 반영 ({changed}줄)";
var at = rows.FirstOrDefault(r => r.HasLocal && !r.IsMergeNote && Lines.Contains(r));
if (at is not null && _workspace.Settings.InsertMergeComment)
{
var note = BuildMergeNote(MergeNames.LocalSource(LocalSourceName));
_undo.Push(new LineEdit(at, at.BaseText, at.HasBase, at.BaseText, at.HasBase,
EditKind.NoteOnly, description)
{
Side = MergeLine.TextPane.Base,
InsertedNote = InsertBaseNoteAbove(at, note),
Group = group,
});
}
ClearRedo();
_workspace.AppendLog(description);
AfterEdit();
return changed;
}
/// <summary>[&lt;] - 이 줄을 왼쪽 파일로 반영한다(12차 B-1).</summary> /// <summary>[&lt;] - 이 줄을 왼쪽 파일로 반영한다(12차 B-1).</summary>
[RelayCommand(CanExecute = nameof(CanTakeLineToBase))] [RelayCommand(CanExecute = nameof(CanTakeLineToBase))]
private void TakeLineToBase(MergeLine? line) private void TakeLineToBase(MergeLine? line)
@ -1322,6 +1390,7 @@ namespace MergeHub.ViewModels
SavePlainCommand.NotifyCanExecuteChanged(); SavePlainCommand.NotifyCanExecuteChanged();
SaveLeftPlainCommand.NotifyCanExecuteChanged(); // 12차 B-1 SaveLeftPlainCommand.NotifyCanExecuteChanged(); // 12차 B-1
TakeLineToBaseCommand.NotifyCanExecuteChanged(); // 12차 B-1 TakeLineToBaseCommand.NotifyCanExecuteChanged(); // 12차 B-1
AcceptBlockToBaseCommand.NotifyCanExecuteChanged(); // 12차 G-2
ExportReportCommand.NotifyCanExecuteChanged(); ExportReportCommand.NotifyCanExecuteChanged();
AutoMergeCommand.NotifyCanExecuteChanged(); AutoMergeCommand.NotifyCanExecuteChanged();
@ -3552,6 +3621,7 @@ namespace MergeHub.ViewModels
partial void OnCaretRowChanged(int value) partial void OnCaretRowChanged(int value)
{ {
AcceptBlockCommand.NotifyCanExecuteChanged(); AcceptBlockCommand.NotifyCanExecuteChanged();
AcceptBlockToBaseCommand.NotifyCanExecuteChanged(); // 12차 G-2 - 왼쪽으로 보내는 짝
AcceptMemberCommand.NotifyCanExecuteChanged(); AcceptMemberCommand.NotifyCanExecuteChanged();
OnPropertyChanged(nameof(MemberHint)); OnPropertyChanged(nameof(MemberHint));
} }

View File

@ -1780,10 +1780,18 @@
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}" Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
ToolTip="선택한 줄을 REMOTE(Theirs) 내용으로"/> ToolTip="선택한 줄을 REMOTE(Theirs) 내용으로"/>
<Button Content="이 덩어리 반영" Command="{Binding AcceptBlockCommand}" <Button Content="이 덩어리 반영" x:Name="AcceptBlockButton" Command="{Binding AcceptBlockCommand}"
MinHeight="30" Margin="2,4,2,0" FontSize="11" MinHeight="30" Margin="2,4,2,0" FontSize="11"
ToolTip="차이가 이어지는 구간을 한 번에 반영합니다 (Alt+Shift+Right)"/> ToolTip="차이가 이어지는 구간을 한 번에 반영합니다 (Alt+Shift+Right)"/>
<!-- 12차 G-2: 위 단추의 거울. 두 파일 비교(BothWays)에서만 뜬다 -
Git 모드의 왼쪽은 지난 커밋이라 쓸 파일이 없다. -->
<Button Content="이 덩어리 왼쪽으로" x:Name="AcceptBlockToBaseButton"
Command="{Binding AcceptBlockToBaseCommand}"
MinHeight="30" Margin="2,4,2,0" FontSize="11"
Visibility="{Binding BothWays, Converter={StaticResource BoolToVis}}"
ToolTip="차이가 이어지는 구간을 한 번에 왼쪽 파일로 보냅니다 (Alt+Shift+Left)"/>
<!-- 11차 F-2: 캐럿이 놓인 클래스 멤버를 통째로. 못 잡으면 꺼지고 아래 한 줄이 까닭을 말한다. --> <!-- 11차 F-2: 캐럿이 놓인 클래스 멤버를 통째로. 못 잡으면 꺼지고 아래 한 줄이 까닭을 말한다. -->
<Button Content="이 메서드 반영" Command="{Binding AcceptMemberCommand}" <Button Content="이 메서드 반영" Command="{Binding AcceptMemberCommand}"
MinHeight="30" Margin="2,4,2,0" FontSize="11" MinHeight="30" Margin="2,4,2,0" FontSize="11"

View File

@ -1038,10 +1038,16 @@ namespace MergeHub.Views
case ShortcutAction.CopyLeftToRight: case ShortcutAction.CopyLeftToRight:
return Run(vm.IsThreeWay ? (ICommand)vm.AcceptLocalCommand : vm.AcceptCommand); return Run(vm.IsThreeWay ? (ICommand)vm.AcceptLocalCommand : vm.AcceptCommand);
// 2-Way 에서는 12차 G-2 부터 <이 줄을 왼쪽 파일로>다([<] 단추와 같은 일).
// 예전에는 '이 줄 되돌리기'였는데, 화살표가 가리키는 대로 왼쪽으로 보내는 자리로 넘겼다.
// 되돌리기는 Ctrl+Z 가 맡는다.
case ShortcutAction.CopyRightToLeft: case ShortcutAction.CopyRightToLeft:
return Run(vm.IsThreeWay ? (ICommand)vm.AcceptRemoteCommand : vm.RevertCommand); return Run(vm.IsThreeWay ? (ICommand)vm.AcceptRemoteCommand : vm.TakeLineToBaseCommand,
vm.IsThreeWay ? null : vm.SelectedLine);
// 이 덩어리를 왼쪽 파일로(12차 G-2). Alt+Shift+→ 의 거울이다.
case ShortcutAction.AcceptBlockToBase: return Run(vm.AcceptBlockToBaseCommand);
// 되돌릴 머지 작업이 없으면 키를 먹지 않는다 - 그래야 편집기의 글자 되돌리기가 살아 있다.
case ShortcutAction.Undo: return Run(vm.UndoCommand); case ShortcutAction.Undo: return Run(vm.UndoCommand);
case ShortcutAction.Redo: return Run(vm.RedoCommand); case ShortcutAction.Redo: return Run(vm.RedoCommand);
@ -1110,10 +1116,13 @@ namespace MergeHub.Views
} }
/// <summary>지금 못 하는 동작(예: 고칠 게 없는데 저장)이면 키를 먹지 않고 넘긴다.</summary> /// <summary>지금 못 하는 동작(예: 고칠 게 없는데 저장)이면 키를 먹지 않고 넘긴다.</summary>
private static bool Run(ICommand command) private static bool Run(ICommand command) => Run(command, null);
/// <summary>줄 하나를 받는 동작용(12차 G-2 - [&lt;] 처럼 고른 줄에 대고 하는 것).</summary>
private static bool Run(ICommand command, object? parameter)
{ {
if (!command.CanExecute(null)) return false; if (!command.CanExecute(parameter)) return false;
command.Execute(null); command.Execute(parameter);
return true; return true;
} }