Greasy Fork is available in English.
Highlights GDScript code in the Gemini web interface using Prism.js.
// ==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);
})();