您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
LeetCode Monaco JS 原生补全增强 (支持 iframe + 缓存回退) + JavaScript 新版本 esnext 全支持
// ==UserScript== // @name LeetCode Monaco JS 原生补全增强 (支持 iframe + 缓存回退) // @namespace https://github.com/Q-Peppa/LeetCode-Code-Completion // @version 2025-09-13 // @description LeetCode Monaco JS 原生补全增强 (支持 iframe + 缓存回退) + JavaScript 新版本 esnext 全支持 // @author Peppa // @license MIT // @match https://leetcode.cn/problems/* // @icon https://www.google.com/s2/favicons?sz=64&domain=leetcode.cn // @grant GM_log // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant unsafeWindow // ==/UserScript== const LIBS = [ "lib.dom.d.ts", "lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.collection.d.ts", "lib.es2023.array.d.ts", "lib.es2024.object.d.ts", "lib.es2024.collection.d.ts", "lib.esnext.collection.d.ts", ]; const fallbacks = { "lib.dom.d.ts": `interface Document { getElementById(id: string): HTMLElement | null; } interface Window { document: Document; }`, "lib.es5.d.ts": `interface Array<T> { forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void; map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[]; }`, "lib.es2015.core.d.ts": `interface Promise<T> { then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>; }`, "lib.es2015.collection.d.ts": `interface Map<K, V> { get(key: K): V | undefined; set(key: K, value: V): this; has(key: K): boolean; delete(key: K): boolean; }`, "lib.esnext.iterator.d.ts": `interface Iterator<T> { next(value?: any): IteratorResult<T>; }`, }; const options = { suggestOnTriggerCharacters: true, quickSuggestions: { strings: true, comments: false, other: true }, acceptSuggestionOnCommitCharacter: true, acceptSuggestionOnEnter: "on", wordBasedSuggestions: "allDocuments", suggest: { showKeywords: true, showMethods: true, showFields: true, showFunctions: true, showModules: true, showVariables: true, filterGraceful: true, preview: true, }, hover: { enabled: true, delay: 300, sticky: true, }, renderErrors: true, scrollBeyondLastLine: false, }; const customLibs = ` declare class PrioriQueue<T>{ private data: T[]; constructor(compare?:(a: T, b: T) => number); enqueue(data: T): void; dequeue(): T | undefined; peek(): T | undefined; size(): number; isEmpty(): boolean; } declare class ListNode { val: number; next: ListNode | null; constructor(val?:number , next: ListNode | null) ; } interface LoDashStatic { map<T, U>(array: T[], callback: (value: T, index: number, array: T[]) => U): U[]; filter<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean): T[]; add(a: number, b: number): number; every<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean): boolean; some<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean): boolean; reduce<T, U>(array: T[], callback: (accumulator: U, value: T, index: number, array: T[]) => U, initialValue: U): U; forEach<T>(array: T[], callback: (value: T, index: number, array: T[]) => void): void; forEachRight<T>(array: T[], callback: (value: T, index: number, array: T[]) => void): void; invert<T>(object: { [index: string]: T }): { [index: string]: string }; omit<T, K extends keyof T>(object: T, ...paths: K[]): { [index: string]: any }; pick<T, K extends keyof T>(object: T, ...paths: K[]): { [index: string]: any }; chunk<T>(array: T[], size?: number): T[][]; compact<T>(array: T[]): T[]; concat<T>(array: T[], ...values: Array<T | T[]>): T[]; difference<T>(array: T[], ...values: T[][]): T[]; differenceBy<T>(array: T[], values: T[], iteratee: (value: T) => any): T[]; differenceWith<T>(array: T[], values: T[], comparator: (a: T, b: T) => boolean): T[]; drop<T>(array: T[], n?: number): T[]; dropRight<T>(array: T[], n?: number): T[]; dropRightWhile<T>(array: T[], predicate: (value: T) => boolean): T[]; dropWhile<T>(array: T[], predicate: (value: T) => boolean): T[]; fill<T>(array: T[], value: any, start?: number, end?: number): T[]; findIndex<T>(array: T[], predicate: (value: T) => boolean, fromIndex?: number): number; findLastIndex<T>(array: T[], predicate: (value: T) => boolean, fromIndex?: number): number; flatten<T>(array: Array<T | T[]>): T[]; flattenDeep<T>(array: any[]): T[]; flattenDepth(array: any[], depth?: number): any[]; fromPairs<T>(pairs: Array<[string, T]>): { [index: string]: T }; head<T>(array: T[]): T | undefined; indexOf<T>(array: T[], value: T, fromIndex?: number): number; initial<T>(array: T[]): T[]; intersection<T>(...arrays: T[][]): T[]; intersectionBy<T>(arrays: T[][], iteratee: (value: T) => any): T[]; intersectionWith<T>(arrays: T[][], comparator: (a: T, b: T) => boolean): T[]; join<T>(array: T[], separator?: string): string; last<T>(array: T[]): T | undefined; lastIndexOf<T>(array: T[], value: T, fromIndex?: number): number; nth<T>(array: T[], n?: number): T | undefined; pull<T>(array: T[], ...values: T[]): T[]; pullAll<T>(array: T[], values: T[]): T[]; pullAllBy<T>(array: T[], values: T[], iteratee: (value: T) => any): T[]; pullAllWith<T>(array: T[], values: T[], comparator: (a: T, b: T) => boolean): T[]; pullAt<T>(array: T[], ...indexes: number[]): T[]; remove<T>(array: T[], predicate: (value: T) => boolean): T[]; reverse<T>(array: T[]): T[]; slice<T>(array: T[], start?: number, end?: number): T[]; sortedIndex<T>(array: T[], value: T): number; sortedIndexBy<T>(array: T[], value: T, iteratee: (value: T) => any): number; sortedIndexOf<T>(array: T[], value: T): number; sortedLastIndex<T>(array: T[], value: T): number; sortedLastIndexBy<T>(array: T[], value: T, iteratee: (value: T) => any): number; sortedLastIndexOf<T>(array: T[], value: T): number; sortedUniq<T>(array: T[]): T[]; sortedUniqBy<T>(array: T[], iteratee: (value: T) => any): T[]; tail<T>(array: T[]): T[]; take<T>(array: T[], n?: number): T[]; takeRight<T>(array: T[], n?: number): T[]; takeRightWhile<T>(array: T[], predicate: (value: T) => boolean): T[]; takeWhile<T>(array: T[], predicate: (value: T) => boolean): T[]; union<T>(...arrays: T[][]): T[]; unionBy<T>(arrays: T[][], iteratee: (value: T) => any): T[]; unionWith<T>(arrays: T[][], comparator: (a: T, b: T) => boolean): T[]; uniq<T>(array: T[]): T[]; uniqBy<T>(array: T[], iteratee: (value: T) => any): T[]; uniqWith<T>(array: T[], comparator: (a: T, b: T) => boolean): T[]; unzip<T>(array: T[][]): T[][]; unzipWith<T, R>(array: T[][], iteratee: (...values: T[]) => R): R[]; without<T>(array: T[], ...values: T[]): T[]; xor<T>(...arrays: T[][]): T[]; xorBy<T>(arrays: T[][], iteratee: (value: T) => any): T[]; xorWith<T>(arrays: T[][], comparator: (a: T, b: T) => boolean): T[]; zip<T>(...arrays: T[][]): T[][]; zipObject(props: string[], values: any[]): { [index: string]: any }; zipObjectDeep(props: string[], values: any[]): { [index: string]: any }; zipWith<T, R>(arrays: T[][], iteratee: (...values: T[]) => R): R[]; } declare const _: LoDashStatic; `; var globalMonaco = null; (function () { "use strict"; const TSC_VERSION = "5.9.2"; const CACHE_KEY_PREFIX = "LeetCode_monaco_V2"; const FETCH_TIMEOUT = 5000; const U = `https://cdn.jsdelivr.net/npm/typescript@${TSC_VERSION}/lib/`; async function getLibContent(libName = "") { // 先检查缓存 const cache = GM_getValue(CACHE_KEY_PREFIX + libName); if (cache) { GM_log(`[Cache] 📦 命中缓存 ${libName}`); return cache; } // 使用 GM_download 替代 fetch return new Promise((resolve) => { GM_log(`[Download] 🔍 下载 ${libName}`); GM_xmlhttpRequest({ method: "GET", url: U + libName, onload(data) { const text = data.responseText; try { GM_setValue(CACHE_KEY_PREFIX + libName, text); GM_log(`📤 缓存成功了 ${libName}`); resolve(text); } catch (e) { GM_log(`⚠️ 缓存失败了 ${libName}: ${e.message}`); resolve(text); // 即使缓存失败,也返回内容 } }, onerror(error) { GM_log(`❌ 下载失败 ${libName}: ${error.message}`); // 返回回退内容 const fallback = fallbacks[libName] ?? ""; GM_log(`[Fallback] 📥 使用本地定义的模型: ${libName}`); resolve(fallback); }, timeout: FETCH_TIMEOUT, ontimeout: () => { GM_log(`❌ 获取 ${libName} 超时`); // 获取回退内容 const fallback = fallbacks[libName] ?? ""; GM_log(`[Fallback] 📥 使用本地定义的模型: ${libName}`); resolve(fallback); }, }); }); } function waitForMonaco(target = unsafeWindow) { return new Promise((resolve) => { const check = () => { if ( target.monaco && target.monaco.languages?.typescript && target.monaco.editor && target.monaco.editor.createModel ) { resolve(target.monaco); return true; } return false; }; // Check immediately if (check()) return; const interval = setInterval(() => { if (check()) { clearInterval(interval); } }, 200); // Extend timeout to 10 seconds setTimeout(() => { clearInterval(interval); resolve(null); }, 10000); }); } async function enableJSCompletion(target = unsafeWindow) { try { GM_log("🔍 等待Monaco编辑器加载..."); globalMonaco = await waitForMonaco(target); if (!globalMonaco) { GM_log("❌ 失败:未找到Monaco编辑器"); return; } } catch (e) { GM_log("❌ 初始化出错:" + e.message); return; } GM_log("🔧 开始配置语言服务", globalMonaco); const jsDefaults = globalMonaco.languages.typescript.javascriptDefaults; // 添加自定义类型(这是基础类型,先加载) jsDefaults.addExtraLib(customLibs.trim(), "ts:custom-types.d.ts"); jsDefaults.setDiagnosticsOptions({ noSuggestionDiagnostics: false, noSyntaxValidation: false, noSemanticValidation: false, }); jsDefaults.setCompilerOptions({ allowJs: true, allowNonTsExtensions: true, target: 99, checkJs: true, strict: true, noImplicitAny: true, noEmit: true, }); // 按顺序加载类型定义,确保依赖正确 for (const libName of LIBS) { try { const content = await getLibContent(libName); if (content) { const uri = `ts:${libName}`; jsDefaults.addExtraLib(content, uri); GM_log(`✅ 加载完成: ${libName}`); } else { GM_log(`❌ 无法获取: ${libName}`); } } catch (e) { GM_log(`❌ 处理 ${libName} 时出错: ${e.message}`); } } // 更新现有编辑器选项 globalMonaco.editor.getEditors?.().forEach((editor) => { editor.updateOptions({ ...options }); }); // 为新创建的编辑器设置选项 globalMonaco.editor.onDidCreateEditor((editor) => { editor.updateOptions({ ...options }); }); // 最后启用模型同步 jsDefaults.setEagerModelSync(true); // 强制刷新语言服务 globalMonaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions( globalMonaco.languages.typescript.javascriptDefaults.getDiagnosticsOptions() ); GM_log( "✅【LeetCode 补全代码】 全部的功能已经就绪、试试输入 const ans = [], ans. 有没有补全 ~" ); } function detectMonacoInPage() { // 检查主页面 if (document?.querySelector(".monaco-editor") && !globalMonaco) { GM_log("发现主页面含有 Monaco!"); enableJSCompletion(unsafeWindow); return true; } // 检查所有iframe const iframes = document.querySelectorAll("iframe"); for (const iframe of iframes) { try { const doc = iframe.contentDocument; if (doc && doc.querySelector(".monaco-editor")) { GM_log("发现 IFrame 页面含有 Monaco!"); enableJSCompletion(iframe.contentWindow); return true; } } catch (e) { // 跨域访问会抛出异常,忽略 GM_log("检查iframe时出错(可能是跨域):" + e.message); } } return false; } GM_log("🚀 【LeetCode 补全代码】 插件准备加载了~"); const observer = new MutationObserver(() => { if (detectMonacoInPage()) { observer.disconnect(); } }); observer.observe(document, { childList: true, subtree: true }); // 多次检查机制,提高成功率 const checkTimes = [500, 1000, 2000, 3000]; checkTimes.forEach((delay) => { setTimeout(detectMonacoInPage, delay); }); })();