// ==UserScript== // @name LeetCode|力扣 智能C++语法智能提示 (IDE级增强版) // @namespace https://tampermonkey.net/ // @version 6.1.1 // @description 为 LeetCode 提供增强版的 C++ 自动补全:支持 STL 容器成员函数、LeetCode 内置结构体(TreeNode/ListNode)、常用算法模板(DFS/BFS/二分)、以及变量类型追踪。纯前端实现,伟大,无需多言。 // @author bigonion & Gemini // @match https://leetcode.cn/problems/* // @grant unsafeWindow // @license MIT // ==/UserScript== (function () { 'use strict'; // --- 配置:常用 STL 和 LeetCode 结构体的成员函数库 --- const MEMBER_METHODS = { 'vector': ['push_back', 'pop_back', 'size', 'empty', 'clear', 'resize', 'reserve', 'begin', 'end', 'back', 'front', 'insert', 'erase'], 'string': ['length', 'size', 'substr', 'find', 'push_back', 'pop_back', 'empty', 'c_str', 'append', 'replace'], 'stack': ['push', 'pop', 'top', 'empty', 'size'], 'queue': ['push', 'pop', 'front', 'back', 'empty', 'size'], 'deque': ['push_back', 'push_front', 'pop_back', 'pop_front', 'front', 'back', 'size', 'empty'], 'priority_queue': ['push', 'pop', 'top', 'empty', 'size'], 'set': ['insert', 'erase', 'find', 'count', 'begin', 'end', 'size', 'empty', 'lower_bound', 'upper_bound'], 'unordered_set': ['insert', 'erase', 'find', 'count', 'begin', 'end', 'size', 'empty'], 'map': ['insert', 'erase', 'find', 'count', 'begin', 'end', 'size', 'empty', 'lower_bound', 'upper_bound'], 'unordered_map': ['insert', 'erase', 'find', 'count', 'begin', 'end', 'size', 'empty'], 'pair': ['first', 'second'], // LeetCode Specific 'TreeNode': ['val', 'left', 'right'], 'ListNode': ['val', 'next'] }; // --- 1. STATIC DICTIONARY (全局关键词 + 算法模板) --- const globalKeywords = [ // Keywords { label: 'int', type: 'keyword' }, { label: 'long long', type: 'keyword' }, { label: 'double', type: 'keyword' }, { label: 'bool', type: 'keyword' }, { label: 'void', type: 'keyword' }, { label: 'auto', type: 'keyword' }, { label: 'const', type: 'keyword' }, { label: 'static', type: 'keyword' }, { label: 'return', type: 'keyword' }, { label: 'continue', type: 'keyword' }, { label: 'break', type: 'keyword' }, { label: 'struct', type: 'keyword' }, { label: 'class', type: 'keyword' }, { label: 'nullptr', type: 'keyword' }, { label: 'new', type: 'keyword' }, { label: 'delete', type: 'keyword' }, // Control Flow Snippets { label: 'for', type: 'snippet', snippet: 'for (int ${1:i} = 0; ${1:i} < ${2:n}; ++${1:i}) {\n\t$0\n}', hover: 'For Loop' }, { label: 'while', type: 'snippet', snippet: 'while (${1:cond}) {\n\t$0\n}', hover: 'While Loop' }, { label: 'if', type: 'snippet', snippet: 'if (${1:cond}) {\n\t$0\n}', hover: 'If Statement' }, { label: 'else', type: 'snippet', snippet: 'else {\n\t$0\n}', hover: 'Else Statement' }, // Algorithm Snippets (核心增强) { label: 'dfs', type: 'snippet', snippet: 'function dfs = [&](int u) {\n\tif (u == ${1:target}) return;\n\tvisited[u] = true;\n\tfor (auto& v : adj[u]) {\n\t\tif (!visited[v]) dfs(v);\n\t}\n};', hover: 'DFS Template (Lambda)' }, { label: 'bfs', type: 'snippet', snippet: 'queue q;\nq.push(${1:start});\nvisited[${1:start}] = true;\nwhile (!q.empty()) {\n\tint u = q.front(); q.pop();\n\t$0\n}', hover: 'BFS Template' }, { label: 'binary_search', type: 'snippet', snippet: 'int l = 0, r = ${1:n} - 1;\nwhile (l <= r) {\n\tint mid = l + (r - l) / 2;\n\tif (check(mid)) l = mid + 1;\n\telse r = mid - 1;\n}', hover: 'Binary Search Template' }, { label: 'union_find', type: 'snippet', snippet: 'struct DSU {\n\tvector p;\n\tDSU(int n) : p(n) { iota(p.begin(), p.end(), 0); }\n\tint find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }\n\tvoid unite(int x, int y) { p[find(x)] = find(y); }\n};', hover: 'Disjoint Set Union Class' }, // Common Constants & Macros { label: 'INT_MAX', type: 'constant' }, { label: 'INT_MIN', type: 'constant' }, { label: 'LLONG_MAX', type: 'constant' }, { label: 'LLONG_MIN', type: 'constant' }, { label: 'NULL', type: 'constant' }, // Standard Functions { label: 'sort', type: 'function', snippet: 'sort(${1:begin}, ${2:end});' }, { label: 'reverse', type: 'function', snippet: 'reverse(${1:begin}, ${2:end});' }, { label: 'max', type: 'function' }, { label: 'min', type: 'function' }, { label: 'abs', type: 'function' }, { label: 'pow', type: 'function' }, { label: 'memset', type: 'function', snippet: 'memset(${1:arr}, 0, sizeof(${1:arr}));' }, { label: 'accumulate', type: 'function', snippet: 'accumulate(${1:begin}, ${2:end}, 0LL)' } ]; // --- 2. 工具函数 --- const wait = (ms) => new Promise(r => setTimeout(r, ms)); const waitForMonaco = async () => { while (!unsafeWindow.monaco || !unsafeWindow.monaco.editor || !unsafeWindow.monaco.editor.getModels().length) { await wait(200); } return unsafeWindow.monaco; }; // --- 3. 主逻辑 --- waitForMonaco().then(init); function init(monaco) { const model = monaco.editor.getModels()[0]; // 强制识别为CPP if (model.getLanguageId() !== 'cpp') monaco.editor.setModelLanguage(model, 'cpp'); // --- 状态管理 --- let varTable = new Map(); // name -> { type, normalizedType, line } // 预设 LeetCode 常见变量 (兜底用,虽然现在的解析器应该能抓到) varTable.set('root', { type: 'TreeNode*', normalizedType: 'TreeNode', line: 0 }); varTable.set('head', { type: 'ListNode*', normalizedType: 'ListNode', line: 0 }); // --- 4. 解析器 (Parser) [已再次增强] --- // [关键修改] // 1. Group 1 (Type): 非贪婪匹配,尽可能少吃字符。 // 2. Separator: (?:[\s*&]+) <-- 这里是核心。 // 它表示:类型和变量名之间,只要有“空格”、“*”或“&”的任意组合即可。 // 这样 "ListNode *headA" 中的 " *" 就会被当成分隔符,而被正确识别。 const VAR_RE = /(?:const\s+|static\s+)?(?:unsigned\s+)?([a-zA-Z0-9_<>:*\s&]+?)(?:[\s*&]+)([a-zA-Z_]\w*)(?:\s*[=;,)])/g; // 关键字黑名单 const KEYWORD_BLOCKLIST = new Set([ 'return', 'class', 'struct', 'if', 'else', 'while', 'for', 'switch', 'case', 'public', 'private', 'protected', 'new', 'delete' ]); // 归一化类型 function normalizeType(rawType) { let t = rawType.trim(); t = t.replace(/[&*]+/g, '').trim(); // 移除可能残留的指针符 const templateMatch = /^([a-zA-Z0-9_]+)\s*<.*>$/.exec(t); if (templateMatch) return templateMatch[1]; return t; } const buildIndex = () => { const code = model.getValue(); const lines = code.split('\n'); lines.forEach((line, i) => { VAR_RE.lastIndex = 0; let m; while ((m = VAR_RE.exec(line)) !== null) { const rawType = m[1]; // 例如 "ListNode" const name = m[2]; // 例如 "headA" if (KEYWORD_BLOCKLIST.has(name)) continue; // 排除函数定义的误判 (如果后面紧跟 '(' ) const codeAfter = line.substring(m.index + m[0].length); if (codeAfter.trim().startsWith('(')) continue; varTable.set(name, { type: rawType.trim(), normalizedType: normalizeType(rawType), line: i + 1 }); } }); }; // 初始构建 + 监听变化 (防抖) buildIndex(); let timeout; model.onDidChangeContent(() => { clearTimeout(timeout); timeout = setTimeout(buildIndex, 800); // 稍微延长一点防抖时间,减少性能开销 }); // --- 5. Completion Provider (核心) --- monaco.languages.registerCompletionItemProvider('cpp', { triggerCharacters: ['.', '>', ':'], provideCompletionItems: (model, position) => { const textUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column }); const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn }; // check 1: 成员访问 const memberAccessMatch = textUntilPosition.trim().match(/([a-zA-Z_]\w*)\s*(\.|->)$/); if (memberAccessMatch) { const varName = memberAccessMatch[1]; // const op = memberAccessMatch[2]; const varInfo = varTable.get(varName); if (varInfo) { const type = varInfo.normalizedType; const methods = MEMBER_METHODS[type]; if (methods) { return { suggestions: methods.map(m => ({ label: m, kind: monaco.languages.CompletionItemKind.Method, insertText: m, sortText: '0_' + m, range: range })) }; } } return { suggestions: [] }; } // check 2: 全局建议 (排除 . 或 ->) if (textUntilPosition.trim().endsWith('.') || textUntilPosition.trim().endsWith('>')) { return { suggestions: [] }; } const suggestions = []; const seen = new Set(); // A. 用户变量 varTable.forEach((info, name) => { suggestions.push({ label: name, kind: monaco.languages.CompletionItemKind.Variable, detail: info.type, insertText: name, sortText: '1_' + name, range: range }); seen.add(name); }); // B. 静态字典 globalKeywords.forEach(item => { if (!seen.has(item.label)) { suggestions.push({ label: item.label, kind: item.type === 'snippet' ? monaco.languages.CompletionItemKind.Snippet : item.type === 'function' ? monaco.languages.CompletionItemKind.Function : monaco.languages.CompletionItemKind.Keyword, insertText: item.snippet || item.label, insertTextRules: item.snippet ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet : undefined, detail: item.hover || '', sortText: '2_' + item.label, range: range }); } }); // C. STL 容器类型 Object.keys(MEMBER_METHODS).forEach(cls => { if(!seen.has(cls)) { suggestions.push({ label: cls, kind: monaco.languages.CompletionItemKind.Class, insertText: cls, sortText: '3_' + cls, range: range }); } }); return { suggestions: suggestions }; } }); // --- 6. Hover Provider (悬浮提示) --- monaco.languages.registerHoverProvider('cpp', { provideHover: (model, position) => { const word = model.getWordAtPosition(position); if (!word) return; const v = varTable.get(word.word); if (v) { return { range: new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), contents: [ { value: `**variable** \`${word.word}\`` }, { value: 'Type: `' + v.type + '`' } ] }; } const k = globalKeywords.find(x => x.label === word.word); if (k && k.hover) { return { range: new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), contents: [{ value: k.hover }] }; } } }); console.log('%c [LeetCode C++ Helper] Loaded (Fixed)! ', 'background: #222; color: #bada55; font-size:14px'); } })();