|
| 1 | +import CodeMirror from 'codemirror'; |
| 2 | +import parseCode from './parseCode'; |
| 3 | + |
| 4 | +const scopeMap = require('./finalScopeMap.json'); |
| 5 | + |
| 6 | +export default function contextAwareHinter(cm, options = {}) { |
| 7 | + const { hinter } = options; |
| 8 | + if (!hinter || typeof hinter.search !== 'function') { |
| 9 | + console.warn('Hinter is not available or invalid.'); |
| 10 | + return { list: [], from: cm.getCursor(), to: cm.getCursor() }; |
| 11 | + } |
| 12 | + |
| 13 | + const { line, ch } = cm.getCursor(); // getCursor has line, ch, sticky |
| 14 | + console.log('cm.getcursor ', cm.getCursor()); |
| 15 | + const { start, end, string } = cm.getTokenAt({ line, ch }); |
| 16 | + // console.log('cm.gettokenat', cm.getTokenAt()); |
| 17 | + const currentWord = string.trim(); |
| 18 | + console.log('currentwork ', currentWord); |
| 19 | + |
| 20 | + const context = parseCode(cm); // e.g. 'draw' |
| 21 | + const allHints = hinter.search(currentWord); // <- from options, not cm.hinter |
| 22 | + const whitelist = scopeMap[context]?.whitelist || []; |
| 23 | + const blacklist = scopeMap[context]?.blacklist || []; |
| 24 | + console.log('allhints: ', allHints); |
| 25 | + |
| 26 | + // for each hint, only keep ones that match the typed prefix |
| 27 | + const filteredHints = allHints |
| 28 | + .filter( |
| 29 | + (h) => |
| 30 | + h && |
| 31 | + h.item && |
| 32 | + typeof h.item.text === 'string' && |
| 33 | + h.item.text.startsWith(currentWord) |
| 34 | + ) |
| 35 | + .map((h) => { |
| 36 | + const name = h.item.text; |
| 37 | + const isWhitelisted = whitelist.includes(name); |
| 38 | + const isBlacklisted = blacklist.includes(name); |
| 39 | + |
| 40 | + const from = CodeMirror.Pos(line, start); |
| 41 | + const to = CodeMirror.Pos(line, end); |
| 42 | + |
| 43 | + let className = ''; |
| 44 | + if (isBlacklisted) { |
| 45 | + className = 'blacklisted-hint'; |
| 46 | + } else if (isWhitelisted) { |
| 47 | + className = 'whitelisted-hint'; |
| 48 | + } |
| 49 | + |
| 50 | + return { |
| 51 | + text: name, // Ensure `text` is explicitly defined |
| 52 | + displayText: h.item.displayText || name, |
| 53 | + className, |
| 54 | + from, |
| 55 | + to, |
| 56 | + render: (el, self, data) => { |
| 57 | + el.innerText = data.text; |
| 58 | + if (isBlacklisted) { |
| 59 | + el.style.color = 'red'; |
| 60 | + el.style.opacity = 0.6; |
| 61 | + } else if (isWhitelisted) { |
| 62 | + el.style.fontWeight = 'bold'; |
| 63 | + } |
| 64 | + }, |
| 65 | + hint: (editor, data, completion) => { |
| 66 | + const { from: fromPos, to: toPos } = completion; |
| 67 | + |
| 68 | + if (!completion.text || typeof completion.text !== 'string') { |
| 69 | + console.error('Invalid completion.text:', completion); |
| 70 | + return; |
| 71 | + } |
| 72 | + |
| 73 | + editor.replaceRange(completion.text, fromPos, toPos); |
| 74 | + |
| 75 | + if (blacklist.includes(completion.text)) { |
| 76 | + console.warn( |
| 77 | + `Using "${completion.text}" inside "${context}" is not recommended.` |
| 78 | + ); |
| 79 | + } |
| 80 | + } |
| 81 | + }; |
| 82 | + }); |
| 83 | + |
| 84 | + console.log('filtered hints: ', filteredHints); |
| 85 | + |
| 86 | + const sorted = filteredHints.sort((a, b) => { |
| 87 | + const score = (name) => { |
| 88 | + if (whitelist.includes(name)) return 0; |
| 89 | + if (blacklist.includes(name)) return 2; |
| 90 | + return 1; |
| 91 | + }; |
| 92 | + return score(a.text) - score(b.text) || a.text.localeCompare(b.text); |
| 93 | + }); |
| 94 | + |
| 95 | + return { |
| 96 | + list: sorted, |
| 97 | + from: CodeMirror.Pos(line, start), |
| 98 | + to: CodeMirror.Pos(line, end) |
| 99 | + }; |
| 100 | +} |
0 commit comments