E-5, E-6: 반영 색 유지 · 차이 이동 시 코드 겹침

E-5. 줄을 반영한 뒤 비교 규칙을 하나 건드리면 초록(머지 반영함)이던 줄이
보라(직접 수정)로 바뀌었다.

반영 표시가 되돌리기 스택에만 있었다. 규칙을 바꾸면 화면을 통째로 다시
만들면서 그 스택을 비운다. 그러면 표시가 사라지고 남는 단서는 "글이 원본과
다르다"뿐이라 직접 수정으로 칠해진다. B-3 에서 Take Mine 이 충돌을 못 지우던
것과 같은 뿌리다.

- 반영할 때 줄 번호로도 남긴다. BASE 줄 번호, 없으면 MINE 줄 번호.
- 다시 만들 때 반영 여부를 먼저 본다. 반영한 줄은 직접 수정 조건에도 걸린다.
- 되돌리면 그 기록도 함께 지운다.

곁들여 머지 주석 줄도 같은 이유로 보라색이 됐다. 주석 줄이라는 표시 역시 줄
객체에만 있어서 다시 만들면 사라진다. 글을 보고 알아보게 했다
(MergeNote.LooksLikeNote).

E-6. Alt+위/아래로 차이를 옮겨 다니면 칸 아래 상태바 위에 코드가 겹쳐
그려졌다. 표시층은 편집기 글자와 눈높이를 맞추려고 RenderTransform 으로 살짝
밀어 두는데, 그건 배치가 아니라 그리기라 칸 밖으로 나가도 막는 것이 없다.
격자의 행도 자기 칸 밖으로 나가는 자식을 자르지 않는다. 코드가 놓이는 자리를
자르는 상자로 감쌌다. 네 칸 모두.

확인: 반영 후 공백/Aa 규칙을 눌러도 머지 반영함 2 · 직접 수정 0 그대로.
Alt+아래 8회, Alt+위 6회 눌러도 마지막 줄이 경계에서 깨끗이 잘린다.
NoteTest 13건 추가. 전체 399 -> 412건 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
김승준 2026-09-09 13:50:13 +09:00
parent 05c74fcb0e
commit 4f33cacd4f
4 changed files with 208 additions and 57 deletions

View File

