쿼리 편집기 — 서드파티 없이 만든 구문 강조와 편집 보조
배포본에 서드파티 편집기 DLL 을 넣지 않기로 해서 직접 만들었다. ■ 색칠 방식 — 투명 TextBox 뒤에 그린다 RichTextBox 로 직접 칠하면 입력할 때마다 서식을 다시 입혀야 해서 실행취소 묶음이 깨지고 IME 조합 중 깜빡인다. 대신 편집은 평범한 TextBox 가 그대로 하고(캐럿·선택·실행취소·IME·스크롤이 전부 공짜다) 글자만 투명하게 해서, 뒤에 깔린 SqlHighlightLayer 가 같은 자리에 색을 입혀 그린다. 맞춰야 하는 것은 하나뿐이다 — 글꼴·크기·여백·줄바꿈 설정이 TextBox 와 완전히 같을 것. 성능은 화면에 보이는 줄만 그려서 잡았다. x 위치는 앞부분을 다시 재서 잡는다 — 조각 폭을 누적하면 자간·힌팅 때문에 뒤로 갈수록 한두 픽셀씩 밀린다. ■ 조각내기(SqlTokenizer) — Core 에 순수 함수로 예약어 · 내장함수 · 숫자 · 문자열 · 주석 · 치환변수, 그리고 Broken. Broken 을 따로 둔 것이 요점이다. 이 쿼리는 EMR 런타임에서 돌고 실패해도 빈 catch 가 삼켜 화면에 아무 표시가 없다. 닫히지 않은 따옴표나 << >> 는 편집기에서 즉시 눈에 띄어야 한다. Oracle 방언 기준이고, '' 이스케이프를 문자열 끊김으로 오인하지 않는다. 줄바꿈을 넘긴 문자열은 닫는 따옴표를 빠뜨린 것으로 본다 — 안 그러면 뒤 문장 전체가 문자열로 물든다. 조각은 겹치지 않고 전 구간을 덮는다(빈틈이 있으면 그 글자가 안 그려진다). 테스트로 고정했다. ■ 편집 보조 줄 번호 · 글자/줄 수 · Tab·Shift+Tab 들여쓰기(선택 줄 단위) · Enter 자동 들여쓰기 · Ctrl+/ 주석 토글(섞인 상태면 전부 붙인다 — 토글이 예측 가능해진다). 저장 전 경고 두 가지를 하단에 띄운다: 닫히지 않은 조각, 그리고 카탈로그에 없는 치환 변수. 후자는 접두어를 임의로 줄여 쓴 경우를 잡는다 — 런타임은 그걸 치환하지 못하고 <<...>> 를 SQL 에 그대로 남겨 ORA 오류를 내며, 그 데이터소스를 참조하는 컨트롤이 전부 공백이 된다. ■ 치환 변수 패널 이름·설명·초성으로 좁히는 검색을 붙였다(ㅊㅌㅂㅎ → 차트번호). Enter 로도 삽입된다. 변수 목록 자체의 전량 이식은 별도 커밋으로 이어간다 — 현재 25개는 레거시 surface 의 절반이다. SQL 이 아닌 용도(상용구 문구)로 재사용할 때는 색칠을 끈다. 예약어와 겹치는 낱말이 엉뚱하게 물들면 안 된다. 단위 테스트 12건 추가(전 구간 덮기·대소문자·식별자 속 예약어 오검출·'' 이스케이프· 변수 통째 인식·닫히지 않은 조각 2종·줄 넘긴 문자열). 회귀: 테스트 174/174, 편집 스모크 실패 0, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
146d808aac
commit
de46f70035
@@ -1,16 +1,20 @@
|
||||
<Window x:Class="SheetMe.Designer.Views.QueryEditorWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="쿼리 편집" Width="960" Height="560" MinWidth="720" MinHeight="400"
|
||||
xmlns:ctl="clr-namespace:SheetMe.Designer.Controls"
|
||||
Title="쿼리 편집" Width="1060" Height="640" MinWidth="780" MinHeight="440"
|
||||
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
|
||||
Style="{StaticResource ThemedWindow}">
|
||||
<DockPanel Margin="12">
|
||||
<TextBlock x:Name="HintText" DockPanel.Dock="Top" Foreground="{DynamicResource B.Muted}" TextWrapping="Wrap" Margin="0,0,0,8"
|
||||
Text="작성 시점에 <<변수>> 가 실제 값으로 치환됩니다. 변수는 클래스 전체 이름을 포함해야 하므로 우측 목록을 더블클릭해 삽입하세요 — 임의로 줄여 쓰면 치환되지 않고 SQL 에 그대로 남아 오류가 납니다."/>
|
||||
Text="작성 시점에 <<변수>> 가 실제 값으로 치환됩니다. 변수는 클래스 전체 이름을 포함해야 하므로 우측 목록에서 삽입하세요 — 임의로 줄여 쓰면 치환되지 않고 SQL 에 그대로 남아 오류가 납니다."/>
|
||||
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<TextBlock x:Name="LengthText" VerticalAlignment="Center" Margin="0,0,12,0" Foreground="{DynamicResource B.Muted}"/>
|
||||
<Button Content="확인" Padding="20,5" Click="OnConfirm"/>
|
||||
<TextBlock x:Name="ProblemText" VerticalAlignment="Center" Margin="0,0,12,0"
|
||||
Foreground="{DynamicResource B.Danger}" FontSize="11.5"
|
||||
TextTrimming="CharacterEllipsis" MaxWidth="420"/>
|
||||
<TextBlock x:Name="LengthText" VerticalAlignment="Center" Margin="0,0,12,0" Foreground="{DynamicResource B.Muted}" FontSize="11.5"/>
|
||||
<Button Content="확인" Padding="20,5" Click="OnConfirm" IsDefault="True"/>
|
||||
<Button Content="취소" Padding="20,5" Margin="8,0,0,0" IsCancel="True"/>
|
||||
</StackPanel>
|
||||
|
||||
@@ -18,32 +22,70 @@
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition x:Name="SplitterColumn" Width="8"/>
|
||||
<ColumnDefinition x:Name="VariableColumn" Width="330"/>
|
||||
<ColumnDefinition x:Name="VariableColumn" Width="360"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- SQL 편집 영역 -->
|
||||
<TextBox x:Name="SqlBox" Grid.Column="0"
|
||||
FontFamily="Consolas, D2Coding, 굴림체" FontSize="13"
|
||||
AcceptsReturn="True" AcceptsTab="True"
|
||||
TextWrapping="NoWrap"
|
||||
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
|
||||
TextChanged="OnSqlChanged"/>
|
||||
<!-- SQL 편집 영역.
|
||||
색칠은 뒤에 깔린 SqlHighlightLayer 가 하고, 편집은 위에 얹힌 투명 TextBox 가 그대로 한다
|
||||
(캐럿·선택·실행취소·IME 를 직접 만들지 않기 위해서다).
|
||||
두 요소의 글꼴·크기·여백이 어긋나면 글자와 색이 어긋난다 — 반드시 같이 고칠 것. -->
|
||||
<Border Grid.Column="0" BorderThickness="1" CornerRadius="6" ClipToBounds="True"
|
||||
BorderBrush="{DynamicResource B.InputBorder}" Background="{DynamicResource B.Input}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 공용 ColSplitter 스타일을 쓴다 — Background 만 주면 양 테마에서 배경과 1.1~1.3:1 로 붙어 손잡이가 안 보인다 -->
|
||||
<GridSplitter Grid.Column="1" Style="{StaticResource ColSplitter}"/>
|
||||
<!-- 줄 번호 -->
|
||||
<Border Grid.Column="0" Background="{DynamicResource B.Surface}" Padding="6,6,6,6">
|
||||
<TextBlock x:Name="LineNumbers" FontFamily="Consolas, D2Coding, 굴림체" FontSize="13"
|
||||
Foreground="{DynamicResource B.Muted}" TextAlignment="Right" MinWidth="26"/>
|
||||
</Border>
|
||||
|
||||
<!-- 치환 변수 목록 (SQL 용도에서만 표시) -->
|
||||
<Grid Grid.Column="1">
|
||||
<ctl:SqlHighlightLayer x:Name="Highlight" IsHitTestVisible="False"/>
|
||||
<TextBox x:Name="SqlBox"
|
||||
FontFamily="Consolas, D2Coding, 굴림체" FontSize="13"
|
||||
AcceptsReturn="True" AcceptsTab="False"
|
||||
TextWrapping="NoWrap" Padding="6"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="Transparent"
|
||||
CaretBrush="{DynamicResource B.Ink}"
|
||||
SelectionBrush="{DynamicResource B.Accent}" SelectionOpacity="0.35"
|
||||
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
|
||||
TextChanged="OnSqlChanged" PreviewKeyDown="OnSqlKeyDown"
|
||||
AutomationProperties.Name="SQL 편집"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<GridSplitter Grid.Column="1" Style="{StaticResource ColSplitter}"/>
|
||||
|
||||
<!-- 치환 변수 (SQL 용도에서만) -->
|
||||
<DockPanel x:Name="VariablePane" Grid.Column="2">
|
||||
<TextBlock DockPanel.Dock="Top" Text="치환 변수 (더블클릭 삽입)" FontWeight="Bold"
|
||||
<TextBlock DockPanel.Dock="Top" Text="치환 변수" FontWeight="Bold"
|
||||
Foreground="{DynamicResource B.Muted}" Margin="4,0,0,6"/>
|
||||
<TextBox x:Name="VariableSearch" DockPanel.Dock="Top" Margin="0,0,0,6" Padding="6,3"
|
||||
VerticalContentAlignment="Center" TextChanged="OnVariableSearchChanged"
|
||||
AutomationProperties.Name="치환 변수 검색"
|
||||
ToolTip="이름·설명·초성으로 찾습니다 (예: ㅊㅌㅂㅎ → 차트번호)"/>
|
||||
<TextBlock DockPanel.Dock="Bottom" x:Name="VariableHint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource B.Muted}" Margin="4,6,0,0"
|
||||
Text="더블클릭 또는 Enter 로 커서 위치에 삽입합니다."/>
|
||||
<ListBox x:Name="VariableList" FontSize="11" MouseDoubleClick="OnInsertVariable"
|
||||
KeyDown="OnVariableListKeyDown"
|
||||
FontFamily="Consolas, D2Coding, 굴림체"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto">
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
AutomationProperties.Name="치환 변수 목록">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0,1" ToolTip="{Binding Token}">
|
||||
<TextBlock Text="{Binding Description}" FontFamily="Segoe UI, 맑은 고딕" FontSize="12"/>
|
||||
<TextBlock Text="{Binding Token}" Foreground="{DynamicResource B.Muted}" FontSize="10"/>
|
||||
<TextBlock Text="{Binding Description}" FontFamily="Segoe UI, 맑은 고딕" FontSize="12"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Text="{Binding Token}" Foreground="{DynamicResource B.Muted}" FontSize="10"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
||||
Reference in New Issue
Block a user