From 80437f05989c522e067d6d3b4ec4edec20721467 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:05:41 +0000 Subject: [PATCH 1/3] Initial plan From e3db988aa0940357712a7ce1ca682a81068ffc26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:16:36 +0000 Subject: [PATCH 2/3] Fix Monaco theme handling for DiffTab and CodeEditor consistency - Changed from defining separate pyxis-dark/pyxis-light themes to a single pyxis-custom theme - pyxis-custom theme now dynamically adapts based on colors (light/dark detection) - Added cache key based on editor-related color properties to avoid unnecessary re-definitions - Theme is only re-defined when relevant color properties change - setTheme('pyxis-custom') is called on every mount to ensure consistency Co-authored-by: Stasshe <133995608+Stasshe@users.noreply.github.com> --- .../Tab/text-editor/editors/monaco-themes.ts | 154 +++++++++--------- 1 file changed, 79 insertions(+), 75 deletions(-) diff --git a/src/components/Tab/text-editor/editors/monaco-themes.ts b/src/components/Tab/text-editor/editors/monaco-themes.ts index 7a9a42e4..68869a01 100644 --- a/src/components/Tab/text-editor/editors/monaco-themes.ts +++ b/src/components/Tab/text-editor/editors/monaco-themes.ts @@ -1,7 +1,9 @@ import type { Monaco } from '@monaco-editor/react'; + import type { ThemeColors } from '@/context/ThemeContext'; -let themesDefined = false; +// キャッシュキー: colorsのエディタ関連プロパティのハッシュ +let lastColorsCacheKey: string | null = null; const isHexLight = (hex?: string) => { if (!hex) return false; @@ -27,15 +29,51 @@ const isHexLight = (hex?: string) => { return false; }; +// colorsからキャッシュキーを生成(エディタ関連プロパティのみ) +function getColorsCacheKey(colors: ThemeColors): string { + return [ + colors.editorBg, + colors.editorFg, + colors.editorLineHighlight, + colors.editorSelection, + colors.editorCursor, + colors.background, + ].join('|'); +} + export function defineAndSetMonacoThemes(mon: Monaco, colors: ThemeColors) { try { - if (!themesDefined) { - // dark - mon.editor.defineTheme('pyxis-dark', { - base: 'vs-dark', + const currentCacheKey = getColorsCacheKey(colors); + const needsRedefine = lastColorsCacheKey !== currentCacheKey; + + if (needsRedefine) { + const bg = colors?.editorBg || (colors as any)?.background || '#1e1e1e'; + const useLight = isHexLight(bg) || (typeof (colors as any).background === 'string' && /white|fff/i.test((colors as any).background)); + + // pyxis-custom テーマを定義(EditorとDiffEditorの両方で使用) + // 現在のcolorsに基づいて適切なbaseを選択し、colorsを反映 + mon.editor.defineTheme('pyxis-custom', { + base: useLight ? 'vs' : 'vs-dark', inherit: true, - rules: [ - { token: 'comment', foreground: '6A9955', fontStyle: 'italic' }, + rules: useLight + ? [ + { token: 'comment', foreground: '6B737A', fontStyle: 'italic' }, + { token: 'keyword', foreground: '0b63c6', fontStyle: 'bold' }, + { token: 'string', foreground: 'a31515' }, + { token: 'number', foreground: '005cc5' }, + { token: 'regexp', foreground: 'b31b1b' }, + { token: 'operator', foreground: '333333' }, + { token: 'delimiter', foreground: '333333' }, + { token: 'type', foreground: '0b7a65' }, + { token: 'parameter', foreground: '1750a0' }, + { token: 'function', foreground: '795e26' }, + { token: 'tag', foreground: '0b7a65', fontStyle: 'bold' }, + { token: 'attribute.name', foreground: '1750a0', fontStyle: 'italic' }, + { token: 'attribute.value', foreground: 'a31515' }, + { token: 'jsx.text', foreground: '2d2d2d' }, + ] + : [ + { token: 'comment', foreground: '6A9955', fontStyle: 'italic' }, { token: 'comment.doc', foreground: '6A9955', fontStyle: 'italic' }, { token: 'keyword', foreground: '569CD6', fontStyle: 'bold' }, { token: 'string', foreground: 'CE9178' }, @@ -50,101 +88,67 @@ export function defineAndSetMonacoThemes(mon: Monaco, colors: ThemeColors) { { token: 'operator', foreground: 'D4D4D4' }, { token: 'delimiter', foreground: 'D4D4D4' }, { token: 'delimiter.bracket', foreground: 'FFD700' }, - - // 型・クラス系 { token: 'type', foreground: '4EC9B0' }, { token: 'type.identifier', foreground: '4EC9B0' }, { token: 'namespace', foreground: '4EC9B0' }, { token: 'struct', foreground: '4EC9B0' }, { token: 'class', foreground: '4EC9B0' }, { token: 'interface', foreground: '4EC9B0' }, - - // 変数・パラメータ系 { token: 'parameter', foreground: '9CDCFE' }, { token: 'variable', foreground: '9CDCFE' }, - { token: 'property', foreground: 'D4D4D4' }, // プロパティは白系に + { token: 'property', foreground: 'D4D4D4' }, { token: 'identifier', foreground: '9CDCFE' }, - - // 関数・メソッド系 { token: 'function', foreground: 'DCDCAA' }, { token: 'function.call', foreground: 'DCDCAA' }, { token: 'method', foreground: 'DCDCAA' }, - - // JSX専用トークン(強調表示) { token: 'tag', foreground: '4EC9B0', fontStyle: 'bold' }, { token: 'tag.jsx', foreground: '4EC9B0', fontStyle: 'bold' }, { token: 'attribute.name', foreground: '9CDCFE', fontStyle: 'italic' }, { token: 'attribute.name.jsx', foreground: '9CDCFE', fontStyle: 'italic' }, { token: 'attribute.value', foreground: 'CE9178' }, - { token: 'jsx.text', foreground: 'D4D4D4' }, // JSX本文テキストは白色 + { token: 'jsx.text', foreground: 'D4D4D4' }, { token: 'delimiter.html', foreground: 'FFD700' }, { token: 'attribute.name.html', foreground: '9CDCFE' }, { token: 'tag.tsx', foreground: '4EC9B0', fontStyle: 'bold' }, - { token: 'tag.jsx', foreground: '4EC9B0', fontStyle: 'bold' }, { token: 'text', foreground: 'D4D4D4' }, - ], - colors: { - 'editor.background': colors.editorBg || '#1e1e1e', - 'editor.foreground': colors.editorFg || '#d4d4d4', - 'editor.lineHighlightBackground': colors.editorLineHighlight || '#2d2d30', - 'editor.selectionBackground': colors.editorSelection || '#264f78', - 'editor.inactiveSelectionBackground': '#3a3d41', - 'editorCursor.foreground': colors.editorCursor || '#aeafad', - 'editorWhitespace.foreground': '#404040', - 'editorIndentGuide.background': '#404040', - 'editorIndentGuide.activeBackground': '#707070', - 'editorBracketMatch.background': '#0064001a', - 'editorBracketMatch.border': '#888888', - }, + ], + colors: useLight + ? { + 'editor.background': colors.editorBg || '#ffffff', + 'editor.foreground': colors.editorFg || '#222222', + 'editor.lineHighlightBackground': colors.editorLineHighlight || '#f0f0f0', + 'editor.selectionBackground': colors.editorSelection || '#cce7ff', + 'editor.inactiveSelectionBackground': '#f3f3f3', + 'editorCursor.foreground': colors.editorCursor || '#0070f3', + 'editorWhitespace.foreground': '#d0d0d0', + 'editorIndentGuide.background': '#e0e0e0', + 'editorIndentGuide.activeBackground': '#c0c0c0', + 'editorBracketMatch.background': '#00000005', + 'editorBracketMatch.border': '#88888822', + } + : { + 'editor.background': colors.editorBg || '#1e1e1e', + 'editor.foreground': colors.editorFg || '#d4d4d4', + 'editor.lineHighlightBackground': colors.editorLineHighlight || '#2d2d30', + 'editor.selectionBackground': colors.editorSelection || '#264f78', + 'editor.inactiveSelectionBackground': '#3a3d41', + 'editorCursor.foreground': colors.editorCursor || '#aeafad', + 'editorWhitespace.foreground': '#404040', + 'editorIndentGuide.background': '#404040', + 'editorIndentGuide.activeBackground': '#707070', + 'editorBracketMatch.background': '#0064001a', + 'editorBracketMatch.border': '#888888', + }, }); - // light - mon.editor.defineTheme('pyxis-light', { - base: 'vs', - inherit: true, - rules: [ - { token: 'comment', foreground: '6B737A', fontStyle: 'italic' }, - { token: 'keyword', foreground: '0b63c6', fontStyle: 'bold' }, - { token: 'string', foreground: 'a31515' }, - { token: 'number', foreground: '005cc5' }, - { token: 'regexp', foreground: 'b31b1b' }, - { token: 'operator', foreground: '333333' }, - { token: 'delimiter', foreground: '333333' }, - { token: 'type', foreground: '0b7a65' }, - { token: 'parameter', foreground: '1750a0' }, - { token: 'function', foreground: '795e26' }, - { token: 'tag', foreground: '0b7a65', fontStyle: 'bold' }, - { token: 'attribute.name', foreground: '1750a0', fontStyle: 'italic' }, - { token: 'attribute.value', foreground: 'a31515' }, - { token: 'jsx.text', foreground: '2d2d2d' }, - ], - colors: { - 'editor.background': colors.editorBg || '#ffffff', - 'editor.foreground': colors.editorFg || '#222222', - 'editor.lineHighlightBackground': colors.editorLineHighlight || '#f0f0f0', - 'editor.selectionBackground': colors.editorSelection || '#cce7ff', - 'editor.inactiveSelectionBackground': '#f3f3f3', - 'editorCursor.foreground': colors.editorCursor || '#0070f3', - 'editorWhitespace.foreground': '#d0d0d0', - 'editorIndentGuide.background': '#e0e0e0', - 'editorIndentGuide.activeBackground': '#c0c0c0', - 'editorBracketMatch.background': '#00000005', - 'editorBracketMatch.border': '#88888822', - }, - }); - - themesDefined = true; + lastColorsCacheKey = currentCacheKey; } - - const bg = colors?.editorBg || (colors as any)?.background || '#1e1e1e'; - const useLight = isHexLight(bg) || (typeof (colors as any).background === 'string' && /white|fff/i.test((colors as any).background)); - const targetTheme = useLight ? 'pyxis-light' : 'pyxis-dark'; - // 常にテーマを設定(新しいエディタインスタンスにも適用されるように) - mon.editor.setTheme(targetTheme); + // テーマを適用(pyxis-custom は常に同じ名前なので、定義が更新されれば自動的に反映される) + mon.editor.setTheme('pyxis-custom'); } catch (e) { // keep MonacoEditor resilient - // eslint-disable-next-line no-console + console.warn('[monaco-themes] Failed to define/set themes:', e); } } From b00bb32db6161cadecf66efcc209e65b99d6400a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:34:05 +0000 Subject: [PATCH 3/3] Simplify Monaco theme logic using themeName instead of color detection - Use LIGHT_THEMES list to determine light/dark theme - Accept themeName parameter instead of computing from colors - Remove getColorsCacheKey function and use themeName for caching - Update all callers to pass themeName from useTheme() Co-authored-by: Stasshe <133995608+Stasshe@users.noreply.github.com> --- src/components/AI/AIReview/AIReviewTab.tsx | 4 +- src/components/Tab/DiffTab.tsx | 4 +- .../Tab/text-editor/editors/MonacoEditor.tsx | 4 +- .../Tab/text-editor/editors/monaco-themes.ts | 55 +++---------------- 4 files changed, 15 insertions(+), 52 deletions(-) diff --git a/src/components/AI/AIReview/AIReviewTab.tsx b/src/components/AI/AIReview/AIReviewTab.tsx index 088aa50a..74a05f8e 100644 --- a/src/components/AI/AIReview/AIReviewTab.tsx +++ b/src/components/AI/AIReview/AIReviewTab.tsx @@ -31,7 +31,7 @@ export default function AIReviewTab({ onUpdateSuggestedContent, onCloseTab, }: AIReviewTabProps) { - const { colors } = useTheme(); + const { colors, themeName } = useTheme(); const { t } = useTranslation(); console.log('[AIReviewTab] Rendering with tab:', tab); @@ -133,7 +133,7 @@ export default function AIReviewTab({ // テーマ定義と適用 try { - defineAndSetMonacoThemes(monaco, colors); + defineAndSetMonacoThemes(monaco, colors, themeName); } catch (e) { console.warn('[AIReviewTab] Failed to define/set themes:', e); } diff --git a/src/components/Tab/DiffTab.tsx b/src/components/Tab/DiffTab.tsx index 649daeeb..75417d6e 100644 --- a/src/components/Tab/DiffTab.tsx +++ b/src/components/Tab/DiffTab.tsx @@ -34,7 +34,7 @@ const DiffTab: React.FC = ({ onContentChange, onImmediateContentChange, }) => { - const { colors } = useTheme(); + const { colors, themeName } = useTheme(); // 各diff領域へのref const diffRefs = useRef<(HTMLDivElement | null)[]>([]); @@ -159,7 +159,7 @@ const DiffTab: React.FC = ({ // テーマ定義と適用 try { - defineAndSetMonacoThemes(monaco, colors); + defineAndSetMonacoThemes(monaco, colors, themeName); } catch (e) { console.warn('[DiffTab] Failed to define/set themes:', e); } diff --git a/src/components/Tab/text-editor/editors/MonacoEditor.tsx b/src/components/Tab/text-editor/editors/MonacoEditor.tsx index 9924838d..79f265eb 100644 --- a/src/components/Tab/text-editor/editors/MonacoEditor.tsx +++ b/src/components/Tab/text-editor/editors/MonacoEditor.tsx @@ -42,7 +42,7 @@ export default function MonacoEditor({ tabSize = 2, insertSpaces = true, }: MonacoEditorProps) { - const { colors } = useTheme(); + const { colors, themeName } = useTheme(); const editorRef = useRef(null); const monacoRef = useRef(null); const [isEditorReady, setIsEditorReady] = useState(false); @@ -83,7 +83,7 @@ export default function MonacoEditor({ // テーマ定義は外部モジュールに移譲 try { - defineAndSetMonacoThemes(mon, colors); + defineAndSetMonacoThemes(mon, colors, themeName); } catch (e) { console.warn('[MonacoEditor] Failed to define/set themes via monaco-themes:', e); } diff --git a/src/components/Tab/text-editor/editors/monaco-themes.ts b/src/components/Tab/text-editor/editors/monaco-themes.ts index 68869a01..bd3c0d8b 100644 --- a/src/components/Tab/text-editor/editors/monaco-themes.ts +++ b/src/components/Tab/text-editor/editors/monaco-themes.ts @@ -2,56 +2,20 @@ import type { Monaco } from '@monaco-editor/react'; import type { ThemeColors } from '@/context/ThemeContext'; -// キャッシュキー: colorsのエディタ関連プロパティのハッシュ -let lastColorsCacheKey: string | null = null; +// ライトテーマのリスト +const LIGHT_THEMES = ['light', 'solarizedLight', 'pastelSoft']; -const isHexLight = (hex?: string) => { - if (!hex) return false; - try { - const h = hex.replace('#', '').trim(); - if (h.length === 3) { - const r = parseInt(h[0] + h[0], 16); - const g = parseInt(h[1] + h[1], 16); - const b = parseInt(h[2] + h[2], 16); - const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - return lum > 0.7; - } - if (h.length === 6) { - const r = parseInt(h.substring(0, 2), 16); - const g = parseInt(h.substring(2, 4), 16); - const b = parseInt(h.substring(4, 6), 16); - const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - return lum > 0.7; - } - } catch (e) { - // ignore - } - return false; -}; - -// colorsからキャッシュキーを生成(エディタ関連プロパティのみ) -function getColorsCacheKey(colors: ThemeColors): string { - return [ - colors.editorBg, - colors.editorFg, - colors.editorLineHighlight, - colors.editorSelection, - colors.editorCursor, - colors.background, - ].join('|'); -} +// 最後に定義したテーマ名をキャッシュ +let lastThemeName: string | null = null; -export function defineAndSetMonacoThemes(mon: Monaco, colors: ThemeColors) { +export function defineAndSetMonacoThemes(mon: Monaco, colors: ThemeColors, themeName: string) { try { - const currentCacheKey = getColorsCacheKey(colors); - const needsRedefine = lastColorsCacheKey !== currentCacheKey; + const needsRedefine = lastThemeName !== themeName; if (needsRedefine) { - const bg = colors?.editorBg || (colors as any)?.background || '#1e1e1e'; - const useLight = isHexLight(bg) || (typeof (colors as any).background === 'string' && /white|fff/i.test((colors as any).background)); + const useLight = LIGHT_THEMES.includes(themeName); // pyxis-custom テーマを定義(EditorとDiffEditorの両方で使用) - // 現在のcolorsに基づいて適切なbaseを選択し、colorsを反映 mon.editor.defineTheme('pyxis-custom', { base: useLight ? 'vs' : 'vs-dark', inherit: true, @@ -141,14 +105,13 @@ export function defineAndSetMonacoThemes(mon: Monaco, colors: ThemeColors) { }, }); - lastColorsCacheKey = currentCacheKey; + lastThemeName = themeName; } - // テーマを適用(pyxis-custom は常に同じ名前なので、定義が更新されれば自動的に反映される) + // テーマを適用 mon.editor.setTheme('pyxis-custom'); } catch (e) { // keep MonacoEditor resilient - console.warn('[monaco-themes] Failed to define/set themes:', e); } }