@ -142,6 +142,8 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
| **완료** | E-2 칸 폭을 줄였다 늘리면 행 높이가 안 돌아옴 |
| **완료** | E-3 경로 줄과 코드 사이의 빈 공간 |
| **완료** | E-4 2-Way 로 돌아오면 LOCAL 옆에 빈 칸이 남음 |
| **완료** | E-5 반영한 줄이 규칙을 바꾸면 "직접 수정" 색으로 바뀜 |
| **완료** | E-6 차이로 이동할 때 코드가 바닥 글 위에 겹쳐 그려짐 |
---
@ -232,6 +234,41 @@ REMOTE 칸이 접히면서 생긴 자리를 <b>아무도 채우지 않는다</b>
**확인**: 3-Way 에서 구분선을 끌고 2-Way 로 돌아와도 두 칸이 폭을 꽉 채운다.
2-Way 에서 끌어 놓은 비율은 다른 모니터를 다녀와도 그대로다.
#### E-5. 반영한 줄이 규칙을 바꾸면 "직접 수정" 색이 되던 문제
줄을 반영(Accept)한 뒤 비교 규칙을 하나 건드리면 그 줄이 초록(머지 반영함)에서
보라(직접 수정)로 바뀌었다.
**반영 표시가 되돌리기 스택에만 있었다.** 규칙을 바꾸면 화면을 통째로 다시 만들면서 그 스택을
비운다. 그러면 표시가 사라지고 남는 단서는 "글이 원본과 다르다"뿐이라 직접 수정으로 칠해진다.
B-3 에서 Take Mine 이 충돌을 못 지우던 것과 같은 뿌리다.
- 반영할 때 <b>줄 번호로도</b> 남긴다. BASE 줄 번호, 없으면 MINE 줄 번호 - 둘 다 얼어 있는 값이라
다시 정렬해도 살아남는다.
- 다시 만들 때 반영 여부를 <b>먼저</b> 본다. 반영한 줄은 직접 수정 조건에도 걸리므로 순서가 중요하다.
- 되돌리면 그 기록도 함께 지운다.
**곁들여** 머지 주석 줄도 같은 이유로 보라색이 됐다. 주석 줄이라는 표시 역시 줄 객체에만 있어서
다시 만들면 사라진다. 글을 보고 알아보게 했다(`MergeNote.LooksLikeNote`). 사람이 우연히 같은
모양으로 쓴 주석을 잘못 볼 수 있지만, 그래도 색이 달라질 뿐이고 반대로 못 알아보면 반영할 때마다
"직접 수정"이 하나씩 늘어난다.
**확인**: 반영 → 공백 규칙 → Aa 규칙 순으로 눌러도 `머지 반영함 2 · 직접 수정 0` 그대로.
`NoteTest` 13건 추가(16 → 29건).
#### E-6. 차이로 이동하면 코드가 바닥 글 위에 겹쳐 그려지던 문제
`Alt+↑/↓` 로 차이를 옮겨 다니면 <b>칸 아래 상태바 위에 코드가 겹쳐</b> 그려졌다.
표시층은 편집기 글자와 눈높이를 맞추려고 `RenderTransform` 으로 살짝 밀어 둔다. 그런데
RenderTransform 은 <b>배치가 아니라 그리기</b>라 칸 밖으로 나가도 아무도 막지 않는다.
격자(Grid)의 행도 자기 칸 밖으로 나가는 자식을 자르지 않는다.
코드가 놓이는 자리를 자르는 상자로 감쌌다(`ClipToBounds`). 네 칸 모두.
**확인**: `Alt+↓` 8회 · `Alt+↑` 6회 눌러도 마지막 줄이 칸 경계에서 깨끗이 잘린다.
**세 가지 모두 확인**: 세로 막대를 끌면 세 칸이 함께 움직인다.
2560×1600 ↔ 1920×1080 을 오가도, 가운데 구분선을 끌었다 되돌려도 처음과 같은 화면이 된다.
테스트 399건 그대로 통과.

View File

