Gemini GDScript Syntax Highlighter

Highlights GDScript code in the Gemini web interface using Prism.js.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Gemini GDScript Syntax Highlighter
// @name:zh-CN   Gemini GDScript 代码高亮
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Highlights GDScript code in the Gemini web interface using Prism.js.
// @description:zh-CN 为 Gemini 网页版的 GDScript 代码片段提供语法高亮,基于 Prism.js。
// @author       Anonymous
// @match        https://gemini.google.com/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-gdscript.min.js
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    if (typeof Prism === 'undefined') {
        console.error('❌ [GDScript Highlighter] 核心库 Prism.js 未加载。');
        return;
    }

    // 1. 创建 TrustedHTML 策略
    let ttPolicy;
    if (window.trustedTypes && window.trustedTypes.createPolicy) {
        try {
            const policyName = 'gdscript-policy-' + Math.random().toString(36).substring(2, 9);
            ttPolicy = window.trustedTypes.createPolicy(policyName, {
                createHTML: (string) => string
            });
        } catch (e) {
            console.error('❌ [GDScript Highlighter] TrustedTypes 策略创建失败:', e);
        }
    }

    // 2. 实时检测 Gemini 网页的实际主题模式
    function updateThemeMode() {
        const textColor = window.getComputedStyle(document.body).color;
        const match = textColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
        let isDarkMode = true;
        if (match) {
            const luma = 0.2126 * parseInt(match[1], 10) + 0.7152 * parseInt(match[2], 10) + 0.0722 * parseInt(match[3], 10);
            isDarkMode = luma > 128;
        }
        document.documentElement.setAttribute('data-gds-theme', isDarkMode ? 'dark' : 'light');
    }

    const themeObserver = new MutationObserver((mutations) => {
        for (let m of mutations) {
            if (m.type === 'attributes' && (m.attributeName === 'class' || m.attributeName === 'style')) {
                updateThemeMode();
                break;
            }
        }
    });
    themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style', 'data-theme'] });
    themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class', 'style'] });
    updateThemeMode();

    // 3. 注入分离的深浅色高亮样式
    GM_addStyle(`
        code.gdscript-injected {
            background: transparent !important;
            text-shadow: none !important;
            font-family: inherit !important;
            tab-size: 4 !important;
        }

        /* ====== 浅色主题 ====== */
        html[data-gds-theme="light"] code.gdscript-injected { color: #24292e !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.comment { color: #6a737d !important; font-style: italic !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.punctuation { color: #24292e !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.keyword { color: #d73a49 !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.function { color: #6f42c1 !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.string { color: #032f62 !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.number { color: #005cc5 !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.class-name,
        html[data-gds-theme="light"] code.gdscript-injected .token.builtin { color: #e36209 !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.operator { color: #d73a49 !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.boolean,
        html[data-gds-theme="light"] code.gdscript-injected .token.property,
        html[data-gds-theme="light"] code.gdscript-injected .token.constant { color: #005cc5 !important; }
        html[data-gds-theme="light"] code.gdscript-injected .token.variable { color: #e36209 !important; }

        /* ====== 深色主题 ====== */
        html[data-gds-theme="dark"] code.gdscript-injected { color: #d4d4d4 !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.comment { color: #6a9955 !important; font-style: italic !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.punctuation { color: #d4d4d4 !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.keyword { color: #569cd6 !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.function { color: #dcdcaa !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.string { color: #ce9178 !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.number { color: #b5cea8 !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.class-name,
        html[data-gds-theme="dark"] code.gdscript-injected .token.builtin { color: #4ec9b0 !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.operator { color: #d4d4d4 !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.boolean,
        html[data-gds-theme="dark"] code.gdscript-injected .token.property,
        html[data-gds-theme="dark"] code.gdscript-injected .token.variable { color: #9cdcfe !important; }
        html[data-gds-theme="dark"] code.gdscript-injected .token.constant { color: #4fc1ff !important; }
    `);

    // 4. 执行 GDScript 识别与渲染
    function highlightGDScript() {
        // 去除之前强制的 :not(.gdscript-injected) 过滤,因为我们在内部用 dataset 判断流式输出状态
        const codeBlocks = document.querySelectorAll('code');

        codeBlocks.forEach((block) => {
            // ================= 关键修复 =================
            // 过滤掉聊天文本中的"行内代码" (Inline code)。多行代码块必定在 <pre> 内。
            const preNode = block.closest('pre');
            if (!preNode) return;
            // ==========================================

            let isGDScript = false;
            const className = block.className.toLowerCase();

            // 1. 直接通过类名判断
            if (className.includes('gdscript') || className.includes('language-gd')) {
                isGDScript = true;
            }

            // 2. 精确查找头部工具栏进行判断
            if (!isGDScript) {
                let node = preNode; // 从 pre 标签开始向上找,而不是直接用 parent.textContent
                let depth = 0;

                while (node && depth < 4) {
                    if (node.previousElementSibling) {
                        const siblingText = node.previousElementSibling.textContent.toLowerCase().trim();
                        // 语言栏通常很短,这里限制长度防止误读整段非代码文本
                        if (siblingText.length < 50 && siblingText.includes('gdscript')) {
                            isGDScript = true;
                            break;
                        }
                    }
                    node = node.parentElement;
                    depth++;
                }
            }

            if (isGDScript) {
                const rawCode = block.textContent;

                // 防止在流式输出时发生死循环或重置用户选择状态
                if (block.dataset.rawText === rawCode) return;
                block.dataset.rawText = rawCode;

                block.className = 'gdscript-injected language-gdscript';
                const highlightedHTML = Prism.highlight(rawCode, Prism.languages.gdscript, 'gdscript');

                if (ttPolicy) {
                    block.innerHTML = ttPolicy.createHTML(highlightedHTML);
                } else {
                    block.innerHTML = highlightedHTML;
                }
            }
        });
    }

    // 5. 监听页面 DOM 变化,适配流式输出
    let debounceTimer = null;
    const observer = new MutationObserver(() => {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(highlightGDScript, 300);
    });

    observer.observe(document.body, { childList: true, subtree: true, characterData: true });

    setTimeout(highlightGDScript, 500);

})();