Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/AI/AIReview/AIReviewTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Tab/DiffTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const DiffTab: React.FC<DiffTabProps> = ({
onContentChange,
onImmediateContentChange,
}) => {
const { colors } = useTheme();
const { colors, themeName } = useTheme();
// 各diff領域へのref
const diffRefs = useRef<(HTMLDivElement | null)[]>([]);

Expand Down Expand Up @@ -159,7 +159,7 @@ const DiffTab: React.FC<DiffTabProps> = ({

// テーマ定義と適用
try {
defineAndSetMonacoThemes(monaco, colors);
defineAndSetMonacoThemes(monaco, colors, themeName);
} catch (e) {
console.warn('[DiffTab] Failed to define/set themes:', e);
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Tab/text-editor/editors/MonacoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function MonacoEditor({
tabSize = 2,
insertSpaces = true,
}: MonacoEditorProps) {
const { colors } = useTheme();
const { colors, themeName } = useTheme();
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
const monacoRef = useRef<Monaco | null>(null);
const [isEditorReady, setIsEditorReady] = useState(false);
Expand Down Expand Up @@ -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);
}
Expand Down
165 changes: 66 additions & 99 deletions src/components/Tab/text-editor/editors/monaco-themes.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
import type { Monaco } from '@monaco-editor/react';

import type { ThemeColors } from '@/context/ThemeContext';

let themesDefined = false;
// ライトテーマのリスト
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;
};
// 最後に定義したテーマ名をキャッシュ
let lastThemeName: string | null = null;

export function defineAndSetMonacoThemes(mon: Monaco, colors: ThemeColors) {
export function defineAndSetMonacoThemes(mon: Monaco, colors: ThemeColors, themeName: string) {
try {
if (!themesDefined) {
// dark
mon.editor.defineTheme('pyxis-dark', {
base: 'vs-dark',
const needsRedefine = lastThemeName !== themeName;

if (needsRedefine) {
const useLight = LIGHT_THEMES.includes(themeName);

// pyxis-custom テーマを定義(EditorとDiffEditorの両方で使用)
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' },
Expand All @@ -50,101 +52,66 @@ 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',
},
});

// 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',
},
],
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',
},
});

themesDefined = true;
lastThemeName = themeName;
}

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);
// テーマを適用
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);
}
}