@ -1,5 +1,6 @@
using System;
using System;
using System.IO;
using System.Text.RegularExpressions;
namespace MergeHub.Services
{
@ -33,6 +34,43 @@ namespace MergeHub.Services
return close is null ? $"{open} {body}" : $"{open} {body} {close}";
}
/// <summary>
/// "[사람] 2026-09-09" 로 시작하는 모양. 우리가 만든 주석의 몸통이다.
/// </summary>
private static readonly Regex NoteBody =
new Regex(@"^\[[^\]]+\]\s+\d{4}-\d{2}-\d{2}(\s|$)", RegexOptions.Compiled);
/// <summary>
/// 이 줄이 <b>우리가 남긴 머지 주석</b>인지 글만 보고 판단한다.
///
/// 왜 필요한가 - 주석 줄에는 "이건 주석 줄"이라는 표시가 붙어 있는데, 비교 규칙을 바꾸는 등
/// 화면을 다시 만들면 줄 객체가 새로 생기면서 그 표시가 사라진다. 그러면 주석 줄이
/// <b>사용자가 직접 넣은 줄</b>로 보여 "직접 수정" 색이 된다.
/// 남아 있는 단서가 글자밖에 없으므로 여기서 다시 알아본다.
///
/// 사람이 우연히 같은 모양의 주석을 썼다면 잘못 볼 수 있다. 그래도 색이 달라질 뿐이고,
/// 반대로 못 알아보면 반영할 때마다 "직접 수정"이 하나씩 늘어난다.
/// </summary>
public static bool LooksLikeNote(string? line, string? path)
{
if (string.IsNullOrWhiteSpace(line)) return false;
var (open, close) = CommentTokensFor(path);
var text = line!.Trim();
if (!text.StartsWith(open, StringComparison.Ordinal)) return false;
var body = text.Substring(open.Length).TrimStart();
if (close is not null)
{
if (!body.EndsWith(close, StringComparison.Ordinal)) return false;
body = body.Substring(0, body.Length - close.Length).TrimEnd();
}
return NoteBody.IsMatch(body);
}
/// <summary>코드 줄 끝에 주석을 붙인다. 줄 끝 공백은 정리한다.</summary>
public static string Append(string line, string note)
{

View File

@ -635,8 +635,8 @@ namespace MergeHub.ViewModels
_baseTextSnapshot = baseText;
_remoteTextSnapshot = threeWay ? remoteText : string.Empty;
_mineTextSnapshot = threeWay ? localText : string.Empty;
_resolvedBaseNumbers.Clear(); // 파일이 바뀌면 앞 파일의 결정은 뜻이 없다
_resolvedMineNumbers.Clear();
_appliedBaseNumbers.Clear(); // 파일이 바뀌면 앞 파일의 결정은 뜻이 없다
_appliedMineNumbers.Clear();
_originalLocalText = BuildLocalText();
SyncEditorFromLines();
@ -921,6 +921,9 @@ namespace MergeHub.ViewModels
{
InsertedNote = InsertNoteAbove(line, note),
});
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
MarkApplied(line);
_redo.Clear();
_workspace.AppendLog(description);
@ -1012,8 +1015,7 @@ namespace MergeHub.ViewModels
// 이미 내 원본과 같다. 충돌 줄이면 "내 것으로 정했다"는 표시만 남긴다.
// 내용이 안 바뀌므로 다시 정렬하면 이 표시가 사라진다 - BASE 줄 번호로 따로 붙들어 둔다.
line.IsApplied = true;
if (line.BaseNumber is int resolvedBase) _resolvedBaseNumbers.Add(resolvedBase);
else if (line.OriginalLocalNumber is int resolvedMine) _resolvedMineNumbers.Add(resolvedMine);
MarkApplied(line);
AfterEdit();
return;
}
@ -1028,6 +1030,9 @@ namespace MergeHub.ViewModels
_undo.Push(new LineEdit(line, before, beforeHasLocal, after, afterHasLocal,
EditKind.TakeBase, description));
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
MarkApplied(line);
_redo.Clear();
_workspace.AppendLog(description);
@ -1095,6 +1100,9 @@ namespace MergeHub.ViewModels
{
InsertedNote = InsertNoteAbove(line, note),
});
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
MarkApplied(line);
_redo.Clear();
_workspace.AppendLog(description);
@ -1286,11 +1294,23 @@ namespace MergeHub.ViewModels
Lines.Clear();
foreach (var row in rows)
{
if (row.LocalNumber is int number && touched.Contains(number))
row.IsManuallyEdited = true;
// 반영한 줄인지를 <b>먼저</b> 본다.
//
// 반영한 줄은 글이 원본과 달라지므로 아래 "직접 수정" 조건에도 걸린다.
// 그대로 두면 규칙을 하나 건드리는 순간 반영한 줄이 전부 보라색(직접 수정)이 된다.
// 사람이 직접 친 것과 버튼으로 가져온 것은 뜻이 다르니 섞이면 안 된다.
row.IsApplied = IsMarkedApplied(row);
// "내 것으로 간다"고 정했던 충돌은 내용이 그대로라 글만 봐서는 알 수 없다.
if (IsDecidedAsMine(row)) row.IsApplied = true;
// 우리가 남긴 머지 주석 줄은 "사용자가 직접 넣은 줄"이 아니다.
// 표시가 줄 객체에만 있어서 다시 만들면 사라진다 - 글을 보고 되찾는다.
if (MergeNote.LooksLikeNote(row.LocalText, SelectedFile))
{
row.IsMergeNote = true;
row.IsApplied = true;
}
if (!row.IsApplied && row.LocalNumber is int number && touched.Contains(number))
row.IsManuallyEdited = true;
Lines.Add(row);
}
@ -1496,8 +1516,8 @@ namespace MergeHub.ViewModels
_baseTextSnapshot = baseText;
_remoteTextSnapshot = string.Empty; // 파일 두 개 비교는 늘 2-Way 다
_mineTextSnapshot = string.Empty;
_resolvedBaseNumbers.Clear();
_resolvedMineNumbers.Clear();
_appliedBaseNumbers.Clear();
_appliedMineNumbers.Clear();
RefreshLanguage();
var rows = BuildLines(baseText, localText);
@ -1989,6 +2009,14 @@ namespace MergeHub.ViewModels
edit.Line.LocalText = toBefore ? edit.Before : edit.After;
edit.Line.HasLocal = toBefore ? edit.BeforeHasLocal : edit.AfterHasLocal;
// 되돌리면 반영 표시도 함께 풀어야 한다. 안 그러면 규칙을 바꿔 화면을 다시 만들 때
// 되돌린 줄이 다시 "머지 반영함"으로 살아난다.
if (edit.Kind == EditKind.TakeBase)
{
if (toBefore) UnmarkApplied(edit.Line);
else MarkApplied(edit.Line);
}
// 이 편집이 끼워 넣은 주석 줄도 함께 넣고 뺀다.
// 본문만 되돌리고 주석을 남기면 파일에 유령 주석이 쌓인다.
if (edit.InsertedNote is not { } note) return;
@ -2006,21 +2034,44 @@ namespace MergeHub.ViewModels
}
/// <summary>
/// "머지 반영함"과 "직접 수정" 표시를 편집 이력에서 다시 계산한다.
/// 둘을 섞으면 안 된다 - 사용자가 직접 지운 줄이 "머지 반영함"으로 보이면 안 되기 때문이다.
/// Undo 로 되돌린 줄은 자동으로 표시가 풀린다.
/// "이 줄은 반영했다"를 <b>줄 번호로</b> 기억해 둔다.
///
/// 반영 표시는 지금까지 되돌리기 스택에만 있었다. 그런데 비교 규칙을 바꾸면 화면을 통째로
/// 다시 만들면서 스택을 비우므로, 그때 표시가 사라지고 <b>글이 원본과 다르다는 이유만으로
/// "직접 수정"으로 칠해졌다.</b> 실제로 Accept 한 줄이 규칙 하나 건드리자 보라색이 됐다.
///
/// 열쇠는 얼어 있는 번호여야 한다 - BASE 줄 번호, 없으면(양쪽이 각자 새 줄을 넣은 자리)
/// MINE 줄 번호.
/// </summary>
private void MarkApplied(MergeLine line)
{
if (line.BaseNumber is int baseNumber) _appliedBaseNumbers.Add(baseNumber);
else if (line.OriginalLocalNumber is int mineNumber) _appliedMineNumbers.Add(mineNumber);
}
/// <summary>되돌리면 그 표시도 함께 지운다. 안 그러면 되돌린 줄이 계속 "반영함"으로 남는다.</summary>
private void UnmarkApplied(MergeLine line)
{
if (line.BaseNumber is int baseNumber) _appliedBaseNumbers.Remove(baseNumber);
else if (line.OriginalLocalNumber is int mineNumber) _appliedMineNumbers.Remove(mineNumber);
}
/// <summary>
/// "내 것으로 간다"고 정해 둔 충돌 줄인지.
/// "반영했다"고 표시해 둔 줄인지.
///
/// 그 결정은 <b>글을 바꾸지 않는다</b>(이미 내 원본과 같은 줄이다). 그래서 되돌리기
/// 스택에도 남지 않고, 다시 정렬하면 줄 객체 자체가 새로 만들어진다.
/// 붙들어 둘 것이 줄 번호밖에 없어 여기에 따로 모아 둔다.
/// </summary>
private bool IsDecidedAsMine(MergeLine line) =>
private bool IsMarkedApplied(MergeLine line) =>
line.BaseNumber is int baseNumber
? _resolvedBaseNumbers.Contains(baseNumber)
: line.OriginalLocalNumber is int mineNumber && _resolvedMineNumbers.Contains(mineNumber);
? _appliedBaseNumbers.Contains(baseNumber)
: line.OriginalLocalNumber is int mineNumber && _appliedMineNumbers.Contains(mineNumber);
/// <summary>
/// "머지 반영함"과 "직접 수정" 표시를 편집 이력에서 다시 계산한다.
/// 둘을 섞으면 안 된다 - 사용자가 직접 지운 줄이 "머지 반영함"으로 보이면 안 되기 때문이다.
/// Undo 로 되돌린 줄은 자동으로 표시가 풀린다.
/// </summary>
private void RefreshAppliedFlags()
{
@ -2040,8 +2091,8 @@ namespace MergeHub.ViewModels
{
// "내 것으로 간다"고 정한 충돌은 글을 바꾸지 않아 되돌리기 스택에 남지 않는다.
// 그것까지 여기서 지워 버리면 Take Mine 을 눌러도 남은 충돌이 줄지 않는다.
line.IsApplied = applied.Contains(line) || IsDecidedAsMine(line);
line.IsManuallyEdited = edited.Contains(line);
line.IsApplied = applied.Contains(line) || IsMarkedApplied(line);
line.IsManuallyEdited = !line.IsApplied && edited.Contains(line);
if (!line.IsApplied) line.AppliedNote = null;
}
@ -2174,13 +2225,13 @@ namespace MergeHub.ViewModels
/// 행이 새로 만들어지는데, 이걸 들고 있지 않으면 이미 정한 충돌이 되살아나
/// "남은 충돌"이 줄지 않는다. BASE 는 얼어 있으므로 그 줄 번호가 안전한 열쇠다.
/// </summary>
private readonly HashSet<int> _resolvedBaseNumbers = new HashSet<int>();
private readonly HashSet<int> _appliedBaseNumbers = new HashSet<int>();
/// <summary>
/// BASE 에 없는 충돌(양쪽이 각자 새 줄을 넣은 자리)용. 이런 줄은 BASE 줄 번호가 없으므로
/// 역시 얼어 있는 MINE 줄 번호로 붙든다.
/// </summary>
private readonly HashSet<int> _resolvedMineNumbers = new HashSet<int>();
private readonly HashSet<int> _appliedMineNumbers = new HashSet<int>();
private bool CanAutoMerge() => IsThreeWay && Lines.Any(l => l.ThreeWay == ThreeWayState.RemoteChanged);

View File

@ -810,7 +810,10 @@
<Border Grid.Column="2" Grid.Row="0" BorderThickness="1"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<Grid>
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="PaneHeader"/>
<RowDefinition Height="*"/>
@ -823,16 +826,20 @@
TextTrimming="CharacterEllipsis"/>
</StackPanel>
<ListBox Grid.Row="1" x:Name="OriginalLocalLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource OriginalLocalLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다. 표시층의 미세 보정은 배치가 아니라
그리기라 자르지 않으면 아래 바닥 글 위에 코드가 겹쳐 그려진다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<ListBox x:Name="OriginalLocalLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource OriginalLocalLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
</Grid>
<!--
원본 칸의 가로 스크롤 막대. 3-Way 에서 워드랩을 끄고 긴 줄이 있을 때만 보인다.
@ -878,7 +885,10 @@
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="PaneHeader"/>
<RowDefinition Height="*"/>
@ -911,16 +921,20 @@
</Grid>
</StackPanel>
<ListBox Grid.Row="1" x:Name="BaseLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource BaseLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다. 표시층의 미세 보정은 배치가 아니라
그리기라 자르지 않으면 아래 바닥 글 위에 코드가 겹쳐 그려진다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<ListBox x:Name="BaseLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource BaseLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
</Grid>
<!--
원본 칸의 가로 스크롤 막대. 3-Way 에서 워드랩을 끄고 긴 줄이 있을 때만 보인다.
@ -974,7 +988,10 @@
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition x:Name="MergedHeaderRow" Height="Auto"/>
<RowDefinition Height="*"/>
@ -1009,7 +1026,8 @@
</Grid>
</StackPanel>
<Grid Grid.Row="1">
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다 - 표시층 보정은 그리기라 자르지 않으면 바닥 글 위에 겹친다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
@ -1233,7 +1251,10 @@
<Border Grid.Column="6" Grid.Row="0" BorderThickness="1"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<Grid>
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="PaneHeader"/>
<RowDefinition Height="*"/>
@ -1258,16 +1279,20 @@
</StackPanel>
</StackPanel>
<ListBox Grid.Row="1" x:Name="RemoteLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource RemoteLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다. 표시층의 미세 보정은 배치가 아니라
그리기라 자르지 않으면 아래 바닥 글 위에 코드가 겹쳐 그려진다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<ListBox x:Name="RemoteLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource RemoteLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
</Grid>
<!--
원본 칸의 가로 스크롤 막대. 3-Way 에서 워드랩을 끄고 긴 줄이 있을 때만 보인다.