diff --git a/README.md b/README.md index 29bde15..482255c 100644 --- a/README.md +++ b/README.md @@ -1351,3 +1351,6 @@ MIT License - 업데이트: 2026-04-06 18:34 (KST) - 다른 앱에서 타이핑할 때도 AX Copilot가 키 입력 훅 경로에서 과하게 개입하던 부분을 줄였다. [InputListener.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Core/InputListener.cs)는 이제 모든 키다운마다 시스템 파일 대화상자 판정을 하지 않고, 실제 핫키 메인 키·캡처 메인 키·키 필터가 필요한 경우에만 억제 창 검사를 수행한다. - [SnippetExpander.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Core/SnippetExpander.cs)는 추적 중이 아닐 때 `;` 시작 키 외에는 즉시 반환하도록 바꿨다. 이전에는 일반 타이핑 중에도 모든 키마다 `Ctrl/Alt/Shift` 상태를 읽고 버퍼 로직을 거쳤는데, 이제는 실제 스니펫 시작 상황에서만 그런 검사를 하게 되어 글로벌 키보드 훅의 평상시 부담을 줄였다. +업데이트: 2026-04-06 20:18 (KST) +- AX Agent 메시지 마크다운 렌더에 코드 심볼 강조를 추가해 Cowork/Code 답변의 파일 경로·camelCase/PascalCase·snake_case가 더 선명하게 보이도록 조정했다. +- 코드 탭 입력부 위에 저장소/브랜치/변경 수치를 보여주는 Git 요약 배너를 추가해 `claude-code` 스타일의 repo context를 더 빠르게 읽을 수 있게 맞췄다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 6da5a78..bf4d32c 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5018,3 +5018,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - Document update: 2026-04-06 18:34 (KST) - `SnippetExpander.cs` now short-circuits immediately for normal typing unless snippet tracking is already active or the current key is the `;` trigger. This removes repeated `GetAsyncKeyState` and buffer logic from ordinary typing in other apps and makes the low-level hook substantially lighter under continuous input. +업데이트: 2026-04-06 20:18 (KST) +- Improved AX Agent markdown rendering for Cowork/Code by highlighting file paths plus code-like symbols, and added a compact repository summary banner above the Code composer to surface branch and diff context closer to `claude-code`. diff --git a/src/AxCopilot/Services/MarkdownRenderer.cs b/src/AxCopilot/Services/MarkdownRenderer.cs index e427b9b..68172ad 100644 --- a/src/AxCopilot/Services/MarkdownRenderer.cs +++ b/src/AxCopilot/Services/MarkdownRenderer.cs @@ -12,6 +12,9 @@ namespace AxCopilot.Services; /// public static class MarkdownRenderer { + private static readonly Brush FilePathBrush = new SolidColorBrush(Color.FromRgb(0x3B, 0x82, 0xF6)); + private static readonly Brush CodeSymbolBrush = new SolidColorBrush(Color.FromRgb(0xF2, 0x8C, 0x79)); + public static StackPanel Render(string markdown, Brush textColor, Brush secondaryColor, Brush accentColor, Brush codeBg) { var panel = new StackPanel(); @@ -272,19 +275,34 @@ public static class MarkdownRenderer /// 파일 경로 활성 여부 (설정 연동). public static bool EnableFilePathHighlight { get; set; } = true; + /// 코드 심볼 강조 활성 여부 (설정/탭 연동). + public static bool EnableCodeSymbolHighlight { get; set; } = true; + + private static readonly Regex CodeSymbolPattern = new( + @"(?일반 텍스트에서 파일 경로 패턴을 감지하여 파란색으로 강조합니다. private static void AddPlainTextWithFilePaths(InlineCollection inlines, string text) { - if (!EnableFilePathHighlight) + if (!EnableFilePathHighlight && !EnableCodeSymbolHighlight) { inlines.Add(new Run(text)); return; } - var matches = FilePathPattern.Matches(text); - if (matches.Count == 0) + MatchCollection? matches = null; + if (EnableFilePathHighlight) + matches = FilePathPattern.Matches(text); + if (matches == null || matches.Count == 0) { - inlines.Add(new Run(text)); + AddPlainTextWithCodeSymbols(inlines, text); return; } @@ -293,18 +311,54 @@ public static class MarkdownRenderer { // 매치 전 텍스트 if (pm.Index > lastIndex) - inlines.Add(new Run(text[lastIndex..pm.Index])); + AddPlainTextWithCodeSymbols(inlines, text[lastIndex..pm.Index]); // 파일 경로 — 파란색 강조 inlines.Add(new Run(pm.Value) { - Foreground = new SolidColorBrush(Color.FromRgb(0x3B, 0x82, 0xF6)), // #3B82F6 + Foreground = FilePathBrush, FontWeight = FontWeights.Medium, }); lastIndex = pm.Index + pm.Length; } // 남은 텍스트 + if (lastIndex < text.Length) + AddPlainTextWithCodeSymbols(inlines, text[lastIndex..]); + } + + private static void AddPlainTextWithCodeSymbols(InlineCollection inlines, string text) + { + if (string.IsNullOrEmpty(text)) + return; + + if (!EnableCodeSymbolHighlight) + { + inlines.Add(new Run(text)); + return; + } + + var matches = CodeSymbolPattern.Matches(text); + if (matches.Count == 0) + { + inlines.Add(new Run(text)); + return; + } + + var lastIndex = 0; + foreach (Match match in matches) + { + if (match.Index > lastIndex) + inlines.Add(new Run(text[lastIndex..match.Index])); + + inlines.Add(new Run(match.Value) + { + Foreground = CodeSymbolBrush, + FontWeight = FontWeights.SemiBold, + }); + lastIndex = match.Index + match.Length; + } + if (lastIndex < text.Length) inlines.Add(new Run(text[lastIndex..])); } diff --git a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs index c16fbdd..664855e 100644 --- a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs @@ -78,6 +78,7 @@ public partial class ChatWindow UpdateDataUsageUI(); RefreshContextUsageVisual(); ScheduleGitBranchRefresh(); + UpdateGitBranchUi(_currentGitBranchName, GitBranchFilesText?.Text ?? "", GitBranchAddedText?.Text ?? "", GitBranchDeletedText?.Text ?? "", _currentGitTooltip ?? "", BtnGitBranch?.Visibility ?? Visibility.Collapsed); } private void UpdateDataUsageUI() diff --git a/src/AxCopilot/Views/ChatWindow.GitBranchPresentation.cs b/src/AxCopilot/Views/ChatWindow.GitBranchPresentation.cs index 139d5df..bd0c3a9 100644 --- a/src/AxCopilot/Views/ChatWindow.GitBranchPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.GitBranchPresentation.cs @@ -33,9 +33,76 @@ public partial class ChatWindow GitBranchDeletedText.Text = deletedText; if (GitBranchSeparator != null) GitBranchSeparator.Visibility = visibility; + + UpdateCodeRepoSummaryUi(branchName, filesText, addedText, deletedText, tooltip, visibility); }); } + private void UpdateCodeRepoSummaryUi(string? branchName, string filesText, string addedText, string deletedText, string tooltip, Visibility visibility) + { + if (CodeRepoSummaryBar == null) + return; + + var isVisible = visibility == Visibility.Visible && + string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrWhiteSpace(GetCurrentWorkFolder()); + + CodeRepoSummaryBar.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed; + if (!isVisible) + return; + + var gitRoot = _currentGitRoot ?? ResolveGitRoot(GetCurrentWorkFolder()); + var repoName = !string.IsNullOrWhiteSpace(gitRoot) + ? System.IO.Path.GetFileName(gitRoot.TrimEnd('\\', '/')) + : "저장소"; + var normalizedBranch = string.IsNullOrWhiteSpace(branchName) ? "detached" : branchName!; + + if (CodeRepoSummaryRepoLabel != null) + CodeRepoSummaryRepoLabel.Text = repoName; + if (CodeRepoSummaryBranchLabel != null) + CodeRepoSummaryBranchLabel.Text = normalizedBranch; + if (CodeRepoSummaryDetailText != null) + { + var details = new List(); + if (!string.IsNullOrWhiteSpace(filesText)) + details.Add($"변경 파일 {filesText}"); + if (!string.IsNullOrWhiteSpace(addedText)) + details.Add(addedText); + if (!string.IsNullOrWhiteSpace(deletedText)) + details.Add(deletedText); + + CodeRepoSummaryDetailText.Text = details.Count > 0 + ? string.Join(" · ", details) + : "현재 저장소 컨텍스트와 변경 상태를 기준으로 작업합니다."; + CodeRepoSummaryDetailText.ToolTip = string.IsNullOrWhiteSpace(tooltip) ? null : tooltip; + } + + if (CodeRepoSummaryAddedPill != null && CodeRepoSummaryAddedText != null) + { + var hasAdded = !string.IsNullOrWhiteSpace(addedText); + CodeRepoSummaryAddedPill.Visibility = hasAdded ? Visibility.Visible : Visibility.Collapsed; + CodeRepoSummaryAddedText.Text = hasAdded ? addedText : ""; + } + + if (CodeRepoSummaryDeletedPill != null && CodeRepoSummaryDeletedText != null) + { + var hasDeleted = !string.IsNullOrWhiteSpace(deletedText); + CodeRepoSummaryDeletedPill.Visibility = hasDeleted ? Visibility.Visible : Visibility.Collapsed; + CodeRepoSummaryDeletedText.Text = hasDeleted ? deletedText : ""; + } + } + + private async void CodeRepoSummaryBar_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + e.Handled = true; + if (BtnGitBranch == null) + return; + + await RefreshGitBranchStatusAsync(); + BuildGitBranchPopup(); + GitBranchPopup.IsOpen = true; + } + private void BuildGitBranchPopup() { if (GitBranchItems == null) diff --git a/src/AxCopilot/Views/ChatWindow.MessageBubblePresentation.cs b/src/AxCopilot/Views/ChatWindow.MessageBubblePresentation.cs index 2e03bd8..d1cc7c0 100644 --- a/src/AxCopilot/Views/ChatWindow.MessageBubblePresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.MessageBubblePresentation.cs @@ -51,10 +51,12 @@ public partial class ChatWindow var userCodeBgBrush = TryFindResource("HintBackground") as Brush ?? Brushes.DarkGray; MarkdownRenderer.EnableFilePathHighlight = (System.Windows.Application.Current as App)?.SettingsService?.Settings.Llm.EnableFilePathHighlight ?? true; + MarkdownRenderer.EnableCodeSymbolHighlight = true; bubble.Child = MarkdownRenderer.Render(content, primaryText, secondaryText, accentBrush, userCodeBgBrush); } else { + MarkdownRenderer.EnableCodeSymbolHighlight = false; bubble.Child = new TextBlock { Text = content, @@ -176,6 +178,9 @@ public partial class ChatWindow var app = System.Windows.Application.Current as App; MarkdownRenderer.EnableFilePathHighlight = app?.SettingsService?.Settings.Llm.EnableFilePathHighlight ?? true; + MarkdownRenderer.EnableCodeSymbolHighlight = + string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase) || + string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase); var codeBgBrush = TryFindResource("HintBackground") as Brush ?? Brushes.DarkGray; if (IsBranchContextMessage(content)) { diff --git a/src/AxCopilot/Views/ChatWindow.xaml b/src/AxCopilot/Views/ChatWindow.xaml index bfe27a2..3bb9a81 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml +++ b/src/AxCopilot/Views/ChatWindow.xaml @@ -1699,6 +1699,97 @@ HorizontalAlignment="Center" VerticalAlignment="Bottom"> + + + + + + + + + + + + + + + + + + + + + + + + + + + +