쿼리 편집기 — 자동완성 위치 버그와 목록 가독성

■ 팝업이 화면 구석으로 날아가던 문제

두 가지가 겹쳐 있었다.
1. WPF Popup 은 열려 있는 동안 Placement 를 다시 계산하지 않는다. 글자를 칠 때마다
   HorizontalOffset/VerticalOffset 만 바꿔서는 처음 열린 자리에 그대로 머문다.
2. GetRectFromCharacterIndex 는 스크롤 밖이나 범위를 벗어난 위치에서 Empty 를 준다.
   그대로 쓰면 좌표가 무한대가 되어 팝업이 화면 밖으로 나간다.

기준점을 커서가 아니라 <b>완성 중인 낱말의 시작</b>으로 바꾸고(글자마다 흔들리지 않는다),
그 기준점이 달라졌을 때만 닫았다 다시 연다. Empty·무한대는 캐럿 → 원점 순으로 물러선다.

■ 별칭 없이 컬럼을 칠 때 아무것도 안 뜨던 문제

  select * FROM P_COMINF where comcht|

점을 찍어야만 컬럼을 제안하고 있었다. 그런데 별칭을 안 쓰는 쪽이 오히려 흔하고,
WHERE 절이야말로 컬럼 이름이 가장 많이 필요한 자리다.
이제 일반 위치에서도 이 쿼리가 참조하는 테이블(FROM/JOIN)의 컬럼을 먼저 제안한다.
FROM 뒤에 키워드가 오면 테이블로 보지 않고, 같은 테이블이 두 번 조인돼도 후보는 한 번만 만든다.

■ 치환 변수 목록 가독성

토큰이 60자가 넘고 앞 40자(M.CMM.HISOperatingInfo.bzPatientInfo.)가 모든 줄에서 똑같았다.
그대로 두 줄로 깔면 정작 다른 부분인 속성명이 오른쪽 끝에서 잘려 무엇이 무엇인지 구분되지 않는다.
설명 + 속성명 한 줄로 바꾸고 전체 토큰은 툴팁으로 옮겼다. 줄 수도 절반이 됐다.
DataRow 형태는 꼬리(.item("컬럼명"))를 빼고 속성명만 남긴다 — 컬럼 채우기라는 사실은 설명이 말해 준다.

■ 현재 줄 강조

긴 쿼리에서 지금 어디를 고치고 있는지 잃지 않도록 커서 줄에 옅은 띠를 깐다.
선택 중일 때는 끈다 — 선택 색과 겹치면 오히려 읽기 어렵다.

단위 테스트 6건 추가(별칭 없는 테이블 참조·중복 조인·FROM 뒤 키워드 배제·
짧은 형태에 클래스 이름 없음·길이 상한·DataRow 속성명).
회귀: 테스트 216/216, 편집 스모크 실패 0, 검증 실행 점검 10/10,
DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-13 15:28:10 +09:00
co-authored by Claude Opus 5
parent 79db31cb4d
commit f7c90979b9
7 changed files with 186 additions and 13 deletions
@@ -54,6 +54,8 @@ public sealed class SqlHighlightLayer : FrameworkElement
source.TextChanged += OnSourceChanged;
source.SizeChanged += OnSourceChanged;
source.Loaded += OnSourceLoaded;
// 커서가 움직이면 현재 줄 띠도 따라와야 한다
source.SelectionChanged += OnSourceChanged;
HookScroller();
}
@@ -64,6 +66,7 @@ public sealed class SqlHighlightLayer : FrameworkElement
source.TextChanged -= OnSourceChanged;
source.SizeChanged -= OnSourceChanged;
source.Loaded -= OnSourceLoaded;
source.SelectionChanged -= OnSourceChanged;
}
if (scroller is not null)
{
@@ -153,6 +156,9 @@ public sealed class SqlHighlightLayer : FrameworkElement
typeface, source.FontSize, Brushes.Black, dpi);
var lineHeight = probe.Height;
// 커서가 있는 줄 — 긴 쿼리에서 지금 어디를 고치고 있는지 잃지 않게 옅은 띠를 깐다
var caretLine = LineIndexOf(text, source.CaretIndex);
var lineIndex = 0;
var lineStart = 0;
var height = ActualHeight;
@@ -163,6 +169,13 @@ public sealed class SqlHighlightLayer : FrameworkElement
var lineEnd = newline < 0 ? text.Length : newline;
var y = offsetY + lineIndex * lineHeight;
if (lineIndex == caretLine && y + lineHeight >= 0 && y <= height
&& source.SelectionLength == 0
&& TryFindResource("B.Hover") is Brush band)
{
dc.DrawRectangle(band, null, new Rect(0, y, Math.Max(0, ActualWidth), lineHeight));
}
// 화면 밖 줄은 그리지 않는다 — 긴 쿼리에서 매 입력마다 전부 그리면 눈에 띄게 느려진다
if (y > height)
{
@@ -182,6 +195,21 @@ public sealed class SqlHighlightLayer : FrameworkElement
}
}
/// <summary>주어진 위치가 몇 번째 줄인지(0부터)</summary>
private static int LineIndexOf(string text, int index)
{
var line = 0;
var upto = Math.Clamp(index, 0, text.Length);
for (var i = 0; i < upto; i++)
{
if (text[i] == '\n')
{
line++;
}
}
return line;
}
/// <summary>
/// 한 줄을 조각별 색으로 그린다.
/// 조각 경계마다 <see cref="FormattedText"/> 를 새로 만들되, x 위치는 <b>앞부분 전체를 다시 재서</b> 잡는다 —