From bdea15d213361bed8c4030e36257bb7d8d9b7450 Mon Sep 17 00:00:00 2001 From: klymp <14829626+kylmp@users.noreply.github.com> Date: Sun, 8 Feb 2026 22:12:26 +0000 Subject: [PATCH] feat: use runescript-lsp --- .gitignore | 1 + .vscode/launch.json | 38 ++++ client/cache/activeCursorCache.js | 24 --- client/cache/activeFileCache.js | 120 ----------- client/cache/cacheManager.js | 197 ------------------ client/cache/class/LineReferenceCache.js | 57 ----- client/cache/class/Trie.js | 89 -------- client/cache/completionCache.js | 55 ----- client/cache/identifierCache.js | 136 ------------ client/cache/returnBlockLinesCache.js | 11 - client/cache/switchStmtLinesCache.js | 10 - client/enum/hoverConfigOptions.js | 9 - client/enum/hoverDisplayItems.js | 14 -- client/enum/regex.js | 22 -- client/info/configKeyInfo.js | 47 ----- client/info/triggerInfo.js | 18 -- client/matching/matchType.js | 196 ----------------- client/matching/matchWord.js | 131 ------------ client/matching/matchers/commandMatcher.js | 22 -- client/matching/matchers/configMatcher.js | 147 ------------- client/matching/matchers/localVarMatcher.js | 23 -- client/matching/matchers/packMatcher.js | 25 --- client/matching/matchers/parametersMatcher.js | 155 -------------- client/matching/matchers/prevCharMatcher.js | 17 -- client/matching/matchers/regexWordMatcher.js | 21 -- client/matching/matchers/switchCaseMatcher.js | 17 -- client/matching/matchers/triggerMatcher.js | 27 --- client/provider/color24Provider.js | 41 ---- client/provider/completionProvider.js | 132 ------------ client/provider/configHelpProvider.js | 56 ----- client/provider/gotoDefinition.js | 39 ---- client/provider/hoverProvider.js | 80 ------- client/provider/recolorProvider.js | 41 ---- client/provider/referenceProvider.js | 46 ---- client/provider/renameProvider.js | 111 ---------- client/provider/signatureHelpProvider.js | 134 ------------ client/provider/vscodeCommands.js | 17 -- client/resource/configKeys.js | 44 ---- client/resource/dataTypeToMatchId.js | 15 -- client/resource/hoverConfigResolver.js | 22 -- client/resource/identifierFactory.js | 135 ------------ client/resource/postProcessors.js | 80 ------- client/resource/triggers.js | 61 ------ client/runescript-language.js | 61 ------ client/utils/cacheUtils.js | 29 --- client/utils/markdownUtils.js | 66 ------ client/utils/matchUtils.js | 54 ----- client/utils/stringUtils.js | 49 ----- package-lock.json | 108 +++++++++- package.json | 31 ++- src/clientState.js | 11 + src/commands.js | 21 ++ src/devModeHighlights.js | 39 ++++ src/events.js | 28 +++ src/runescript-extension.js | 75 +++++++ 55 files changed, 347 insertions(+), 2908 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 client/cache/activeCursorCache.js delete mode 100644 client/cache/activeFileCache.js delete mode 100644 client/cache/cacheManager.js delete mode 100644 client/cache/class/LineReferenceCache.js delete mode 100644 client/cache/class/Trie.js delete mode 100644 client/cache/completionCache.js delete mode 100644 client/cache/identifierCache.js delete mode 100644 client/cache/returnBlockLinesCache.js delete mode 100644 client/cache/switchStmtLinesCache.js delete mode 100644 client/enum/hoverConfigOptions.js delete mode 100644 client/enum/hoverDisplayItems.js delete mode 100644 client/enum/regex.js delete mode 100644 client/info/configKeyInfo.js delete mode 100644 client/info/triggerInfo.js delete mode 100644 client/matching/matchType.js delete mode 100644 client/matching/matchWord.js delete mode 100644 client/matching/matchers/commandMatcher.js delete mode 100644 client/matching/matchers/configMatcher.js delete mode 100644 client/matching/matchers/localVarMatcher.js delete mode 100644 client/matching/matchers/packMatcher.js delete mode 100644 client/matching/matchers/parametersMatcher.js delete mode 100644 client/matching/matchers/prevCharMatcher.js delete mode 100644 client/matching/matchers/regexWordMatcher.js delete mode 100644 client/matching/matchers/switchCaseMatcher.js delete mode 100644 client/matching/matchers/triggerMatcher.js delete mode 100644 client/provider/color24Provider.js delete mode 100644 client/provider/completionProvider.js delete mode 100644 client/provider/configHelpProvider.js delete mode 100644 client/provider/gotoDefinition.js delete mode 100644 client/provider/hoverProvider.js delete mode 100644 client/provider/recolorProvider.js delete mode 100644 client/provider/referenceProvider.js delete mode 100644 client/provider/renameProvider.js delete mode 100644 client/provider/signatureHelpProvider.js delete mode 100644 client/provider/vscodeCommands.js delete mode 100644 client/resource/configKeys.js delete mode 100644 client/resource/dataTypeToMatchId.js delete mode 100644 client/resource/hoverConfigResolver.js delete mode 100644 client/resource/identifierFactory.js delete mode 100644 client/resource/postProcessors.js delete mode 100644 client/resource/triggers.js delete mode 100644 client/runescript-language.js delete mode 100644 client/utils/cacheUtils.js delete mode 100644 client/utils/markdownUtils.js delete mode 100644 client/utils/matchUtils.js delete mode 100644 client/utils/stringUtils.js create mode 100644 src/clientState.js create mode 100644 src/commands.js create mode 100644 src/devModeHighlights.js create mode 100644 src/events.js create mode 100644 src/runescript-extension.js diff --git a/.gitignore b/.gitignore index 49fa97c..39f601f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.vsix *.DS_store +node_modules/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..28eb403 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run RuneScriptLanguage Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": [], + "sourceMaps": false, + "resolveSourceMapLocations": [ + "${workspaceFolder}/**", + "!**/node_modules/**" + ] + }, + { + "name": "Attach to RuneScript LSP", + "type": "node", + "request": "attach", + "port": 6009, + "restart": true, + "sourceMaps": true, + "outFiles": [ + "${workspaceFolder}/node_modules/runescript-lsp/dist/**/*.js" + ], + "sourceMapPathOverrides": { + "../../src/*": "${workspaceFolder}/../runescript-lsp/src/*", + "../src/*": "${workspaceFolder}/../runescript-lsp/src/*" + }, + "resolveSourceMapLocations": [ + "${workspaceFolder}/node_modules/runescript-lsp/dist/**/*.js", + "${workspaceFolder}/node_modules/runescript-lsp/src/**/*.ts", + "!**/node_modules/**" + ] + } + ] +} diff --git a/client/cache/activeCursorCache.js b/client/cache/activeCursorCache.js deleted file mode 100644 index aa0b697..0000000 --- a/client/cache/activeCursorCache.js +++ /dev/null @@ -1,24 +0,0 @@ -let activeCursorMatchTypeId; -let line; -let index; -let path; - -function get(document, position) { - if (document.uri.fsPath === path && position.line === line && getIndex(document, position) === index) { - return activeCursorMatchTypeId; - } - return null; -} - -function set(value, document, position) { - path = document.uri.fsPath; - index = getIndex(document, position); - line = position.line; - activeCursorMatchTypeId = value; -} - -function getIndex(document, position) { - return document.lineAt(position.line).text.substring(0, position.character).split(',').length; -} - -module.exports = { get, set }; diff --git a/client/cache/activeFileCache.js b/client/cache/activeFileCache.js deleted file mode 100644 index 7610735..0000000 --- a/client/cache/activeFileCache.js +++ /dev/null @@ -1,120 +0,0 @@ -const vscode = require('vscode'); -const { TRIGGER_LINE, TRIGGER_DEFINITION, LOCAL_VAR_WORD_PATTERN } = require('../enum/regex'); -const { getWords } = require('../utils/matchUtils'); -const dataTypeToMatchId = require('../resource/dataTypeToMatchId'); -const { getLines } = require('../utils/stringUtils'); - -/** - * A cache which keeps track of script blocks in the active / viewing file - * Only applies to rs2 files - * Allows a quick look up of script data by passing in a line number - * Script data object: -{ - name: string - start: number (line number that the script starts on) - trigger: string - returns: string[] (matchTypeId) - variables: { $varName1: {type: string, matchTypeId: string, parameter: boolean, declaration: range, references: range[]}, ... } -} - */ -var scriptData; -var lineNumToScript; -var curData; - -function getScriptData(lineNum) { - let data; - for (const script of scriptData) { - if (lineNum >= script.start) data = script; - } - return data; -} - -function rebuild() { - scriptData = []; - lineNumToScript = {}; - curData = null; - const activeEditor = vscode.window.activeTextEditor; - if (activeEditor && activeEditor.document.uri.path.endsWith('.rs2')) { - parseFile(getLines(activeEditor.document.getText()), activeEditor.document.uri); - } -} - -function parseFile(lines, uri) { - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - let indexOffset = 0; - if (TRIGGER_LINE.test(line)) { - const definitionLength = TRIGGER_DEFINITION.exec(line); - if (definitionLength) { - // Split the line into definition part and code part, for scripts with same line code - indexOffset = definitionLength[0].length; - parseTriggerLine(line.substring(0, indexOffset), i, uri); - line = line.substring(indexOffset); // update line to only the code portion of the line (if any) - } - } - parseLine(line, i, uri, indexOffset); - } - if (curData) scriptData.push(curData); -} - -function parseTriggerLine(line, lineNum, uri) { - // Save previously parsed script data and init a new one for this block - if (curData) scriptData.push(curData); - curData = {start: lineNum, variables: {}, returns: []}; - - // Parse for script name and trigger - const nameAndTrigger = line.substring(1, line.indexOf(']')).split(','); - curData.trigger = nameAndTrigger[0]; - curData.name = nameAndTrigger[1]; - - // Parse script params and save as variables - let openingIndex = line.indexOf('('); - let closingIndex = line.indexOf(')'); - if (openingIndex >= 0 && closingIndex >= 0 && ++openingIndex !== closingIndex) { - line.substring(openingIndex, closingIndex).split(',').forEach(param => { - const split = param.trim().split(' '); - const position = new vscode.Position(lineNum, line.indexOf(split[1])); - const location = new vscode.Location(uri, new vscode.Range(position, position.translate(0, split[1].length))); - addVariable(split[0], split[1], location, true); - }); - } - - // Parse return type into an array of matchTypeId (string) - line = line.substring(closingIndex + 1); - openingIndex = line.indexOf('('); - closingIndex = line.indexOf(')'); - if (openingIndex >= 0 && closingIndex >= 0 && ++openingIndex !== closingIndex) { - curData.returns = line.substring(openingIndex, closingIndex).split(',').map(item => dataTypeToMatchId(item.trim())); - } -} - -function parseLine(line, lineNum, uri, indexOffset=0) { - const words = getWords(line.split('//')[0], LOCAL_VAR_WORD_PATTERN); - for (let i = 0; i < words.length; i++) { - if (words[i].value.charAt(0) === '$') { - const name = words[i].value; - const position = new vscode.Position(lineNum, words[i].start + indexOffset); - const location = new vscode.Location(uri, new vscode.Range(position, position.translate(0, name.length))); - (i > 0 && words[i-1].value.startsWith('def_')) ? addVariable(words[i-1].value.substring(4), name, location) : addVariableReference(name, location); - } - } -} - -function addVariable(type, name, location, isParam=false) { - curData.variables[name] = { - type: type, - matchTypeId: dataTypeToMatchId(type), - parameter: isParam, - declaration: location, - references: [] - }; - addVariableReference(name, location); -} - -function addVariableReference(name, location) { - if (curData.variables[name]) { - curData.variables[name].references.push(location); - } -} - -module.exports = { rebuild, getScriptData }; diff --git a/client/cache/cacheManager.js b/client/cache/cacheManager.js deleted file mode 100644 index f1647e7..0000000 --- a/client/cache/cacheManager.js +++ /dev/null @@ -1,197 +0,0 @@ -const fs = require('fs').promises; -const vscode = require('vscode'); -const matchType = require("../matching/matchType"); -const identifierCache = require('./identifierCache'); -const activeFileCache = require('./activeFileCache'); -const stringUtils = require('../utils/stringUtils'); -const { matchWords } = require('../matching/matchWord'); -const identifierFactory = require('../resource/identifierFactory'); -const { INFO_MATCHER, TRIGGER_LINE } = require('../enum/regex'); -const cacheUtils = require('../utils/cacheUtils'); -const returnBlockLinesCache = require('./returnBlockLinesCache'); -const switchStmtLinesCache = require('./switchStmtLinesCache'); -const dataTypeToMatchId = require('../resource/dataTypeToMatchId'); - -/** - * Builds the set of monitored file types, any file events with other file types will be ignored - * Monitored file types are determined by checking all file types defined in the matchType object - */ -const monitoredFileTypes = new Set(); -function determineFileTypes() { - monitoredFileTypes.add('pack'); - Object.keys(matchType).filter(mt => !mt.referenceOnly).forEach(matchTypeId => { - const fileTypes = matchType[matchTypeId].fileTypes || []; - for (const fileType of fileTypes) { - monitoredFileTypes.add(fileType); - } - }); -} - -/** - * Rebuilds the entire identifier cache for all relevant workspace files - * Need to do 2 passes on the files to for ensuring things like engine command - * parameters get matched correctly. On the first pass, the commands don't yet exist in the cache - * so the matching service cannot accurately build everything until 2 passes are made - */ -async function rebuildAll() { - if (monitoredFileTypes.size === 0) determineFileTypes(); - clearAll(); - const fileUris = await getFiles(); - await Promise.all(fileUris.map(uri => parseFileAndCacheIdentifiers(uri))); - await Promise.all(fileUris.map(uri => parseFileAndCacheIdentifiers(uri))); - return rebuildActiveFile(); -} - -/** - * Rebuilds the activeFileCache, parses the active text editor file and stores relevant script data - * such as script variables, script return types, switch statement types, etc... - */ -var debounceTimer; -function rebuildActiveFile() { - clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => { - activeFileCache.rebuild(); - }, 400); -} - -/** - * Rebuilds the identifier cache for identifiers in the provided file uri - */ -async function rebuildFile(uri) { - if (isValidFile(uri)) { - clearFile(uri); - parseFileAndCacheIdentifiers(uri); - rebuildActiveFile(); - } -} - -/** - * Clears the identifier cache for identifiers in the provided list of file uris - */ -async function clearFiles(uris) { - for (const uri of uris) { - if (isValidFile(uri)) { - clearFile(uri); - } - } -} - -/** - * Clears the identifier cache for identifiers in the provided list of old file uris - * and then recaches the files using the new file names - */ -async function renameFiles(uriPairs) { - for (const uriPair of uriPairs) { - if (isValidFile(uriPair.oldUri) && isValidFile(uriPair.newUri)) { - clearFile(uriPair.oldUri); - parseFileAndCacheIdentifiers(uriPair.newUri); - } - } -} - -/** - * Adds to cache for new files - */ -async function createFiles(uris) { - for (const uri of uris) { - if (isValidFile(uri)) { - parseFileAndCacheIdentifiers(uri); - } - } -} - -/** - * Get a list of all relevant files in the workspace which might contain identifiers - */ -async function getFiles() { - const fileTypesToScan = []; - monitoredFileTypes.forEach(fileType => fileTypesToScan.push(`**/*.${fileType}`)); - return vscode.workspace.findFiles(`{${[...fileTypesToScan].join(',')}}`); -} - -/** - * Parses the input file for identifiers, and caches them when found - */ -async function parseFileAndCacheIdentifiers(uri) { - const isRs2 = uri.fsPath.endsWith('.rs2'); - const fileText = await fs.readFile(uri.fsPath, "utf8"); - const lines = stringUtils.getLines(fileText); - for (let line = 0; line < lines.length; line++) { - cacheSwitchStatementBlock(line, uri); - const matches = (matchWords(lines[line], line, uri) || []).filter(match => match && match.match.cache); - if (matches.length > 0) { - const text = {lines: null, start: 0}; - matches.forEach(match => { - if (match.match.declaration) { - text.lines = (text.lines) ? text.lines : lines.slice(line); - const location = new vscode.Location(uri, new vscode.Position(line, match.context.word.start)); - const identifier = identifierFactory.build(match.word, match.match, location, getInfo(lines, line), text); - identifierCache.put(match.word, match.match, identifier); - identifierCache.putReference(match.word, match.match, uri, line, match.context.word.start); - cacheReturnBlock(identifier, line, match); - } else { - const id = match.context.cert ? undefined : match.context.packId; - let index = match.context.word.start; - if (!match.context.modifiedWord && match.word.indexOf(':') > 0) { - index += match.word.indexOf(':') + 1; - } - identifierCache.putReference(match.word, match.match, uri, line, index, id); - } - }); - } - } - - function cacheReturnBlock(identifier, line, match) { - if (isRs2 && identifier.signature.returns.length > 0 && TRIGGER_LINE.test(lines[line])) { - returnBlockLinesCache.put(line + 1, cacheUtils.resolveKey(match.word, match.match), uri); - } - } - - function cacheSwitchStatementBlock(line, uri) { - if (isRs2) { - const switchSplit = lines[line].split("switch_"); - if (switchSplit.length > 1) { - const switchMatchType = dataTypeToMatchId(switchSplit[1].split(/[ (]/)[0]); - if (switchMatchType !== matchType.UNKNOWN.id) { - switchStmtLinesCache.put(line + 1, switchMatchType, uri); - } - } - } - } -} - -/** - * Checks the previous line before an identifier for an "info" tag, if so it is added to the identifier - */ -function getInfo(lines, line) { - if (line < 1) return null; - const infoMatch = INFO_MATCHER.exec(lines[line - 1]); - return (infoMatch && infoMatch[2]) ? infoMatch[2].trim() : null; -} - -/** - * Checks if the file extension of the uri is in the list of monitored file types - */ -function isValidFile(uri) { - return monitoredFileTypes.has(uri.fsPath.split(/[#?]/)[0].split('.').pop().trim()); -} - -/** - * Empty the caches entirely - */ -function clearAll() { - identifierCache.clear(); - returnBlockLinesCache.clear(); - switchStmtLinesCache.clear(); -} - -/** - * Empty the caches for a single file - */ -function clearFile(uri) { - identifierCache.clearFile(uri); - returnBlockLinesCache.clearFile(uri); - switchStmtLinesCache.clearFile(uri); -} - -module.exports = { rebuildAll, rebuildFile, rebuildActiveFile, clearFiles, renameFiles, createFiles, clearAll } diff --git a/client/cache/class/LineReferenceCache.js b/client/cache/class/LineReferenceCache.js deleted file mode 100644 index e4c35bd..0000000 --- a/client/cache/class/LineReferenceCache.js +++ /dev/null @@ -1,57 +0,0 @@ -const { resolveFileKey } = require("../../utils/cacheUtils"); - -function encodeLineValue(startLine, identifierKey) { - return `${startLine}|${identifierKey}`; -} - -function decodeLineValue(encodedValue) { - const split = encodedValue.split('|'); - return (split.length !== 2) ? null : { line: Number(split[0]), value: split[1] }; -} - -class LineReferenceCache { - constructor() { - this.cache = {}; - } - - put(startLine, value, uri) { - const fileKey = resolveFileKey(uri); - if (value && fileKey) { - const fileLineReferences = this.cache[fileKey] || new Set(); - fileLineReferences.add(encodeLineValue(startLine, value)); - this.cache[fileKey] = fileLineReferences; - } - } - - get(lineNum, uri) { - const fileKey = resolveFileKey(uri); - const fileLineReferences = this.cache[fileKey] || new Set(); - let curKey; - let curLine = 0; - fileLineReferences.forEach(ref => { - const { line, value } = decodeLineValue(ref); - if (lineNum >= line && curLine < line) { - curKey = value; - curLine = line; - } - }); - return curKey; - } - - getAll() { - return this.cache; - } - - clearFile(uri) { - const fileKey = resolveFileKey(uri); - if (fileKey) { - delete this.cache[fileKey]; - } - } - - clear() { - this.cache = {}; - } -} - -module.exports = LineReferenceCache; diff --git a/client/cache/class/Trie.js b/client/cache/class/Trie.js deleted file mode 100644 index 8466921..0000000 --- a/client/cache/class/Trie.js +++ /dev/null @@ -1,89 +0,0 @@ -class Trie { - constructor() { - this.root = new TrieNode(); - } - - insert(word) { - if (!word) return false; - let currNode = this.root; - for (const letter of word) { - if (!currNode.children.has(letter)) { - currNode.children.set(letter, new TrieNode(letter)); - } - currNode = currNode.children.get(letter); - } - currNode.endOfWord = true; - return currNode; - } - - getLastNode(letters, start = this.root) { - let currNode = start; - for (const letter of letters) { - if (!currNode.children.has(letter)) return false; - currNode = currNode.children.get(letter); - } - return currNode; - } - - hasWord(word, start = this.root) { - let node = this.getLastNode(word, start); - return node && node !== this.root ? node.endOfWord : false; - } - - findAllWithPrefix(prefix, start = this.root) { - let words = []; - let currNode = this.getLastNode(prefix, start); - if (currNode) { - if (currNode.endOfWord) words.push(prefix); - currNode.children.forEach((child) => - this.getWordsFrom(child, prefix, words) - ); - } - return words; - } - - getWordsFrom(node = this.root, string = '', array = []) { - if (!node) return; - string += node.value; - if (node.endOfWord) array.push(string); - node.children.forEach((child) => { - this.getWordsFrom(child, string, array); - }); - return array; - } - - removeWord(word) { - if (!word) return false; - let currNode = this.root; - let stack = []; - for (const letter of word) { - if (!currNode.children.has(letter)) return false; - currNode = currNode.children.get(letter); - if (word[word.length - 1] !== currNode.value) stack.push(currNode); - } - currNode.endOfWord = false; - while (stack.length > 0 && !currNode.endOfWord) { - let prevNode = currNode; - currNode = stack.pop(); - if (prevNode.children.size > 0) { - break; - } - currNode.children.delete(prevNode.value); - } - return true; - } - - clear() { - this.root.children.clear(); - } -} - -class TrieNode { - constructor(value = "") { - this.children = new Map(); - this.value = value; - this.endOfWord = false; - } -} - -module.exports = Trie; diff --git a/client/cache/completionCache.js b/client/cache/completionCache.js deleted file mode 100644 index b9a96cc..0000000 --- a/client/cache/completionCache.js +++ /dev/null @@ -1,55 +0,0 @@ -const Trie = require('./class/Trie'); - -/** - * One trie per matchType, stores the names of all identifiers of a matchtype in a trie datastructure - * This is used for quicker code completion lookups - */ -var completionCache = {}; - -function put(name, matchTypeId) { - if (!completionCache[matchTypeId]) { - completionCache[matchTypeId] = new Trie(); - } - completionCache[matchTypeId].insert(name); - const colonIndex = name.indexOf(':'); - if (colonIndex >= 0) { - completionCache[matchTypeId].insert(name.substring(colonIndex + 1)); - } -} - -function getAllWithPrefix(prefix, matchTypeId) { - const matchTrie = completionCache[matchTypeId]; - if (matchTrie) { - return matchTrie.findAllWithPrefix(prefix); - } - return null; -} - -function contains(name, matchTypeId) { - const matchTrie = completionCache[matchTypeId]; - if (matchTrie) { - return matchTrie.hasWord(name); - } - return false; -} - -function remove(name, matchTypeId) { - const matchTrie = completionCache[matchTypeId]; - if (matchTrie) { - matchTrie.removeWord(name); - } -} - -function clear(matchTypeId) { - if (matchTypeId) { - delete completionCache[matchTypeId]; - } else { - completionCache = {}; - } -} - -function getTypes() { - return Object.keys(completionCache); -} - -module.exports = { put, getAllWithPrefix, getTypes, contains, remove, clear }; \ No newline at end of file diff --git a/client/cache/identifierCache.js b/client/cache/identifierCache.js deleted file mode 100644 index 2f9fdb6..0000000 --- a/client/cache/identifierCache.js +++ /dev/null @@ -1,136 +0,0 @@ -const { buildRef } = require('../resource/identifierFactory'); -const cacheUtils = require('../utils/cacheUtils'); -const completionCache = require('./completionCache'); - -/** - * The identifierCache stores all matched identifiers in the workspace - * identifierCache = {key [name+matchTypeId]: identifier} - * See identifierFactory.js for the object structure - */ -var identifierCache = {}; - -/** - * The fileToIdentiferMap keeps track of all identifiers and references in a file - * This is used for updating the cache as necessary when a file is modified - * fileToIdentiferMap = {filePath: {declarations: Set(): identifierKey, references: Set(): identifierKey}} - */ -var fileToIdentifierMap = {}; - -function contains(name, match) { - return identifierCache[cacheUtils.resolveKey(name, match)] !== undefined; -} - -function get(name, match) { - return identifierCache[cacheUtils.resolveKey(name, match)]; -} - -function getByKey(key) { - return identifierCache[key]; -} - -/** - * Given a file URI, a line number, this will return the closest declaration identifier - * to the given line number which is above the line number provided. - */ -function getParentDeclaration(uri, lineNum, requiredMatchTypeId=undefined) { - const fileIdentifiers = fileToIdentifierMap[cacheUtils.resolveFileKey(uri)]; - if (!fileIdentifiers) { - return null; - } - let lineRef = -1; - let declaration; - fileIdentifiers.declarations.forEach(dec => { - const iden = identifierCache[dec]; - if (iden.declaration && iden.declaration.range.start.line < lineNum && iden.declaration.range.start.line > lineRef) { - if (!requiredMatchTypeId || requiredMatchTypeId === iden.matchId) { - lineRef = iden.declaration.range.start.line; - declaration = iden; - } - } - }); - return declaration; -} - -function put(name, match, identifier) { - const key = cacheUtils.resolveKey(name, match); - const fileKey = cacheUtils.resolveFileKey(identifier.declaration.uri); - if (!key || !fileKey) { - return null; - } - let curIdentifier = identifierCache[key]; - if (curIdentifier && curIdentifier.declaration) { - return null; // declaration already exists, don't overwrite, if it needs to be updated it should be deleted first - } - if (curIdentifier) { - if (curIdentifier.id) identifier.id = curIdentifier.id; - if (!curIdentifier.declaration) identifier.references = curIdentifier.references; - } - addToFileMap(fileKey, key); - identifierCache[key] = identifier; - completionCache.put(name, match.id); -} - -function putReference(name, match, uri, lineNum, index, packId) { - const key = cacheUtils.resolveKey(name, match) - const fileKey = cacheUtils.resolveFileKey(uri); - if (!key || !fileKey) { - return null; - } - if (!identifierCache[key]) { - identifierCache[key] = buildRef(name, match); - } - const fileReferences = identifierCache[key].references[fileKey] || new Set(); - fileReferences.add(cacheUtils.encodeReference(lineNum, index)); - addToFileMap(fileKey, key, false); - identifierCache[key].references[fileKey] = fileReferences; - if (packId) identifierCache[key].id = packId; - if (match.referenceOnly) completionCache.put(name, match.id); -} - -function clear() { - identifierCache = {}; - fileToIdentifierMap = {}; - completionCache.clear(); -} - -function clearFile(uri) { - const fileKey = cacheUtils.resolveFileKey(uri); - const identifiersInFile = fileToIdentifierMap[fileKey] || { declarations: new Set(), references: new Set() }; - identifiersInFile.references.forEach(key => { - if (identifierCache[key]) { - // Delete references to the cleared file from every identifier which referenced the file - if (identifierCache[key].references[fileKey]) { - delete identifierCache[key].references[fileKey]; - } - // Cleanup/Delete identifiers without a declaration who no longer have any references - if (Object.keys(identifierCache[key].references).length === 0 && !identifierCache[key].declaration) { - const iden = identifierCache[key]; - completionCache.remove(iden.name, iden.matchId); - delete identifierCache[key]; - } - } - }) - identifiersInFile.declarations.forEach(key => { - if (identifierCache[key]) { - // If the identifier has orphaned references, then we only delete the declaration and keep the identifier w/references - // Otherwise, we delete the entire identifier (no declaration and no references => no longer exists in any capacity) - const iden = identifierCache[key]; - completionCache.remove(iden.name, iden.matchId); - const hasOrphanedRefs = Object.keys(identifierCache[key].references).length > 0; - if (hasOrphanedRefs) { - delete identifierCache[key].declaration; - } else { - delete identifierCache[key]; - } - } - }); - delete fileToIdentifierMap[fileKey]; -} - -function addToFileMap(fileKey, identifierKey, declaration=true) { - const identifiersInFile = fileToIdentifierMap[fileKey] || { declarations: new Set(), references: new Set() }; - (declaration) ? identifiersInFile.declarations.add(identifierKey) : identifiersInFile.references.add(identifierKey); - fileToIdentifierMap[fileKey] = identifiersInFile; -} - -module.exports = { contains, get, getParentDeclaration, getByKey, put, putReference, clear, clearFile }; diff --git a/client/cache/returnBlockLinesCache.js b/client/cache/returnBlockLinesCache.js deleted file mode 100644 index 345b9c6..0000000 --- a/client/cache/returnBlockLinesCache.js +++ /dev/null @@ -1,11 +0,0 @@ -const LineReferenceCache = require("./class/LineReferenceCache"); - -/** - * A cache which enables a quick lookup of the identifier for the block the line is in - * Given a line number, it will return the name of the block that line number is a part of (if any) - * A block referring to the code block of a proc, label, queue, etc... - * This cache is used to quickly determine the return type for a given line - */ -const returnBlockLinesCache = new LineReferenceCache(); - -module.exports = returnBlockLinesCache; diff --git a/client/cache/switchStmtLinesCache.js b/client/cache/switchStmtLinesCache.js deleted file mode 100644 index b2ffbc4..0000000 --- a/client/cache/switchStmtLinesCache.js +++ /dev/null @@ -1,10 +0,0 @@ -const LineReferenceCache = require("./class/LineReferenceCache"); - -/** - * A cache which enables a quick lookup of the matchType of a switch statement - * Given a line number, this cache will return the type (if any) for the switch statement - * that line number is a part of - */ -const switchStmtLinesCache = new LineReferenceCache(); - -module.exports = switchStmtLinesCache; diff --git a/client/enum/hoverConfigOptions.js b/client/enum/hoverConfigOptions.js deleted file mode 100644 index 2ffe6e7..0000000 --- a/client/enum/hoverConfigOptions.js +++ /dev/null @@ -1,9 +0,0 @@ -const option = { - DECLARATION_HOVER_ITEMS: 'DECLARATION_HOVER_ITEMS', // display items that show on hover for identifier declarations - REFERENCE_HOVER_ITEMS: 'REFERENCE_HOVER_ITEMS', // display items that show on hover for identifier references - LANGUAGE: 'LANGUAGE', // the code language that this matchType should use in hover codeblock text - BLOCK_SKIP_LINES: 'BLOCK_SKIP_LINES', // the number of lines to skip in code block displays (default value is 1 -> skip first line for most blocks which is the '[identifierName]' line) - CONFIG_INCLUSIONS: 'CONFIG_INCLUSIONS' // the config tags you want to be shown (ex: obj displays name, desc, and category only), if null (default) then all fields are displayed -} - -module.exports = option; diff --git a/client/enum/hoverDisplayItems.js b/client/enum/hoverDisplayItems.js deleted file mode 100644 index ae4be8d..0000000 --- a/client/enum/hoverDisplayItems.js +++ /dev/null @@ -1,14 +0,0 @@ -// In order for a display item to be shown in hover texts, the matchType to which the identifier belongs to -// must define a declaration or reference config which includes the desired hoverDisplay item in its displayItems array -// Note: in order to get identifier.value to display you must define a custom postProcessor for the matchType which -// populates identifier.value, there is no default value parsing like there is with the others - -const hoverDisplay = { - TITLE: 'title', // hover text title display : fileType.png matchType.id identifier.name - INFO: 'info', // hover text info display : identifier.info (in italics) - VALUE: 'value', // hover text value display : identifier.value (plain text) - SIGNATURE: 'signature', // signature display : identifier.params
identifier.returns (in code syntax) - CODEBLOCK: 'codeblock' // block display : identifier.block (in code syntax) -}; - -module.exports = hoverDisplay; diff --git a/client/enum/regex.js b/client/enum/regex.js deleted file mode 100644 index 707ca58..0000000 --- a/client/enum/regex.js +++ /dev/null @@ -1,22 +0,0 @@ -const regex = { - COORD: /(\d+_){4}\d+/, - COLOR: /\d{6}/, - RECOLOR: /(recol[1-6][sd])=(\d+)/g, - NUMBER: /^\d+.?\d+$/, - END_OF_BLOCK: /(\r\n|\r|\n)(\[.+|val=.+|\^.+|\d+=.+)(?:$|(\r\n|\r|\n))/, - END_OF_BLOCK_LINE: /^(\[|\^|\d+=)/, - START_OF_LINE: /(?<=[\n])(?!.*[\n]).*/, - END_OF_LINE: /\r\n|\r|\n/, - WORD_PATTERN: /(\.\w+)|(\w+:\w+)|([^\`\~\!\@\#\%\^\&\*\(\)\-\$\=\+\[\{\]\}\\\|\;\:\'\\"\,\.\<\>\/\?\s]+)/g, - LOCAL_VAR_WORD_PATTERN: /(\$\w+)|(\.\w+)|(\w+:\w+)|([^\`\~\!\@\#\%\^\&\*\(\)\-\$\=\+\[\{\]\}\\\|\;\:\'\\"\,\.\<\>\/\?\s]+)/g, - CONFIG_LINE: /^\w+=.+$/, - CONFIG_DECLARATION: /\[\w+\]/, - TRIGGER_LINE: /\[\w+,(\.)?\w+(:\w+)?\]/, - TRIGGER_DEFINITION: /\[.+,.+\](\([\w, :\.$]*\))?(\([\w, :\.$]*\))?/, - INFO_MATCHER: /\/\/[ ]{0,1}(desc|info):(.+)/, - SWITCH_CASE: /\s*case.+/, - COLOR24: /(colour|mapcolour|activecolour|overcolour|activeovercolour)=(\w+)/g, - LOC_MODEL: /^(?!model_[a-z0-9]$)\w+_[a-z0-9]\b/ -} - -module.exports = regex; diff --git a/client/info/configKeyInfo.js b/client/info/configKeyInfo.js deleted file mode 100644 index ec589ac..0000000 --- a/client/info/configKeyInfo.js +++ /dev/null @@ -1,47 +0,0 @@ -const { expandCsvKeyObject } = require("../utils/matchUtils"); - -/** - * Defines any config keys with info that will be displayed when the user hovers over that config key - * Format: { key: { 'any': 'info for any fileType', 'obj': 'obj specific info', 'loc, npc': 'loc and npc specific info' } } - * You can define different info for specific file types, or use 'any' to apply to all file types (unless already defined) - * Tip: you can use the same value for multiple file types using a key as a CSV (i.e. use 'obj, loc, npc' as a key) - * Tip: config key will apply to all tags which end in numbers (for example stock will apply to stock1, stock2, stock100, etc...) - * Tip: you can use $TYPE which will be replaced by the file type of the config (loc, obj, etc...) - */ -const configKeyInfo = expandInfo({ - type: { 'varp': 'The data type of this player variable' }, - param: { 'any': 'A param value in the format "paramName,value"' }, - inputtype: { 'enum': 'The input data type for the enum' }, - outputtype: { 'enum': 'The output data type for the enum' }, - val: { 'enum': 'A data value for the enum in the format "inputData,outputData"' }, - scope: { 'varp': 'The lifetime of a player variable\n\nBy default it is temporary and reset on logout/login. You can make it persist by setting scope=perm' }, - protect: { 'varp': 'If the player variable should require protected access\n\nDefault value true (acceptable values: true/yes, false/no)\n\nProtected means a script can not write to it without sole access, but a varp can always be read regardless of the protection.' }, - clientcode: { 'varp, if': 'Ties this to specific client-side code logic\n\nAcceptable value defined in client source, if you actually need this you should already know what to put.' }, - transmit: { 'varp': 'If a player variable should be transmitted to the client\n\nDefault value false (acceptable values: true/yes, false/no)\n\nThe main use for this property is in conjunction with interfaces.' }, - stock: { 'inv': 'Stock of an item in a shop, format "object,stock,restock_ticks"'}, - count: { 'obj': 'Object to use when the based on the stack size of an item' }, - respawnrate: { 'obj, npc': 'Respawn rate of this $TYPE, in game ticks' }, - category: { 'any': 'The category this $TYPE belongs to, multiple categories are possible\n\nCan be used with category engine commands such as inv_totalcat\n\nAlso can be used in triggers by preceeding the category with an underscore (_)\n\nEx: [oplocu,_watersource] is a script which applies to all items with the category \'watersource\'' }, - basevar: { 'varbit': 'The base varp backing this varbit.' }, - startbit: { 'varbit': 'The starting bit range on the basevar to limit this view to.' }, - endbit: { 'varbit': 'The ending bit range on the basevar to limit this view to.' }, -}); -function expandInfo(obj) { - Object.keys(obj).forEach(key => obj[key] = expandCsvKeyObject(obj[key])); - return obj; -} - -// Find info for a given config key. If no fileType, will match config keys for 'any' type. Else, return null. -function matchConfigKeyInfo(key, fileType) { - const endingNums = key.match(/\d+$/); - if (endingNums) { - key = key.substring(0, key.indexOf(endingNums)); - } - const info = configKeyInfo[key]; - if (info[fileType]) { - return info[fileType]; - } - return info.any; -} - -module.exports = matchConfigKeyInfo; diff --git a/client/info/triggerInfo.js b/client/info/triggerInfo.js deleted file mode 100644 index 3215321..0000000 --- a/client/info/triggerInfo.js +++ /dev/null @@ -1,18 +0,0 @@ -const { expandCsvKeyObject } = require("../utils/matchUtils"); - -/** - * Defines trigger information which will be displayed on hover if a user hovers over a trigger keyword - * Tip: You can use CSV keys such as 'oploc1, oploc2, oploc3' to apply the same info message for all of those triggers - * Tip: The string 'NAME' will be replaced with the actual triggers defined name [trigger,triggerName] - */ -const triggerInfo = expandCsvKeyObject({ - logout: 'The script that executes when the user logs out', - debugproc: 'Proc that only runs for users with cheats enabled, run with ::NAME' -}); - - -function matchTriggerInfo(key, triggerName) { - return (triggerInfo[key] || '').replace('NAME', triggerName); -} - -module.exports = matchTriggerInfo; diff --git a/client/matching/matchType.js b/client/matching/matchType.js deleted file mode 100644 index 4ec30f2..0000000 --- a/client/matching/matchType.js +++ /dev/null @@ -1,196 +0,0 @@ -const { dataTypePostProcessor, enumPostProcessor, columnPostProcessor, rowPostProcessor, componentPostProcessor, - fileNamePostProcessor, coordPostProcessor, configKeyPostProcessor, triggerPostProcessor, categoryPostProcessor } = require('../resource/postProcessors'); -const { VALUE, SIGNATURE, CODEBLOCK, TITLE, INFO } = require("../enum/hoverDisplayItems"); -const { DECLARATION_HOVER_ITEMS, REFERENCE_HOVER_ITEMS, LANGUAGE, BLOCK_SKIP_LINES, CONFIG_INCLUSIONS } = require('../enum/hoverConfigOptions'); - -/* -Match types define the possible types of identifiers that can be found. The config for a match type tells the extension -all the necessary data it needs for finding declarations, building hover texts, and finding references. -{ - id: String - the unique id for the matchType, - types: String[] - the type keywords which map to this matchType, for example: [namedobj, obj] for OBJ - fileTypes: String[] - the possible file types this matchType can be declared in - cache: boolean - whether or not identifiers with this matchType should be cached - hoverConfig: Object - Config options to modify the hover display for this matchType, options in hoverConfig.js - postProcessor: Function(identifier) - An optional post processing function to apply for this matchType, see postjs - allowRename: Whether or not to allow rename symbol (F2) on this type - referenceOnly: If true, then declaration is not saved/doesn't exist and only references exist. Default ctrl+click will be goto references rather than goto definition. - hoverOnly: boolean - if true, this match type is only used for hover displays - noop: boolean - if true, nothing is done with this match type (but still useful for terminating word searching early) -} -*/ -const matchType = { - LOCAL_VAR: { - id: 'LOCAL_VAR', types: [], fileTypes: ['rs2'], cache: false, allowRename: true, - }, - GLOBAL_VAR: { - id: 'GLOBAL_VAR', types: ['var'], fileTypes: ['varp', 'varbit', 'vars', 'varn'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'varpconfig'}, - postProcessor: dataTypePostProcessor - }, - CONSTANT: { - id: 'CONSTANT', types: [], fileTypes: ['constant'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'constants', [BLOCK_SKIP_LINES]: 0}, - }, - LABEL: { - id: 'LABEL', types: ['label'], fileTypes: ['rs2'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE]}, - }, - PROC: { - id: 'PROC', types: ['proc'], fileTypes: ['rs2'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE]}, - }, - TIMER: { - id: 'TIMER', types: ['timer'], fileTypes: ['rs2'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE]}, - }, - SOFTTIMER: { - id: 'SOFTTIMER', types: ['softtimer'], fileTypes: ['rs2'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE]}, - }, - QUEUE: { - id: 'QUEUE', types: ['queue'], fileTypes: ['rs2'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE]}, - }, - SEQ: { - id: 'SEQ', types: ['seq'], fileTypes: ['seq'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO], [LANGUAGE]: 'seqconfig'}, - }, - SPOTANIM: { - id: 'SPOTANIM', types: ['spotanim'], fileTypes: ['spotanim'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO], [LANGUAGE]: 'spotanimconfig'}, - }, - HUNT: { - id: 'HUNT', types: ['hunt'], fileTypes: ['hunt'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'huntconfig', [CONFIG_INCLUSIONS]: ['type']}, - }, - LOC: { - id: 'LOC', types: ['loc'], fileTypes: ['loc'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'locconfig', [CONFIG_INCLUSIONS]: ['name', 'desc', 'category']}, - }, - NPC: { - id: 'NPC', types: ['npc'], fileTypes: ['npc'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'npcconfig', [CONFIG_INCLUSIONS]: ['name', 'desc', 'category']}, - }, - OBJ: { - id: 'OBJ', types: ['namedobj', 'obj'], fileTypes: ['obj'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'objconfig', [CONFIG_INCLUSIONS]: ['name', 'desc', 'category']}, - }, - INV: { - id: 'INV', types: ['inv'], fileTypes: ['inv'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'invconfig', [CONFIG_INCLUSIONS]: ['scope', 'size']}, - }, - ENUM: { - id: 'ENUM', types: ['enum'], fileTypes: ['enum'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'enumconfig', [CONFIG_INCLUSIONS]: ['inputtype', 'outputtype']}, - postProcessor: enumPostProcessor - }, - DBCOLUMN: { - id: 'DBCOLUMN', types: ['dbcolumn'], fileTypes: ['dbtable'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'runescript', [BLOCK_SKIP_LINES]: 0}, - postProcessor: columnPostProcessor - }, - DBROW: { - id: 'DBROW', types: ['dbrow'], fileTypes: ['dbrow'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'dbrowconfig', [CONFIG_INCLUSIONS]: ['table']}, - postProcessor: rowPostProcessor - }, - DBTABLE: { - id: 'DBTABLE', types: ['dbtable'], fileTypes: ['dbtable'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'dbtableconfig'}, - }, - INTERFACE: { - id: 'INTERFACE', types: ['interface'], fileTypes: ['if'], cache: true, allowRename: false, referenceOnly: true, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE, INFO], [LANGUAGE]: 'interface'}, - postProcessor: fileNamePostProcessor - }, - COMPONENT: { - id: 'COMPONENT', types: ['component'], fileTypes: ['if'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO], [LANGUAGE]: 'interface'}, - postProcessor: componentPostProcessor - }, - PARAM: { - id: 'PARAM', types: ['param'], fileTypes: ['param'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'paramconfig'}, - postProcessor: dataTypePostProcessor - }, - COMMAND: { - id: 'COMMAND', types: [], fileTypes: ['rs2'], cache: true, allowRename: false, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE]}, - }, - SYNTH: { - id: 'SYNTH', types: ['synth'], fileTypes: ['synth'], cache: true, allowRename: true, referenceOnly: true, renameFile: true, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE, INFO]}, - postProcessor: fileNamePostProcessor - }, - MODEL: { - id: 'MODEL', types: ['ob2', 'model'], fileTypes: ['ob2'], cache: true, allowRename: true, referenceOnly: true, renameFile: true, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE, INFO]}, - }, - WALKTRIGGER: { - id: 'WALKTRIGGER', types: ['walktrigger'], fileTypes: ['rs2'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, SIGNATURE]}, - }, - IDK: { - id: 'IDK', types: ['idk', 'idkit'], fileTypes: ['idk'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO, CODEBLOCK], [LANGUAGE]: 'idkconfig'}, - }, - MESANIM: { - id: 'MESANIM', types: ['mesanim'], fileTypes: ['mesanim'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO], [LANGUAGE]: 'mesanimconfig'}, - }, - STRUCT: { - id: 'STRUCT', types: ['struct'], fileTypes: ['struct'], cache: true, allowRename: true, - hoverConfig: {[DECLARATION_HOVER_ITEMS]: [TITLE, INFO], [REFERENCE_HOVER_ITEMS]: [TITLE, INFO], [LANGUAGE]: 'structconfig'}, - }, - // Hover only match types that are only used for displaying hover displays (no finding references/declarations) - // Useful for terminating word searches early when detected. Postprocessing can be done on these. - // Specify referenceConfig to select which displayItems should be shown on hover. - COORDINATES: { - id: 'COORDINATES', types: [], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE, VALUE]}, - postProcessor: coordPostProcessor - }, - CONFIG_KEY: { - id: 'CONFIG_KEY', types: [], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE, INFO]}, - postProcessor: configKeyPostProcessor - }, - TRIGGER: { - id: 'TRIGGER', types: [], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE, INFO]}, - postProcessor: triggerPostProcessor - }, - STAT: { - id: 'STAT', types: ['stat'], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE]}, - }, - NPC_STAT: { - id: 'NPC_STAT', types: ['npc_stat'], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE]}, - }, - NPC_MODE: { - id: 'NPC_MODE', types: ['npc_mode'], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE]}, - }, - LOCSHAPE: { - id: 'LOCSHAPE', types: ['locshape'], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE]}, - }, - FONTMETRICS: { - id: 'FONTMETRICS', types: ['fontmetrics'], hoverOnly: true, cache: false, allowRename: false, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE]}, - }, - CATEGORY: { - id: 'CATEGORY', types: ['category'], hoverOnly: true, cache: true, allowRename: true, referenceOnly: true, - hoverConfig: {[REFERENCE_HOVER_ITEMS]: [TITLE, VALUE]}, - postProcessor: categoryPostProcessor - }, - // NOOP Match types that might get detected, but nothing is done with them (no hover display, no finding references/declarations) - // Useful for terminating word searching early when detected, and possibly doing something with them at a later date - UNKNOWN: { id: 'UNKNOWN', noop: true, cache: false }, // default to map to when a value is matched but no specific matchType for it - COLOR: { id: 'COLOR', noop: true, cache: false }, - NUMBER: { id: 'NUMBER', noop: true, cache: false } -}; - -module.exports = matchType; diff --git a/client/matching/matchWord.js b/client/matching/matchWord.js deleted file mode 100644 index ff70384..0000000 --- a/client/matching/matchWord.js +++ /dev/null @@ -1,131 +0,0 @@ -const matchType = require('./matchType'); -const { getWordAtIndex, getBaseContext } = require('../utils/matchUtils'); -const { getParentDeclaration } = require('../cache/identifierCache'); -const { LOC_MODEL } = require('../enum/regex'); - -// Do not reorder the matchers unless there is a reason to -// quicker potential matches are processed earlier in order to short circuit faster -const matchers = [ - require('./matchers/packMatcher'), - require('./matchers/regexWordMatcher'), - require('./matchers/commandMatcher'), - require('./matchers/localVarMatcher'), - require('./matchers/prevCharMatcher'), - require('./matchers/triggerMatcher'), - require('./matchers/configMatcher').configMatcher, - require('./matchers/switchCaseMatcher'), - require('./matchers/parametersMatcher').parametersMatcher -]; - -/** - * Match with one word given a vscode document and a vscode position - */ -function matchWordFromDocument(document, position) { - return matchWord(document.lineAt(position.line).text, position.line, document.uri, position.character); -} - -/** - * Match with one word given a line of text and an index position - */ -function matchWord(lineText, lineNum, uri, index) { - if (!lineText || !uri || !index) { - return undefined; - } - const context = getBaseContext(lineText, lineNum, uri); - const word = getWordAtIndex(context.words, index); - const wordContext = { - ...context, - word: word, - lineIndex: index, - prevWord: (word.index === 0) ? undefined : context.words[word.index - 1], - prevChar: lineText.charAt(word.start - 1), - nextChar: lineText.charAt(word.end + 1), - } - return match(wordContext); -} - -/** - * Match with all words given a line of text - */ -function matchWords(lineText, lineNum, uri) { - if (!lineText || !uri) { - return undefined; - } - const context = getBaseContext(lineText, lineNum, uri); - const matches = []; - for (let i = 0; i < context.words.length; i++) { - const wordContext = { - ...context, - word: context.words[i], - lineIndex: context.words[i].start, - prevWord: (i === 0) ? undefined : context.words[i-1], - prevChar: lineText.charAt(context.words[i].start - 1), - nextChar: lineText.charAt(context.words[i].end + 1), - } - matches.push(match(wordContext)); - } - return matches; -} - -/** - * Iterates thru all matchers to try to find a match, short circuits early if a match is made - */ -function match(context) { - if (!context.word || context.word.value === 'null') { // Also ignore null - return response(); - } - - for (const matcher of matchers) { - let match = matcher(context); - if (match) { - return response(match, context); - } - } - return response(); -} - -/** - * Build the response object for a match response - */ -function response(match, context) { - if (!match || !context) { - return undefined; - } - if (match.id === matchType.COMPONENT.id && !context.word.value.includes(':')) { - context.word.value = `${context.file.name}:${context.word.value}`; - context.modifiedWord = true; - } - if (match.id === matchType.DBCOLUMN.id && !context.word.value.includes(':')) { - const requiredType = context.file.type === 'dbtable' ? matchType.DBTABLE.id : matchType.DBROW.id; - const iden = getParentDeclaration(context.uri, context.line.number, requiredType); - if (!iden) { - return undefined; - } - const tableName = (context.file.type === 'dbrow') ? iden.extraData.table : iden.name; - context.word.value = `${tableName}:${context.word.value}`; - context.modifiedWord = true; - } - if (match.id === matchType.OBJ.id && context.word.value.startsWith('cert_')) { - context.word.value = context.word.value.substring(5); - context.word.start = context.word.start + 5; - context.originalPrefix = 'cert_'; - context.cert = true; - context.modifiedWord = true; - } - if (match.id === matchType.CATEGORY.id && context.word.value.startsWith('_')) { - context.word.value = context.word.value.substring(1); - context.word.start = context.word.start + 1; - context.originalPrefix = '_'; - context.modifiedWord = true; - } - // If model match type, determine if it is a loc model and if so remove the suffix part (_0 or _q, etc...) - if (match.id === matchType.MODEL.id && LOC_MODEL.test(context.word.value)) { - const lastUnderscore = context.word.value.lastIndexOf("_"); - context.originalSuffix = context.word.value.slice(lastUnderscore); - context.word.value = context.word.value.slice(0, lastUnderscore); - context.modifiedWord = true; - } - return { match: match, word: context.word.value, context: context }; -} - -module.exports = { matchWord, matchWords, matchWordFromDocument }; diff --git a/client/matching/matchers/commandMatcher.js b/client/matching/matchers/commandMatcher.js deleted file mode 100644 index d8f20d8..0000000 --- a/client/matching/matchers/commandMatcher.js +++ /dev/null @@ -1,22 +0,0 @@ -const identifierCache = require("../../cache/identifierCache"); -const matchType = require("../matchType"); -const { reference, declaration } = require("../../utils/matchUtils"); -const { TRIGGER_LINE } = require("../../enum/regex"); - -/** - * Looks for matches of known engine commands - */ -function commandMatcher(context) { - const command = identifierCache.get(context.word.value, matchType.COMMAND); - if (command) { - if (context.uri.fsPath.includes("engine.rs2") && TRIGGER_LINE.test(context.line.text) && context.word.index === 1) { - return declaration(matchType.COMMAND); - } - if (command.signature.params.length > 0 && context.nextChar !== '('){ - return null; - } - return reference(matchType.COMMAND); - } -} - -module.exports = commandMatcher; diff --git a/client/matching/matchers/configMatcher.js b/client/matching/matchers/configMatcher.js deleted file mode 100644 index e1b0d96..0000000 --- a/client/matching/matchers/configMatcher.js +++ /dev/null @@ -1,147 +0,0 @@ -const { CONFIG_DECLARATION, CONFIG_LINE } = require("../../enum/regex"); -const matchType = require("../matchType"); -const { declaration, reference } = require("../../utils/matchUtils"); -const dataTypeToMatchId = require("../../resource/dataTypeToMatchId"); -const { regexConfigKeys, configKeys, specialCaseKeys } = require("../../resource/configKeys"); -const identifierCache = require('../../cache/identifierCache'); - -/** - * Looks for matches on config files, both config declarations and config line items - */ -function configMatcher(context) { - // Check for config file declarations (i.e. declarations with [NAME]) - if (CONFIG_DECLARATION.test(context.line.text)) { - return declarationMatcher(context); - } - - // Check if the line we are matching is a config line - const configMatch = getConfigLineMatch(context); - return configMatch ? configMatch.match : undefined; -} - -function declarationMatcher(context) { - switch (context.file.type) { - case "varp": case "varbit": case "varn": case "vars": return declaration(matchType.GLOBAL_VAR); - case "obj": return declaration(matchType.OBJ); - case "loc": return declaration(matchType.LOC); - case "npc": return declaration(matchType.NPC); - case "param": return declaration(matchType.PARAM); - case "seq": return declaration(matchType.SEQ); - case "struct": return declaration(matchType.STRUCT); - case "dbrow": return declaration(matchType.DBROW); - case "dbtable": return declaration(matchType.DBTABLE); - case "enum": return declaration(matchType.ENUM); - case "hunt": return declaration(matchType.HUNT); - case "inv": return declaration(matchType.INV); - case "spotanim": return declaration(matchType.SPOTANIM); - case "idk": return declaration(matchType.IDK); - case "mesanim": return declaration(matchType.MESANIM); - case "if": return declaration(matchType.COMPONENT) - } -} - -function getConfigLineMatch(context) { - if (!CONFIG_LINE.test(context.line.text)) return null; - const configKey = context.words[0].value; - let response = {key: configKey}; - // The config key itsself is selected, so check if it is a known config key or not (config key with info) - if (context.word.index === 0) { - return {...response, match: reference(matchType.CONFIG_KEY)}; - } - // Check for special cases that need to be manually handled - if (specialCaseKeys.includes(configKey)) { - return handleSpecialCases(response, configKey, context); - } - // Otherwise, if the second word is the selected word (word after '=') then handle remaining known keys/regex keys - if (context.word.index >= 1) { - const configMatch = configKeys[configKey] || getRegexKey(configKey, context); - if (configMatch) { - const paramIndex = getParamIndex(context); - const param = configMatch.params[paramIndex]; - if (param) { - const match = (param.declaration) ? declaration(matchType[dataTypeToMatchId(param.typeId)]) : reference(matchType[dataTypeToMatchId(param.typeId)]); - return {...response, match: match, params: configMatch.params.map(p => p.typeId), index: paramIndex}; - } - } - } - return null; -} - -function getRegexKey(configKey, context) { - const fileTypeRegexMatchers = regexConfigKeys.get(context.file.type) || []; - for (let regexKey of fileTypeRegexMatchers) { - if (regexKey.regex.test(configKey)) { - return regexKey; - } - } - return null; -} - -function getParamIndex(context) { - let line = context.line.text; - let index = 0; - const split = line.substring(index).split(','); - for (i = 0; i < split.length; i++) { - index += split[i].length + 1; - if (context.lineIndex < index) { - return i; - } - } - return undefined; -} - -function handleSpecialCases(response, key, context) { - switch (key) { - case 'param': return paramSpecialCase(response, context); - case 'val': return valSpecialCase(response, context); - case 'data': return dataSpecialCase(response, context); - } -} - -function paramSpecialCase(response, context) { - if (context.word.index === 1) { - return {...response, match: reference(matchType.PARAM), params: ['param','value'], index: 0}; - } - if (context.word.index === 2) { - const paramIdentifier = identifierCache.get(context.words[1].value, matchType.PARAM); - if (paramIdentifier && paramIdentifier.extraData) { - const match = reference(matchType[dataTypeToMatchId(paramIdentifier.extraData.dataType)]); - return {...response, match: match, params: [paramIdentifier.name, paramIdentifier.extraData.dataType], index: 1}; - } - } - return {...response, match: matchType.UNKNOWN}; -} - -function valSpecialCase(response, context) { - const enumIdentifier = identifierCache.getParentDeclaration(context.uri, context.line.number); - if (enumIdentifier) { - response.params = [enumIdentifier.extraData.inputType, enumIdentifier.extraData.outputType]; - response.index = getParamIndex(context); - response.match = reference(matchType[dataTypeToMatchId(response.params[response.index])]); - return response; - } - return {...response, match: matchType.UNKNOWN}; -} - -function dataSpecialCase(response, context) { - if (context.word.index === 1) { - return {...response, match: reference(matchType.DBCOLUMN), params: ['dbcolumn', 'fields...'], index: 0}; - } - if (context.word.index > 1) { - let colName = context.words[1].value; - if (context.words[1].value.indexOf(':') < 0) { - const row = identifierCache.getParentDeclaration(context.uri, context.line.number); - colName = `${row.extraData.table}:${context.words[1].value}` - } - const col = identifierCache.get(colName, matchType.DBCOLUMN); - if (col && col.extraData) { - response.params = [col.name, ...col.extraData.dataTypes]; - response.index = getParamIndex(context); - response.match = reference(matchType[dataTypeToMatchId(response.params[response.index])]); - return response; - } - } - return {...response, match: matchType.UNKNOWN}; -} - -module.exports = { configMatcher, getConfigLineMatch }; diff --git a/client/matching/matchers/localVarMatcher.js b/client/matching/matchers/localVarMatcher.js deleted file mode 100644 index 6235f83..0000000 --- a/client/matching/matchers/localVarMatcher.js +++ /dev/null @@ -1,23 +0,0 @@ -const matchType = require("../matchType"); -const { reference, declaration } = require("../../utils/matchUtils"); - -/** - * Looks for matches of local variables - */ -function matchLocalVar(context) { - if (context.prevChar === '$') { - let prevWord = context.prevWord; - if (!prevWord) { - return reference(matchType.LOCAL_VAR); - } - prevWord = prevWord.value; - if (prevWord.startsWith("def_")) { - prevWord = prevWord.substr(4); - } - const defKeyword = "\\b(int|string|boolean|seq|locshape|component|idk|midi|npc_mode|namedobj|synth|stat|npc_stat|fontmetrics|enum|loc|model|npc|obj|player_uid|spotanim|npc_uid|inv|category|struct|dbrow|interface|dbtable|coord|mesanim|param|queue|weakqueue|timer|softtimer|char|dbcolumn|proc|label)\\b"; - const match = prevWord.match(new RegExp(defKeyword)); - return !match ? reference(matchType.LOCAL_VAR) : declaration(matchType.LOCAL_VAR); - } -} - -module.exports = matchLocalVar; diff --git a/client/matching/matchers/packMatcher.js b/client/matching/matchers/packMatcher.js deleted file mode 100644 index 5642a78..0000000 --- a/client/matching/matchers/packMatcher.js +++ /dev/null @@ -1,25 +0,0 @@ -const { reference } = require("../../utils/matchUtils"); -const dataTypeToMatchId = require("../../resource/dataTypeToMatchId"); -const matchType = require("../matchType"); - -/** - * Looks for matches in pack files - */ -function packMatcher(context) { - if (context.file.type === 'pack' && context.word.index === 1) { - let match; - if (matchType.GLOBAL_VAR.fileTypes.includes(context.file.name)) { - match = matchType.GLOBAL_VAR; - } else if(context.file.name === 'interface' && context.word.value.includes(':')) { - match = matchType.COMPONENT; - } else { - match = matchType[dataTypeToMatchId(context.file.name)]; - } - if (match.id !== matchType.UNKNOWN.id) { - context.packId = context.words[0].value; - } - return reference(match); - } -} - -module.exports = packMatcher; diff --git a/client/matching/matchers/parametersMatcher.js b/client/matching/matchers/parametersMatcher.js deleted file mode 100644 index 348be36..0000000 --- a/client/matching/matchers/parametersMatcher.js +++ /dev/null @@ -1,155 +0,0 @@ -const matchType = require('../matchType'); -const identifierCache = require('../../cache/identifierCache'); -const returnBlockLinesCache = require('../../cache/returnBlockLinesCache'); -const { getWordAtIndex, reference } = require('../../utils/matchUtils'); - -/** - * Looks for matches of values inside of parenthesis - * This includes return statement params, engine command parameters, proc parameters, label parameters, and queue parameters - */ -function parametersMatcher(context) { - if (context.file.type !== 'rs2') { - return null; - } - const paramsIdentifier = getParamsMatch(context); - return (paramsIdentifier) ? paramsIdentifier.match : null; -} - -// Checks if the index location of a line of code is within the parenthesis of an identifier -// If it is, it returns which param index the cursor is at, the match type of that param, and the parent identifier itself -function getParamsMatch(context) { - const { identifierName, paramIndex } = parseForIdentifierNameAndParamIndex(context.line.text, context.lineIndex, context.words); - if (!identifierName) { - return null; - } - const name = identifierName.value; - const prev = context.line.text.charAt(identifierName.start - 1); - - if (name === 'return') { - const blockIdentifierKey = returnBlockLinesCache.get(context.line.number, context.uri); - if (blockIdentifierKey) { - const iden = identifierCache.getByKey(blockIdentifierKey); - if (iden && iden.signature && iden.signature.returns.length > paramIndex) { - return {identifier: iden, index: paramIndex, match: reference(matchType[iden.signature.returns[paramIndex]]), isReturns: true}; - } - } - return null; - } - - let iden; - let indexOffset = 0; - let dynamicCommand; - if (name === 'queue') { - indexOffset = 2; - if (paramIndex < indexOffset) { - iden = identifierCache.get(name, matchType.COMMAND); - indexOffset = 0; - } else { - const queueName = getWordAtIndex(context.words, identifierName.end + 2); - iden = (queueName) ? identifierCache.get(queueName.value, matchType.QUEUE) : null; - dynamicCommand = name; - } - } else if (name === 'longqueue') { - indexOffset = 3; - if (paramIndex < indexOffset) { - iden = identifierCache.get(name, matchType.COMMAND); - indexOffset = 0; - } else { - const queueName = getWordAtIndex(context.words, identifierName.end + 2); - iden = (queueName) ? identifierCache.get(queueName.value, matchType.QUEUE) : null; - dynamicCommand = name; - } - } else if (prev === '@') { - iden = identifierCache.get(name, matchType.LABEL); - } else if (prev === '~') { - iden = identifierCache.get(name, matchType.PROC); - } else { - iden = identifierCache.get(name, matchType.COMMAND); - } - if (!iden) { - return null; - } - const response = {identifier: iden, index: paramIndex, isReturns: false, dynamicCommand: dynamicCommand}; - if (iden.signature && iden.signature.params.length > (paramIndex - indexOffset)) { - response.match = reference(matchType[iden.signature.params[(paramIndex - indexOffset)].matchTypeId]); - } - return response; -} - -// Determines if we are inside of an identifiers parenthesis, and returns which param index it is if we are -// Scans the characters from the cursor index to the begining of the code -function parseForIdentifierNameAndParamIndex(lineText, index, words) { - const init = initializeString(lineText, index); - lineText = init.interpolatedText || lineText; - let isInString = init.isInString; - let isInInterpolated = 0; - let isInParams = 0; - let paramIndex = 0; - for (let i = index; i >= 0; i--) { - const char = lineText.charAt(i); - - // Handle interpolated code inside of strings, and nested interpolated code - if (char === '>') isInInterpolated++; - if (isInInterpolated > 0) { - if (char === '<') isInInterpolated--; - continue; - } - - // Handle strings and escaped quotes within strings - if (isInString) { - if (char === '"' && i > 0 && lineText.charAt(i - 1) !== '\\') isInString = false; - continue; - } - else if (char === '"' && i > 0 && lineText.charAt(i - 1) !== '\\') { - isInString = true; - continue; - } - - // Handle nested parenthesis - if (char === ')') isInParams++; - if (isInParams > 0) { - if (char === '(') isInParams--; - continue; - } - - // === Code below is only reached when not inside a string, interpolated code, or nested params === - - // Increase param index when a comma is found - if (char === ',') { - paramIndex++; - } - // Reached the end of interpolated code without finding a match, exit early with null response - if (char === '<') { - return {identifierName: null, paramIndex: null}; - } - // Found an opening parenthesis which marks the end of our search, return the previous word (the identifier name) - if (char === '(') { - return {identifierName: getWordAtIndex(words, i - 2), paramIndex: paramIndex}; - } - } - return {identifierName: null, paramIndex: null}; -} - -// Determines if we are currently inside of a string, and if we are inside of interpolated code -// Scans the characters from the cursor index to the end of the line of code -function initializeString(lineText, index) { - let quoteCount = 0; - let interpolatedCount = 0; - for (let i = index; i < lineText.length; i++) { - if (lineText.charAt(i) === '"' && i > 0 && lineText.charAt(i - 1) !== '\\') { - quoteCount++; - } - if (lineText.charAt(i) === '>') { - if (interpolatedCount === 0) { - return { interpolatedText: lineText.substring(0, i - 1), isInString: quoteCount % 2 === 1 }; - } - interpolatedCount--; - } - if (lineText.charAt(i) === '<') { - interpolatedCount++; - } - } - return { isInString: quoteCount % 2 === 1 }; -} - -module.exports = { parametersMatcher, getParamsMatch }; diff --git a/client/matching/matchers/prevCharMatcher.js b/client/matching/matchers/prevCharMatcher.js deleted file mode 100644 index 5f36424..0000000 --- a/client/matching/matchers/prevCharMatcher.js +++ /dev/null @@ -1,17 +0,0 @@ -const matchType = require("../matchType"); -const { reference, declaration } = require("../../utils/matchUtils"); - -/** - * Looks for matches based on the previous character, such as ~WORD indicates a proc reference - */ -function prevCharMatcher(context) { - switch (context.prevChar) { - case '^': return (context.file.type === "constant") ? declaration(matchType.CONSTANT) : reference(matchType.CONSTANT); - case '%': return reference(matchType.GLOBAL_VAR); - case '@': return (context.nextChar === '@') ? null : reference(matchType.LABEL); - case '~': return reference(matchType.PROC); - case ',': return (context.prevWord.value === "p") ? reference(matchType.MESANIM) : null; - } -} - -module.exports = prevCharMatcher; diff --git a/client/matching/matchers/regexWordMatcher.js b/client/matching/matchers/regexWordMatcher.js deleted file mode 100644 index 6a65534..0000000 --- a/client/matching/matchers/regexWordMatcher.js +++ /dev/null @@ -1,21 +0,0 @@ -const { COLOR, COORD, NUMBER } = require("../../enum/regex"); -const matchType = require("../matchType"); -const { reference } = require("../../utils/matchUtils"); - -/** - * Looks for matches with direct word regex checks, such as for coordinates - */ -function regexWordMatcher(context) { - const word = context.word.value; - if (COORD.test(word)) { - return reference(matchType.COORDINATES); - } - if (COLOR.test(word)) { - return reference(matchType.COLOR); - } - if (NUMBER.test(word)) { - return reference(matchType.NUMBER); - } -} - -module.exports = regexWordMatcher; diff --git a/client/matching/matchers/switchCaseMatcher.js b/client/matching/matchers/switchCaseMatcher.js deleted file mode 100644 index 2b456c3..0000000 --- a/client/matching/matchers/switchCaseMatcher.js +++ /dev/null @@ -1,17 +0,0 @@ -const { SWITCH_CASE } = require("../../enum/regex"); -const matchType = require("../matchType"); -const { reference } = require("../../utils/matchUtils"); -const switchStmtLinesCache = require("../../cache/switchStmtLinesCache"); - -/** - * Looks for matches in case statements - */ -function switchCaseMatcher(context) { - if (context.file.type === 'rs2' && context.word.index > 0 && context.word.value !== 'default' && - SWITCH_CASE.test(context.line.text) && context.lineIndex < context.line.text.indexOf(' :')) { - const matchTypeId = switchStmtLinesCache.get(context.line.number, context.uri); - return matchTypeId ? reference(matchType[matchTypeId]) : matchType.UNKNOWN; - } -} - -module.exports = switchCaseMatcher; diff --git a/client/matching/matchers/triggerMatcher.js b/client/matching/matchers/triggerMatcher.js deleted file mode 100644 index 769338d..0000000 --- a/client/matching/matchers/triggerMatcher.js +++ /dev/null @@ -1,27 +0,0 @@ -const { TRIGGER_LINE } = require("../../enum/regex"); -const matchType = require("../matchType"); -const triggers = require("../../resource/triggers"); -const { reference, declaration } = require("../../utils/matchUtils"); - -/** - * Looks for matches with known runescript triggers, see triggers.js - */ -function triggerMatcher(context) { - if (context.file.type !== 'rs2') { - return null; - } - if (TRIGGER_LINE.test(context.line.text) && context.word.index <= 1) { - const trigger = triggers[context.words[0].value.toLowerCase()]; - if (trigger) { - if (context.word.index === 0) { - return reference(matchType.TRIGGER, {triggerName: context.words[1].value}); - } - if (context.word.value.charAt(0) === '_') { - return reference(matchType.CATEGORY, {matchId: trigger.match.id, categoryName: context.word.value.substring(1)}); - } - return trigger.declaration ? declaration(trigger.match) : reference(trigger.match); - } - } -} - -module.exports = triggerMatcher; diff --git a/client/provider/color24Provider.js b/client/provider/color24Provider.js deleted file mode 100644 index 8b80bbc..0000000 --- a/client/provider/color24Provider.js +++ /dev/null @@ -1,41 +0,0 @@ -const vscode = require('vscode'); -const { COLOR24 } = require('../enum/regex'); - -const color24Provider = { - provideColorPresentations(color, context, token) { - const r = Math.round(color.red * 255); - const g = Math.round(color.green * 255); - const b = Math.round(color.blue * 255); - const rgb = (r << 16) | (g << 8) | b; - - return [ - { - label: 'Color Picker', - textEdit: new vscode.TextEdit(context.range, '0x' + rgb.toString(16).toUpperCase().padStart(6, '0')) - } - ]; - }, - - provideDocumentColors(document) { - const text = document.getText(); - let match; - - const matches = []; - while (match = COLOR24.exec(text)) { - const rgb = parseInt(match[2], 16); - - const r = (rgb >> 16) & 0xFF; - const g = (rgb >> 8) & 0xFF; - const b = rgb & 0xFF; - - matches.push({ - color: new vscode.Color(r / 255, g / 255, b / 255, 1), - range: new vscode.Range(document.positionAt(match.index + match[1].length + 1), document.positionAt(match.index + match[1].length + match[2].length + 1)) - }); - } - - return matches; - } -}; - -module.exports = color24Provider; diff --git a/client/provider/completionProvider.js b/client/provider/completionProvider.js deleted file mode 100644 index 439b0d2..0000000 --- a/client/provider/completionProvider.js +++ /dev/null @@ -1,132 +0,0 @@ -const vscode = require('vscode'); -const activeFileCache = require('../cache/activeFileCache'); -const completionCache = require('../cache/completionCache'); -const matchType = require('../matching/matchType'); -const { matchWord } = require('../matching/matchWord'); -const activeCursorCache = require('../cache/activeCursorCache'); -const runescriptTrigger = require('../resource/triggers'); - -const triggers = ['$', '^', '%', '~', '@', '`', '>']; -const autoTriggeredTypeIds = [ - matchType.CONSTANT.id, - matchType.GLOBAL_VAR.id, - matchType.LOCAL_VAR.id, - matchType.PROC.id, - matchType.LABEL.id -]; - -const provider = { - provideCompletionItems(document, position, cancellationToken, context) { - if (context.triggerKind === 1) { - if (context.triggerCharacter === '`' && position.character > 1 && - document.lineAt(position.line).text.charAt(position.character - 2) === '`') { - return searchForMatchType(document, position, true); - } - return invoke(document, position, position.character - 1, ''); - } - const wordRange = document.getWordRangeAtPosition(position); - const word = (!wordRange) ? '' : document.getText(wordRange); - const triggerIndex = (!wordRange) ? position.character - 1 : wordRange.start.character - 1; - return invoke(document, position, triggerIndex, word); - } -} - -function invoke(document, position, triggerIndex, word) { - switch (document.lineAt(position.line).text.charAt(triggerIndex)) { - case '$': return completeLocalVar(position); - case '`': return completionTypeSelector(position); - case '>': return completionByType(document, position, triggerIndex, word); - case '^': return completionByTrigger(word, matchType.CONSTANT.id); - case '%': return completionByTrigger(word, matchType.GLOBAL_VAR.id); - case '~': return completionByTrigger(word, matchType.PROC.id); - case '@': return completionByTrigger(word, matchType.LABEL.id); - default: return searchForMatchType(document, position); - } -} - -function completeLocalVar(position) { - const completionItems = []; - const completionKind = getCompletionItemKind(matchType.LOCAL_VAR.id); - const scriptData = activeFileCache.getScriptData(position.line); - if (scriptData) { - Object.keys(scriptData.variables).forEach(varName => { - const localVar = scriptData.variables[varName]; - const range = localVar.declaration.range; - if (position.line > range.start.line || (position.line === range.start.line && position.character > range.end.character)) { - const item = new vscode.CompletionItem(varName, completionKind); - item.range = new vscode.Range(position.translate(0, -1), position); - item.detail = localVar.parameter ? `${localVar.type} (param)` : localVar.type; - completionItems.push(item); - } - }); - } - return completionItems; -} - -function completionByTrigger(prefix, matchTypeId, additionalTextEdits) { - let identifierNames; - if (matchTypeId === matchType.TRIGGER.id) { - identifierNames = Object.keys(runescriptTrigger); - } else { - identifierNames = completionCache.getAllWithPrefix(prefix, matchTypeId); - } - if (!identifierNames) { - return null; - } - const completionKind = getCompletionItemKind(matchTypeId); - const completionItems = []; - identifierNames.forEach(identifierName => { - const item = new vscode.CompletionItem(identifierName, completionKind); - item.detail = matchTypeId.toLowerCase(); - if (additionalTextEdits) item.additionalTextEdits = additionalTextEdits; - completionItems.push(item); - }); - return completionItems; -} - -function completionTypeSelector(position) { - const completionItems = completionCache.getTypes().filter(type => !autoTriggeredTypeIds.includes(type)).map(type => { - const item = new vscode.CompletionItem(`${type}>`, vscode.CompletionItemKind.Enum); - item.additionalTextEdits = [vscode.TextEdit.delete(new vscode.Range(position.translate(0, -1), position))]; - item.command = { command: 'editor.action.triggerSuggest' }; - return item; - }); - return completionItems; -} - -function completionByType(document, position, triggerIndex, word) { - prevWordRange = document.getWordRangeAtPosition(new vscode.Position(position.line, triggerIndex)); - if (!prevWordRange) { - return null; - } - const matchTypeId = document.getText(prevWordRange); - const additionalTextEdits = [vscode.TextEdit.delete(new vscode.Range(prevWordRange.start, prevWordRange.end.translate(0, 1)))]; - return completionByTrigger(word, matchTypeId, additionalTextEdits); -} - -function searchForMatchType(document, position, fromTrigger = false) { - const triggerOffset = fromTrigger ? 2 : 0; - let matchTypeId = fromTrigger ? false : activeCursorCache.get(document, position); - if (!matchTypeId) { - let str = document.lineAt(position.line).text; - str = str.substring(0, position.character - triggerOffset) + 'temp' + str.substring(position.character); - const match = matchWord(str, position.line, document.uri, position.character); - matchTypeId = (match) ? match.match.id : matchType.COMMAND.id; - } - const additionalTextEdits = [vscode.TextEdit.delete(new vscode.Range(position.translate(0, -triggerOffset), position))]; - return completionByTrigger('', matchTypeId, additionalTextEdits); -} - -function getCompletionItemKind(matchTypeId) { - switch (matchTypeId) { - case matchType.CONSTANT.id: return vscode.CompletionItemKind.Constant; - case matchType.LOCAL_VAR.id: - case matchType.GLOBAL_VAR.id: return vscode.CompletionItemKind.Variable; - case matchType.COMMAND.id: - case matchType.PROC.id: - case matchType.LABEL.id: return vscode.CompletionItemKind.Function; - default: return vscode.CompletionItemKind.Text; - } -} - -module.exports = {triggers, provider}; diff --git a/client/provider/configHelpProvider.js b/client/provider/configHelpProvider.js deleted file mode 100644 index 0c1a5c9..0000000 --- a/client/provider/configHelpProvider.js +++ /dev/null @@ -1,56 +0,0 @@ -const vscode = require('vscode'); -const { getBaseContext, getWordAtIndex } = require('../utils/matchUtils'); -const { getConfigLineMatch } = require('../matching/matchers/configMatcher'); -const dataTypeToMatchId = require('../resource/dataTypeToMatchId'); -const { contains } = require('../cache/completionCache'); -const matchType = require('../matching/matchType'); -const activeCursorCache = require('../cache/activeCursorCache'); - -const metadata = { - triggerCharacters: ['=', ','], - retriggerCharacters: [','] -} - -const provider = { - provideSignatureHelp(document, position) { - let str = document.lineAt(position.line).text; - str = str.substring(0, position.character) + 'temp' + str.substring(position.character); - const matchContext = getBaseContext(str, position.line, document.uri); - matchContext.lineIndex = position.character + 1; - matchContext.word = getWordAtIndex(matchContext.words, matchContext.lineIndex); - const config = getConfigLineMatch(matchContext); - if (!config) { - return null; - } - - // Build the signature info - const signatureInfo = new vscode.SignatureInformation(`${config.key}=${config.params.join(',')}`); - let index = config.key.length + 1; // Starting index of params - config.params.forEach(param => { - // use range instead of param name due to possible duplicates - signatureInfo.parameters.push(new vscode.ParameterInformation([index, index + param.length])); - index += param.length + 1; - }); - signatureInfo.activeParameter = config.index; - - // Build the signature help - const signatureHelp = new vscode.SignatureHelp(); - signatureHelp.signatures.push(signatureInfo); - signatureHelp.activeSignature = 0; - invokeCompletionItems(dataTypeToMatchId(config.params[config.index]), document, position); - return signatureHelp; - } -} - -function invokeCompletionItems(matchTypeId, document, position) { - activeCursorCache.set(matchTypeId, document, position); - if (matchTypeId !== matchType.UNKNOWN.id) { - const word = document.getText(document.getWordRangeAtPosition(position)); - if (contains(word, matchTypeId)) { - return; - } - vscode.commands.executeCommand('editor.action.triggerSuggest'); - } -} - -module.exports = { provider, metadata }; diff --git a/client/provider/gotoDefinition.js b/client/provider/gotoDefinition.js deleted file mode 100644 index 4b243ae..0000000 --- a/client/provider/gotoDefinition.js +++ /dev/null @@ -1,39 +0,0 @@ -const vscode = require('vscode'); -const identifierCache = require("../cache/identifierCache"); -const matchType = require('../matching/matchType'); -const { matchWordFromDocument } = require('../matching/matchWord'); -const activeFileCache = require('../cache/activeFileCache'); - -const gotoDefinitionProvider = { - async provideDefinition(document, position) { - // Get a match for the current word, and ignore noop or hover only tagged matches - const { match, word } = matchWordFromDocument(document, position) - if (!match || match.noop || match.isHoverOnly) { - return null; - } - - // If we are already on a declaration, there is nowhere to goto. Returning current location - // indicates to vscode that we instead want to try doing "find references" - if (match.declaration || match.referenceOnly) { - return new vscode.Location(document.uri, position); - } - - // Search for the identifier and its declaration location, and goto it if found - if (match.id === matchType.LOCAL_VAR.id) { - return gotoLocalVar(position, word); - } - return gotoDefinition(word, match); - } -} - -const gotoLocalVar = (position, word) => { - const scriptData = activeFileCache.getScriptData(position.line); - return (scriptData) ? (scriptData.variables[`$${word}`] || {declaration: null}).declaration : null; -} - -const gotoDefinition = async (word, match) => { - const definition = identifierCache.get(word, match); - return (definition) ? definition.declaration : null; -} - -module.exports = gotoDefinitionProvider; diff --git a/client/provider/hoverProvider.js b/client/provider/hoverProvider.js deleted file mode 100644 index eeaf2a7..0000000 --- a/client/provider/hoverProvider.js +++ /dev/null @@ -1,80 +0,0 @@ -const vscode = require('vscode'); -const matchType = require('../matching/matchType'); -const identifierCache = require('../cache/identifierCache'); -const activeFileCache = require('../cache/activeFileCache'); -const identifierFactory = require('../resource/identifierFactory'); -const { matchWordFromDocument } = require('../matching/matchWord'); -const { resolve } = require('../resource/hoverConfigResolver'); -const { DECLARATION_HOVER_ITEMS, REFERENCE_HOVER_ITEMS } = require('../enum/hoverConfigOptions'); -const { markdownBase, appendTitle, appendInfo, appendValue, appendSignature, - appendCodeBlock, expectedIdentifierMessage } = require('../utils/markdownUtils'); - -const hoverProvider = function(context) { - return { - async provideHover(document, position) { - // Find a match for the word user is hovering over, and ignore noop tagged matches - const { word, match, context: matchContext } = matchWordFromDocument(document, position); - if (!match || match.noop) { - return null; - } - - // Setup the hover text markdown content object - const markdown = markdownBase(context); - - // Local vars are handled differently than the rest - if (match.id === matchType.LOCAL_VAR.id) { - appendLocalVarHoverText(position, word, match, markdown); - return new vscode.Hover(markdown); - } - - // If no config found, or no items to display then exit early - const hoverDisplayItems = (match.declaration) ? resolve(DECLARATION_HOVER_ITEMS, match) : resolve(REFERENCE_HOVER_ITEMS, match); - if (hoverDisplayItems.length === 0) { - return null; - } - - // Get/Build identifier object for the match found - const identifier = getIdentifier(word, match, document, position); - - // No identifier or hideDisplay property is set, then there is nothing to display - if (!identifier || identifier.hideDisplay) { - return null; - } - - // Match type is a reference, but it has no declaration => display a warning message "expected identifier" - if (!match.declaration && !match.referenceOnly && !identifier.declaration) { - expectedIdentifierMessage(word, match, markdown); - return new vscode.Hover(markdown); - } - - // Append the registered hoverDisplayItems defined in the matchType for the identifier - appendTitle(identifier.name, identifier.fileType, identifier.matchId, markdown, identifier.id, matchContext.cert); - appendInfo(identifier, hoverDisplayItems, markdown); - appendValue(identifier, hoverDisplayItems, markdown); - appendSignature(identifier, hoverDisplayItems, markdown); - appendCodeBlock(identifier, hoverDisplayItems, markdown); - return new vscode.Hover(markdown); - } - }; -} - -function appendLocalVarHoverText(position, word, match, markdown) { - const scriptData = activeFileCache.getScriptData(position.line); - if (scriptData) { - const variable = scriptData.variables[`$${word}`]; - if (variable) { - appendTitle(word, 'rs2', match.id, markdown); - markdown.appendCodeblock(variable.parameter ? `${variable.type} $${word} (script parameter)` : `${variable.type} $${word}`, 'runescript'); - } else { - expectedIdentifierMessage(word, match, markdown); - } - } -} - -function getIdentifier(word, match, document, position) { - return (match.hoverOnly) ? - identifierFactory.build(word, match, new vscode.Location(document.uri, position)) : - identifierCache.get(word, match, match.declaration ? document.uri : null); -} - -module.exports = hoverProvider; diff --git a/client/provider/recolorProvider.js b/client/provider/recolorProvider.js deleted file mode 100644 index 916e543..0000000 --- a/client/provider/recolorProvider.js +++ /dev/null @@ -1,41 +0,0 @@ -const vscode = require('vscode'); -const { RECOLOR } = require('../enum/regex'); - -const recolProvider = { - provideColorPresentations(color, context, token) { - const r = Math.round(color.red * 31); - const g = Math.round(color.green * 31); - const b = Math.round(color.blue * 31); - const rgb = (r << 10) | (g << 5) | b; - - return [ - { - label: 'Model Recolor', - textEdit: new vscode.TextEdit(context.range, rgb.toString()) - } - ]; - }, - - provideDocumentColors(document) { - const text = document.getText(); - let match; - - const matches = []; - while (match = RECOLOR.exec(text)) { - const rgb = parseInt(match[2]); - - const r = (rgb >> 10) & 0x1f; - const g = (rgb >> 5) & 0x1f; - const b = rgb & 0x1f; - - matches.push({ - color: new vscode.Color(r / 31, g / 31, b / 31, 1), - range: new vscode.Range(document.positionAt(match.index + match[1].length + 1), document.positionAt(match.index + match[1].length + match[2].length + 1)) - }); - } - - return matches; - } -}; - -module.exports = recolProvider; diff --git a/client/provider/referenceProvider.js b/client/provider/referenceProvider.js deleted file mode 100644 index 0a4eea0..0000000 --- a/client/provider/referenceProvider.js +++ /dev/null @@ -1,46 +0,0 @@ -const vscode = require('vscode'); -const { matchWordFromDocument } = require('../matching/matchWord'); -const identifierCache = require('../cache/identifierCache'); -const cacheUtils = require('../utils/cacheUtils'); -const matchType = require('../matching/matchType'); -const activeFileCache = require('../cache/activeFileCache'); - -const referenceProvider = { - async provideReferences(document, position) { - // Find a match for the current word, and ignore noop or hoverOnly tagged matches - const { match, word } = matchWordFromDocument(document, position) - if (!match || match.noop || match.isHoverOnly) { - return null; - } - - // Use activeFileCache to get references of variables for active script block - if (match.id === matchType.LOCAL_VAR.id) { - const scriptData = activeFileCache.getScriptData(position.line); - if (scriptData) { - return (scriptData.variables[`$${word}`] || {references: []}).references; - } - return null; - } - - // Get the identifier from the cache - const identifier = identifierCache.get(word, match); - if (!identifier || !identifier.references) { - return null; - } - - // Decode all the references for the identifier into an array of vscode Location objects - const referenceLocations = []; - Object.keys(identifier.references).forEach(fileKey => { - const uri = vscode.Uri.file(fileKey); - identifier.references[fileKey].forEach(encodedReference => - referenceLocations.push(cacheUtils.decodeReferenceToLocation(uri, encodedReference))); - }); - // If there is only one reference and its the declaration, return null as theres no other references to show - if (match.declaration && referenceLocations.length === 1) { - return null; - } - return referenceLocations; - } -} - -module.exports = referenceProvider; diff --git a/client/provider/renameProvider.js b/client/provider/renameProvider.js deleted file mode 100644 index bb82877..0000000 --- a/client/provider/renameProvider.js +++ /dev/null @@ -1,111 +0,0 @@ -const vscode = require('vscode'); -const identifierCache = require('../cache/identifierCache'); -const { matchWordFromDocument } = require('../matching/matchWord'); -const cacheUtils = require('../utils/cacheUtils'); -const matchType = require('../matching/matchType'); -const activeFileCache = require('../cache/activeFileCache'); -const { LOC_MODEL } = require('../enum/regex'); - -const renameProvider = { - prepareRename(document, position) { - const matchedWord = matchWordFromDocument(document, position); - if (!matchedWord) { - throw new Error("Cannot rename"); - } - const { match, word } = matchedWord; - if (!match.allowRename || match.noop) { - throw new Error(`${match.id} renaming not supported`); - } - if (match.id !== matchType.LOCAL_VAR.id) { - const identifier = identifierCache.get(word, match); - if (!identifier) { - return new Error('Cannot find any references to rename'); - } - } - }, - - provideRenameEdits(document, position, newName) { - const { word, match, context } = matchWordFromDocument(document, position); - - if (match.id === matchType.LOCAL_VAR.id) { - return renameLocalVariableReferences(position, word, newName); - } - - const validationError = validate(match, newName); - if (validationError) { - vscode.window.showErrorMessage('Rename failed: ' + validationError); - return new vscode.WorkspaceEdit(); - } - - const adjustedNewName = adjustNewName(context, newName); - const identifier = identifierCache.get(word, match); - renameFiles(match, word, adjustedNewName); - return renameReferences(identifier, word, adjustedNewName); - } -} - -// Use activeFileCache to get references of variables for active script block -function renameLocalVariableReferences(position, word, newName) { - const renameWorkspaceEdits = new vscode.WorkspaceEdit(); - const scriptData = activeFileCache.getScriptData(position.line); - if (scriptData) { - (scriptData.variables[`$${word}`] || { references: [] }).references.forEach(location => { - renameWorkspaceEdits.replace(location.uri, location.range, `$${newName}`); - }); - } - return renameWorkspaceEdits; -} - -function validate(match, newName) { - if (match.id === matchType.MODEL.id && LOC_MODEL.test(newName)) { - return `Do not include final _X suffix in model renames`; - } -} - -// Decode all the references for the identifier into an array of vscode ranges, -// then use that to rename all of the references to the newName -function renameReferences(identifier, oldName, newName) { - const renameWorkspaceEdits = new vscode.WorkspaceEdit(); - if (identifier.references) { - const wordLength = oldName.length - oldName.indexOf(':') - 1; - Object.keys(identifier.references).forEach(fileKey => { - const uri = vscode.Uri.file(fileKey); - identifier.references[fileKey].forEach(encodedReference => { - const range = cacheUtils.decodeReferenceToRange(wordLength, encodedReference); - renameWorkspaceEdits.replace(uri, range, newName); - }); - }); - } - return renameWorkspaceEdits; -} - -function adjustNewName(context, newName) { - // Strip the cert_ and the _ prefix on objs or categories - if (context.originalPrefix && newName.startsWith(context.originalPrefix)) { - newName = newName.substring(context.originalPrefix.length); - } - // Strip the _0 (or others) on loc models - if (context.originalSuffix && newName.endsWith(context.originalSuffix)) { - newName = newName.slice(0, -2); - } - // Strip the left side of identifier names with colons in them - if (newName.indexOf(':') > -1) { - newName = newName.substring(newName.indexOf(':') + 1); - } - return newName; -} - -async function renameFiles(match, oldName, newName) { - if (match.renameFile && Array.isArray(match.fileTypes) && match.fileTypes.length > 0) { - const fileSearch = match.id === matchType.MODEL.id ? `**/${oldName}_*.${match.fileTypes[0]}` : `**/${oldName}.${match.fileTypes[0]}`; - const files = await vscode.workspace.findFiles(fileSearch) || []; - for (const oldUri of files) { - const suffix = match.id === matchType.MODEL.id ? oldUri.path.slice(-6, -4) : ''; - const newFileName = suffix ? `${newName}${suffix}.${match.fileTypes[0]}` : `${newName}.${match.fileTypes[0]}`; - const newUri = vscode.Uri.joinPath(oldUri.with({ path: oldUri.path.replace(/\/[^/]+$/, '') }), newFileName); - vscode.workspace.fs.rename(oldUri, newUri); - } - } -} - -module.exports = renameProvider; diff --git a/client/provider/signatureHelpProvider.js b/client/provider/signatureHelpProvider.js deleted file mode 100644 index 4667323..0000000 --- a/client/provider/signatureHelpProvider.js +++ /dev/null @@ -1,134 +0,0 @@ -const vscode = require('vscode'); -const { getBaseContext } = require('../utils/matchUtils'); -const matchType = require('../matching/matchType'); -const { get } = require('../cache/identifierCache'); -const activeCursorCache = require('../cache/activeCursorCache'); -const dataTypeToMatchId = require('../resource/dataTypeToMatchId'); -const runescriptTrigger = require('../resource/triggers'); -const { contains } = require('../cache/completionCache'); -const { getParamsMatch } = require('../matching/matchers/parametersMatcher'); - -const metadata = { - triggerCharacters: ['(', ',', '['], - retriggerCharacters: [','] -} - -const provider = { - provideSignatureHelp(document, position) { - const signatureHelp = getScriptTriggerHelp(document, position); - if (signatureHelp) { - return signatureHelp; - } - return getParametersHelp(document, position); - } -} - -function getScriptTriggerHelp(document, position) { - let matchTypeId = matchType.UNKNOWN.id; - let signatureInfo; - const str = document.lineAt(position.line).text; - if (str.charAt(0) === '[') { - if (position.character > str.indexOf(']')) { - return null; - } - const split = str.split(','); - if (split.length > 1) { - const triggerName = split[0].substring(1); - const trigger = runescriptTrigger[triggerName]; - if (trigger) { - matchTypeId = trigger.declaration ? matchType.UNKNOWN.id : trigger.match.id; - const matchLabel = matchTypeId === matchType.UNKNOWN.id ? `script_name` : matchTypeId.toLowerCase(); - signatureInfo = new vscode.SignatureInformation(`script [${triggerName},${matchLabel}]`); - signatureInfo.parameters.push(new vscode.ParameterInformation(triggerName)); - signatureInfo.parameters.push(new vscode.ParameterInformation(matchLabel)); - signatureInfo.activeParameter = 1; - } - } else { - matchTypeId = matchType.TRIGGER.id; - signatureInfo = new vscode.SignatureInformation('script [trigger,value]'); - signatureInfo.parameters.push(new vscode.ParameterInformation('trigger')); - signatureInfo.parameters.push(new vscode.ParameterInformation('value')); - signatureInfo.activeParameter = 0; - } - } - if (signatureInfo) { - const signatureHelp = new vscode.SignatureHelp(); - signatureHelp.signatures.push(signatureInfo); - signatureHelp.activeSignature = 0; - invokeCompletionItems(matchTypeId, document, position); - return signatureHelp; - } - return null; -} - -function getParametersHelp(document, position) { - let str = document.lineAt(position.line).text; - str = str.substring(0, position.character) + 'temp' + str.substring(position.character); - const matchContext = getBaseContext(str, position.line, document.uri); - matchContext.lineIndex = position.character + 1; - var paramIden = getParamsMatch(matchContext); - if (!paramIden) { - return null; - } - if (!paramIden.isReturns && paramIden.identifier.signature.paramsText.length === 0) { - return displayMessage(`${paramIden.identifier.matchId} ${paramIden.identifier.name} has no parameters, remove the parenthesis`); - } - - // For things like queues, manually handled - todo try to find better way - paramIden = handleDynamicParams(paramIden); - - // Build the signature info - const signature = paramIden.identifier.signature; - const params = (paramIden.isReturns) ? signature.returnsText : signature.paramsText; - const label = (paramIden.isReturns) ? `return (${params})` : `${paramIden.identifier.name}(${params})${signature.returnsText.length > 0 ? `: ${signature.returnsText}` : ''}`; - const signatureInfo = new vscode.SignatureInformation(label); - params.split(',').forEach(param => signatureInfo.parameters.push(new vscode.ParameterInformation(param.trim()))); - signatureInfo.activeParameter = paramIden.index; - - // Build the signature help - const signatureHelp = new vscode.SignatureHelp(); - signatureHelp.signatures.push(signatureInfo); - signatureHelp.activeSignature = 0; - - // Trigger autocomplete suggestions - invokeCompletionItems(dataTypeToMatchId(signatureInfo.parameters[paramIden.index].label.split(' ')[0]), document, position); - - return signatureHelp; -} - -function handleDynamicParams(paramIdentifier) { - if (paramIdentifier.dynamicCommand && paramIdentifier.identifier.signature.paramsText.length > 0) { - const name = paramIdentifier.identifier.name; - const command = get(paramIdentifier.dynamicCommand, matchType.COMMAND); - if (command && command.signature.paramsText.length > 0) { - let paramsText = `${command.signature.paramsText}, ${paramIdentifier.identifier.signature.paramsText}`; - paramsText = `${name}${paramsText.substring(paramsText.indexOf(','))}`; - return {index: paramIdentifier.index, identifier: {name: paramIdentifier.dynamicCommand, signature: {paramsText: paramsText, returnsText: ''}}}; - } - } - return paramIdentifier; -} - -function displayMessage(message) { - const signatureInfo = new vscode.SignatureInformation(message); - const signatureHelp = new vscode.SignatureHelp(); - signatureHelp.signatures.push(signatureInfo); - signatureHelp.activeSignature = 0; - return signatureHelp; -} - -function invokeCompletionItems(matchTypeId, document, position) { - activeCursorCache.set(matchTypeId, document, position); - if (matchTypeId !== matchType.UNKNOWN.id) { - const word = document.getText(document.getWordRangeAtPosition(position)); - if (matchType.TRIGGER.id === matchTypeId && runescriptTrigger[word]) { - return; - } - if (contains(word, matchTypeId)) { - return; - } - vscode.commands.executeCommand('editor.action.triggerSuggest'); - } -} - -module.exports = { provider, metadata }; diff --git a/client/provider/vscodeCommands.js b/client/provider/vscodeCommands.js deleted file mode 100644 index ec39cb1..0000000 --- a/client/provider/vscodeCommands.js +++ /dev/null @@ -1,17 +0,0 @@ -const vscode = require('vscode'); -const cacheManager = require('../cache/cacheManager'); - -const commands = { - rebuildCache: { - id: 'RuneScriptLanguage.rebuildCache', - command: () => { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: "Runescript Extension: Building cache / Indexing files...", - cancellable: false - }, cacheManager.rebuildAll); - } - } -}; - -module.exports = commands; diff --git a/client/resource/configKeys.js b/client/resource/configKeys.js deleted file mode 100644 index 1b7200d..0000000 --- a/client/resource/configKeys.js +++ /dev/null @@ -1,44 +0,0 @@ -// === STATIC CONFIG KEY MATCHES === -const configKeys = { - walkanim: { params: [param('seq'), param('seq'), param('seq'), param('seq')] }, - multivar: { params: [param('var')] }, - multiloc: { params: [param('int'), param('loc')] }, - multinpc: { params: [param('int'), param('npc')] }, - basevar: { params: [param('var')] }, - - category: { params: [param('category')] }, - huntmode: { params: [param('hunt')] }, - table: { params: [param('dbtable')] }, - column: { params: [param('dbcolumn', true)] }, -} - -// === REGEX CONFIG KEY MATCHES === -const regexConfigKeys = groupByFileType([ - { regex: /stock\d+/, params: [param('obj'), param('int'), param('int')], fileTypes: ["inv"] }, - { regex: /count\d+/, params: [param('obj'), param('int')], fileTypes: ["obj"] }, - { regex: /(model|head|womanwear|manwear|womanhead|manhead|activemodel)\d*/, params: [param('ob2')], fileTypes:['npc', 'loc', 'obj', 'spotanim', 'if', 'idk'] }, - { regex: /\w*anim\w*/, params: [param('seq')], fileTypes: ["loc", "npc", "if", "spotanim"] }, - { regex: /replaceheldleft|replaceheldright/, params: [param('obj')], fileTypes: ["seq"] }, -]); - -// === CONFIG KEYS THAT ARE HANDLED MANUALLY IN CONFIG_MATCHER === -const specialCaseKeys = ['val', 'param', 'data']; - -function param(type, declaration = false) { - return {typeId: type, declaration: declaration}; -} - -function groupByFileType(config) { - const result = new Map(); - for (const { regex, params, fileTypes } of config) { - for (const fileType of fileTypes) { - if (!result.has(fileType)) { - result.set(fileType, []); - } - result.get(fileType).push({ regex, params }); - } - } - return result; -} - -module.exports = { configKeys, regexConfigKeys, specialCaseKeys }; diff --git a/client/resource/dataTypeToMatchId.js b/client/resource/dataTypeToMatchId.js deleted file mode 100644 index ecc8427..0000000 --- a/client/resource/dataTypeToMatchId.js +++ /dev/null @@ -1,15 +0,0 @@ -const matchType = require("../matching/matchType"); - -const keywordToId = {}; - -Object.keys(matchType).forEach(matchTypeId => { - for (let keyword of (matchType[matchTypeId].types || [])) { - keywordToId[keyword] = matchTypeId; - } -}); - -function dataTypeToMatchId(keyword) { - return keywordToId[keyword] || matchType.UNKNOWN.id; -} - -module.exports = dataTypeToMatchId; diff --git a/client/resource/hoverConfigResolver.js b/client/resource/hoverConfigResolver.js deleted file mode 100644 index 02f9291..0000000 --- a/client/resource/hoverConfigResolver.js +++ /dev/null @@ -1,22 +0,0 @@ -const { DECLARATION_HOVER_ITEMS, REFERENCE_HOVER_ITEMS, LANGUAGE, BLOCK_SKIP_LINES, CONFIG_INCLUSIONS } = require("../enum/hoverConfigOptions"); - -const resolve = function(opt, match) { - const config = (!match.hoverConfig) ? {} : match.hoverConfig; - switch(opt) { - case DECLARATION_HOVER_ITEMS: return config[opt] || []; - case REFERENCE_HOVER_ITEMS: return config[opt] || []; - case LANGUAGE: return config[opt] || 'runescript'; - case BLOCK_SKIP_LINES: return (config[opt] !== undefined) ? match.hoverConfig[opt] : 1; - case CONFIG_INCLUSIONS: return config[opt] || null; - } -} - -const resolveAllHoverItems = function(match) { - const config = (!match.hoverConfig) ? {} : match.hoverConfig; - const displayItems = new Set(); - (config[DECLARATION_HOVER_ITEMS] || []).forEach(item => displayItems.add(item)); - (config[REFERENCE_HOVER_ITEMS] || []).forEach(item => displayItems.add(item)); - return displayItems; -} - -module.exports = { resolve, resolveAllHoverItems }; diff --git a/client/resource/identifierFactory.js b/client/resource/identifierFactory.js deleted file mode 100644 index 929a3ad..0000000 --- a/client/resource/identifierFactory.js +++ /dev/null @@ -1,135 +0,0 @@ -const dataTypeToMatchId = require('./dataTypeToMatchId'); -const hoverConfigResolver = require('./hoverConfigResolver'); -const { SIGNATURE, CODEBLOCK } = require('../enum/hoverDisplayItems'); -const { END_OF_BLOCK_LINE } = require('../enum/regex'); -const { LANGUAGE, BLOCK_SKIP_LINES, CONFIG_INCLUSIONS } = require('../enum/hoverConfigOptions'); -const matchType = require('../matching/matchType'); - -/** - * Builds an identifier object - * identifier = { - * name: String, - * matchId: matchTypeId, - * declaration: vscode.Location - * references: {filePath1: String[], filePath2: String[], ...} (String is encoded location value) - * fileType: String, - * language: String, - * info?: String, - * signature?: {params: {type: String, name: String, matchTypeId: String}[], returns: String, paramsText: String}, - * block?: String - * } - */ -function build(name, match, location, info = null, text = {lines: [], start: 0}) { - const identifier = { - name: name, - match: match, - declaration: location, - references: {}, - fileType: location ? location.uri.fsPath.split(/[#?]/)[0].split('.').pop().trim() : 'rs2', - language: hoverConfigResolver.resolve(LANGUAGE, match), - text: text - } - if (info) identifier.info = info; - addExtraData(identifier, match.extraData); - process(identifier); - cleanup(identifier); - return identifier; -} - -function buildRef(name, match) { - const identifier = { - name: name, - match: match, - references: {}, - fileType: (match.fileTypes || [])[0] || 'rs2', - language: hoverConfigResolver.resolve(LANGUAGE, match), - } - if (match.referenceOnly) { - addExtraData(identifier, match.extraData); - process(identifier); - } - cleanup(identifier); - return identifier; -} - -function process(identifier) { - // Process specififed display items - if (identifier.text) { - const hoverDisplayItems = hoverConfigResolver.resolveAllHoverItems(identifier.match); - for (const hoverDisplayItem of hoverDisplayItems) { - switch(hoverDisplayItem) { - case SIGNATURE: processSignature(identifier); break; - case CODEBLOCK: processCodeBlock(identifier); break; - } - } - } - - // Execute custom post processing for the identifier's matchType (if defined) - if (identifier.match.postProcessor) { - identifier.match.postProcessor(identifier); - } -} - -function cleanup(identifier) { - identifier.matchId = identifier.match.id; - delete identifier.match; - delete identifier.text; -} - -function processSignature(identifier) { - // Get first line of text, which should contain the data for parsing the signature - let line = identifier.text.lines[identifier.text.start]; - - // Parse input params - const params = []; - let openingIndex = line.indexOf('('); - let closingIndex = line.indexOf(')'); - if (openingIndex >= 0 && closingIndex >= 0 && ++openingIndex !== closingIndex) { - line.substring(openingIndex, closingIndex).split(',').forEach(param => { - if (param.startsWith(' ')) param = param.substring(1); - const split = param.split(' '); - if (split.length === 2) { - params.push({type: split[0], name: split[1], matchTypeId: dataTypeToMatchId(split[0])}); - } - }); - } - - // Parse response type - let returns = []; - let returnsText = ''; - line = line.substring(closingIndex + 1); - openingIndex = line.indexOf('('); - closingIndex = line.indexOf(')'); - if (openingIndex >= 0 && closingIndex >= 0 && ++openingIndex !== closingIndex) { - returnsText = line.substring(openingIndex, closingIndex); - returns = line.substring(openingIndex, closingIndex).split(',').map(item => dataTypeToMatchId(item.trim())); - } - - // Add signature to identifier - const paramsText = (params.length > 0) ? params.map(param => `${param.type} ${param.name}`).join(', ') : ''; - identifier.signature = {params: params, returns: returns, paramsText: paramsText, returnsText: returnsText}; -} - -function processCodeBlock(identifier) { - const lines = identifier.text.lines; - const startIndex = identifier.text.start + hoverConfigResolver.resolve(BLOCK_SKIP_LINES, identifier.match); - const configInclusionTags = hoverConfigResolver.resolve(CONFIG_INCLUSIONS, identifier.match); - let blockInclusionLines = []; - if (identifier.match.id === matchType.CONSTANT.id) blockInclusionLines.push(lines[startIndex]); - for (let i = startIndex; i < lines.length; i++) { - let currentLine = lines[i]; - if (END_OF_BLOCK_LINE.test(currentLine)) break; - if (currentLine.startsWith('//')) continue; - if (configInclusionTags && !configInclusionTags.some(inclusionTag => currentLine.startsWith(inclusionTag))) continue; - blockInclusionLines.push(currentLine); - } - identifier.block = blockInclusionLines.join('\n'); -} - -function addExtraData(identifier, extraData) { - if (!extraData) return; - if (!identifier.extraData) identifier.extraData = {}; - Object.keys(extraData).forEach(key => identifier.extraData[key] = extraData[key]); -} - -module.exports = { build, buildRef }; diff --git a/client/resource/postProcessors.js b/client/resource/postProcessors.js deleted file mode 100644 index 195434e..0000000 --- a/client/resource/postProcessors.js +++ /dev/null @@ -1,80 +0,0 @@ -const { END_OF_LINE } = require('../enum/regex'); -const matchConfigKeyInfo = require('../info/configKeyInfo'); -const matchTriggerInfo = require('../info/triggerInfo'); -const { getLineText } = require('../utils/stringUtils'); - -// Post processors are used for any additional post modification needed for a matchType, after an identifier has been built -// postProcessors must be a function which takes indentifier as an input, and directly modifies that identifier as necessary - -const coordPostProcessor = function(identifier) { - const coordinates = identifier.name.split('_'); - const xCoord = Number(coordinates[1] << 6) + Number(coordinates[3]); - const zCoord = Number(coordinates[2] << 6) + Number(coordinates[4]); - identifier.value = `Absolute coordinates: (${xCoord}, ${zCoord})`; -} - -const enumPostProcessor = function(identifier) { - const inputtypeLine = getLineText(identifier.block.substring(identifier.block.indexOf("inputtype="))); - const outputtypeLine = getLineText(identifier.block.substring(identifier.block.indexOf("outputtype="))); - identifier.extraData = {inputType: inputtypeLine.substring(10), outputType: outputtypeLine.substring(11)}; -} - -const dataTypePostProcessor = function(identifier) { - const index = identifier.block.indexOf("type="); - const dataType = (index < 0) ? 'int' : getLineText(identifier.block.substring(index)).substring(5); - identifier.extraData = {dataType: dataType}; -} - -const configKeyPostProcessor = function(identifier) { - const info = matchConfigKeyInfo(identifier.name, identifier.fileType); - info ? identifier.info = info.replaceAll('$TYPE', identifier.fileType) : identifier.hideDisplay = true; -} - -const triggerPostProcessor = function(identifier) { - const info = matchTriggerInfo(identifier.name, identifier.extraData.triggerName); - if (info) identifier.info = info; -} - -const categoryPostProcessor = function(identifier) { - const extraData = identifier.extraData; - if (extraData && extraData.matchId && extraData.categoryName) { - identifier.value = `This script applies to all ${identifier.extraData.matchId} with \`category=${identifier.extraData.categoryName}\``; - } -} - -const componentPostProcessor = function(identifier) { - const split = identifier.name.split(':'); - identifier.info = `A component of the ${split[0]} interface`; - identifier.name = split[1]; -} - -const rowPostProcessor = function(identifier) { - if (identifier.block) { - const tableName = (identifier.block.split('=') || ['', ''])[1]; - identifier.info = `A row in the ${tableName} table`; - delete identifier.block; - identifier.extraData = {table: tableName}; - } -} - -const columnPostProcessor = function(identifier) { - const split = identifier.name.split(':'); - identifier.info = `A column of the ${split[0]} table`; - identifier.name = split[1]; - - const exec = END_OF_LINE.exec(identifier.block); - if (!exec) return; - const types = identifier.block.substring(8 + identifier.name.length, exec.index).split(','); - identifier.extraData = {dataTypes: types}; - identifier.block = `Field types: ${types.join(', ')}`; -} - -const fileNamePostProcessor = function(identifier) { - identifier.info = `Refers to the file ${identifier.name}.${identifier.fileType}`; -} - -module.exports = { - coordPostProcessor, enumPostProcessor, dataTypePostProcessor, configKeyPostProcessor, - triggerPostProcessor, categoryPostProcessor, componentPostProcessor, columnPostProcessor, - fileNamePostProcessor, rowPostProcessor -}; diff --git a/client/resource/triggers.js b/client/resource/triggers.js deleted file mode 100644 index 1d72ad2..0000000 --- a/client/resource/triggers.js +++ /dev/null @@ -1,61 +0,0 @@ -const matchType = require("../matching/matchType"); - -const runescriptTrigger = { - proc: build(matchType.PROC, true), - label: build(matchType.LABEL, true), - queue: build(matchType.QUEUE, true), - softtimer: build(matchType.SOFTTIMER, true), - timer: build(matchType.TIMER, true), - ai_timer: build(matchType.NPC, false), - if_button: build(matchType.COMPONENT, false), - if_close: build(matchType.COMPONENT, false), - walktrigger: build(matchType.WALKTRIGGER, true), - ai_walktrigger: build(matchType.NPC, false), - debugproc: build(matchType.UNKNOWN, true), - login: build(matchType.UNKNOWN, true), - logout: build(matchType.UNKNOWN, true), - tutorial: build(matchType.UNKNOWN, true), - advancestat: build(matchType.STAT, false), - mapzone: build(matchType.UNKNOWN, true), - mapzoneexit: build(matchType.UNKNOWN, true), - zone: build(matchType.UNKNOWN, true), - zoneexit: build(matchType.UNKNOWN, true), - command: build(matchType.COMMAND, true) -} - -const configDuplicates = [ - {startsWith: 'opnpc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.NPC}, - {startsWith: 'apnpc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.NPC}, - {startsWith: 'ai_apnpc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.NPC}, - {startsWith: 'ai_opnpc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.NPC}, - {startsWith: 'opobj', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.OBJ}, - {startsWith: 'apobj', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.OBJ}, - {startsWith: 'ai_apobj', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.OBJ}, - {startsWith: 'ai_opobj', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.OBJ}, - {startsWith: 'oploc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.LOC}, - {startsWith: 'aploc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.LOC}, - {startsWith: 'ai_aploc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.LOC}, - {startsWith: 'ai_oploc', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.LOC}, - {startsWith: 'opplayer', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.UNKNOWN}, - {startsWith: 'applayer', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.UNKNOWN}, - {startsWith: 'ai_applayer', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.NPC}, - {startsWith: 'ai_opplayer', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.NPC}, - {startsWith: 'ai_queue', upToNum: 20, includeU: false, includeT: false, includeD: false, defaultMatch: matchType.NPC}, - {startsWith: 'opheld', upToNum: 5, includeU: true, includeT: true, includeD: false, defaultMatch: matchType.OBJ}, - {startsWith: 'inv_button', upToNum: 5, includeU: false, includeT: false, includeD: true, defaultMatch: matchType.COMPONENT}, -]; - -configDuplicates.forEach(dupeDef => { - for (let i = 1; i <= dupeDef.upToNum; i++) { - runescriptTrigger[`${dupeDef.startsWith}${i}`] = build(dupeDef.defaultMatch, false); - } - if (dupeDef.includeU) runescriptTrigger[`${dupeDef.startsWith}u`] = build(dupeDef.defaultMatch, false); - if (dupeDef.includeT) runescriptTrigger[`${dupeDef.startsWith}t`] = build(matchType.COMPONENT, false); - if (dupeDef.includeD) runescriptTrigger[`${dupeDef.startsWith}d`] = build(dupeDef.defaultMatch, false); -}); - -function build(match, declaration) { - return {match: match, declaration: declaration}; -} - -module.exports = runescriptTrigger; diff --git a/client/runescript-language.js b/client/runescript-language.js deleted file mode 100644 index 85d9034..0000000 --- a/client/runescript-language.js +++ /dev/null @@ -1,61 +0,0 @@ -const vscode = require('vscode'); -const hoverProvider = require('./provider/hoverProvider'); -const recolorProvider = require('./provider/recolorProvider'); -const definitionProvider = require('./provider/gotoDefinition'); -const referenceProvider = require('./provider/referenceProvider'); -const renameProvider = require('./provider/renameProvider'); -const cacheManager = require('./cache/cacheManager'); -const commands = require('./provider/vscodeCommands'); -const signatureHelp = require('./provider/signatureHelpProvider'); -const configHelp = require('./provider/configHelpProvider'); -const completionProvider = require('./provider/completionProvider'); -const color24Provider = require('./provider/color24Provider.js'); - -const languages = ['runescript','locconfig','objconfig','npcconfig','dbtableconfig','dbrowconfig','paramconfig','structconfig','enumconfig','varpconfig','varbitconfig','varnconfig','varsconfig','invconfig','seqconfig','spotanimconfig','mesanimconfig','idkconfig','huntconfig','constants','interface','pack','floconfig']; - -function activate(context) { - // Register commands created by this extension - Object.keys(commands).forEach(key => - context.subscriptions.push(vscode.commands.registerCommand(commands[key].id, commands[key].command))); - - // Populate cache on extension activation - vscode.commands.executeCommand(commands.rebuildCache.id); - - // Cache processing event handlers for git branch changes, updating files, create/rename/delete files - vscode.workspace.createFileSystemWatcher('**/.git/HEAD').onDidCreate(() => vscode.commands.executeCommand(commands.rebuildCache.id)); - vscode.workspace.onDidSaveTextDocument(saveDocumentEvent => cacheManager.rebuildFile(saveDocumentEvent.uri)); - vscode.workspace.onDidChangeTextDocument(() => cacheManager.rebuildActiveFile()); - vscode.window.onDidChangeActiveTextEditor(() => cacheManager.rebuildActiveFile()); - vscode.workspace.onDidDeleteFiles(filesDeletedEvent => cacheManager.clearFiles(filesDeletedEvent.files)); - vscode.workspace.onDidRenameFiles(filesRenamedEvent => cacheManager.renameFiles(filesRenamedEvent.files)); - vscode.workspace.onDidCreateFiles(filesCreatedEvent => cacheManager.createFiles(filesCreatedEvent.files)); - - // Register providers (hover, rename, recolor, definition, reference) - for (const language of languages) { - vscode.languages.registerHoverProvider(language, hoverProvider(context)); - vscode.languages.registerRenameProvider(language, renameProvider); - vscode.languages.registerCompletionItemProvider(language, completionProvider.provider, ...completionProvider.triggers); - context.subscriptions.push(vscode.languages.registerDefinitionProvider(language, definitionProvider)); - context.subscriptions.push(vscode.languages.registerReferenceProvider(language, referenceProvider)); - - if (language === 'floconfig' || language === 'interface') { - vscode.languages.registerColorProvider(language, color24Provider); - } else if (language.endsWith('config')) { - vscode.languages.registerColorProvider(language, recolorProvider); - } - - if (language.endsWith('config') || language === 'interface') { - vscode.languages.registerSignatureHelpProvider(language, configHelp.provider, configHelp.metadata); - } - } - vscode.languages.registerSignatureHelpProvider('runescript', signatureHelp.provider, signatureHelp.metadata); -} - -function deactivate() { - cacheManager.clearAll(); - } - -module.exports = { - activate, - deactivate -}; diff --git a/client/utils/cacheUtils.js b/client/utils/cacheUtils.js deleted file mode 100644 index 026c1f9..0000000 --- a/client/utils/cacheUtils.js +++ /dev/null @@ -1,29 +0,0 @@ -const vscode = require('vscode'); - -function resolveKey(name, match) { - return (!name || !match) ? null : name + match.id; -} - -function resolveFileKey(uri) { - return (uri) ? uri.fsPath : null; -} - -function encodeReference(line, index) { - return `${line}|${index}`; -} - -function decodeReferenceToLocation(uri, encodedValue) { - const split = encodedValue.split('|'); - return (split.length !== 2) ? null : new vscode.Location(uri, new vscode.Position(Number(split[0]), Number(split[1]))); -} - -function decodeReferenceToRange(wordLength, encodedValue) { - const split = encodedValue.split('|'); - if (split.length !== 2) { - return null; - } - const startPosition = new vscode.Position(Number(split[0]), Number(split[1])); - return new vscode.Range(startPosition, startPosition.translate(0, wordLength)); -} - -module.exports = { resolveKey, resolveFileKey, encodeReference, decodeReferenceToLocation, decodeReferenceToRange }; diff --git a/client/utils/markdownUtils.js b/client/utils/markdownUtils.js deleted file mode 100644 index 97478e5..0000000 --- a/client/utils/markdownUtils.js +++ /dev/null @@ -1,66 +0,0 @@ -const vscode = require('vscode'); -const path = require('path'); -const { INFO, VALUE, SIGNATURE, CODEBLOCK } = require('../enum/hoverDisplayItems'); -const { GLOBAL_VAR } = require('../matching/matchType'); - -function markdownBase(extensionContext) { - const markdown = new vscode.MarkdownString(); - markdown.supportHtml = true; - markdown.isTrusted = true; - markdown.supportThemeIcons = true; - markdown.baseUri = vscode.Uri.file(path.join(extensionContext.extensionPath, 'icons', path.sep)); - return markdown; -} - -function expectedIdentifierMessage(word, match, markdown) { - markdown.appendMarkdown(`${match.id}${word} not found`); -} - -function appendTitle(name, type, matchId, markdown, id, isCert) { - if (isCert && id) { - name = `${name} (cert) [${Number(id) + 1}]`; - } else if (id) { - name = `${name} [${id}]`; - } - //   - if (matchId === GLOBAL_VAR.id) { - markdown.appendMarkdown(`${type.toUpperCase()} ${name}`); - } else { - markdown.appendMarkdown(`${matchId} ${name}`); - } -} - -function appendInfo(identifier, displayItems, markdown) { - if (displayItems.includes(INFO) && identifier.info) { - appendBody(`${identifier.info}`, markdown); - } -} - -function appendValue(identifier, displayItems, markdown) { - if (displayItems.includes(VALUE) && identifier.value) { - appendBody(`${identifier.value}`, markdown); - } -} - -function appendSignature(identifier, displayItems, markdown) { - if (displayItems.includes(SIGNATURE) && identifier.signature) { - if (identifier.signature.paramsText.length > 0) markdown.appendCodeblock(`params: ${identifier.signature.paramsText}`, identifier.language); - if (identifier.signature.returnsText.length > 0) markdown.appendCodeblock(`returns: ${identifier.signature.returnsText}`, identifier.language); - } -} - -function appendCodeBlock(identifier, displayItems, markdown) { - if (displayItems.includes(CODEBLOCK) && identifier.block) { - markdown.appendCodeblock(identifier.block, identifier.language); - } -} - -function appendBody(text, markdown) { - if (!markdown.value.includes('---')) { - markdown.appendMarkdown('\n\n---'); - } - markdown.appendMarkdown(`\n\n${text}`); -} - -module.exports = { markdownBase, expectedIdentifierMessage, appendTitle, appendInfo, appendValue, - appendSignature, appendCodeBlock, appendBody }; diff --git a/client/utils/matchUtils.js b/client/utils/matchUtils.js deleted file mode 100644 index 7834dc7..0000000 --- a/client/utils/matchUtils.js +++ /dev/null @@ -1,54 +0,0 @@ -const { WORD_PATTERN } = require("../enum/regex"); - -function getWords(lineText, wordPattern=WORD_PATTERN) { - return [ ...lineText.matchAll(wordPattern) ].map((wordMatch, index) => { - return { value: wordMatch[0], start: wordMatch.index, end: wordMatch.index + wordMatch[0].length - 1, index: index} - }); -} - -function getWordAtIndex(words, index) { - if (words.length < 1) return null; - let prev; - for (let i = words.length - 1; i >= 0; i--) { - if (index <= words[i].end) prev = words[i]; - else break; - } - return (prev && prev.start <= index && prev.end >= index) ? prev : null -} - -function expandCsvKeyObject(obj) { - let keys = Object.keys(obj); - for (let i = 0; i < keys.length; ++i) { - let key = keys[i]; - let subkeys = key.split(/,\s?/); - let target = obj[key]; - delete obj[key]; - subkeys.forEach(k => obj[k] = target); - } - return obj; -} - -/** - * Context items shared by both matchWord and matchWords - */ -function getBaseContext(lineText, lineNum, uri) { - lineText = lineText.split('//')[0]; // Ignore anything after a comment - const words = getWords(lineText); - const fileSplit = uri.fsPath.split('\\').pop().split('/').pop().split('.'); - return { - words: words, - uri: uri, - line: {text: lineText, number: lineNum}, - file: {name: fileSplit[0], type: fileSplit[1]}, - } -} - -function reference(type, extraData) { - return (extraData) ? { ...type, extraData: extraData, declaration: false } : { ...type, declaration: false }; -} - -function declaration(type, extraData) { - return (extraData) ? { ...type, extraData: extraData, declaration: true } : { ...type, declaration: true }; -} - -module.exports = { getWords, getWordAtIndex, getBaseContext, expandCsvKeyObject, reference, declaration }; diff --git a/client/utils/stringUtils.js b/client/utils/stringUtils.js deleted file mode 100644 index ea5d577..0000000 --- a/client/utils/stringUtils.js +++ /dev/null @@ -1,49 +0,0 @@ -const vscode = require('vscode'); -const { END_OF_LINE, END_OF_BLOCK } = require('../enum/regex'); - -const getLineText = function(input) { - const endOfLine = END_OF_LINE.exec(input); - return !endOfLine ? input : input.substring(0, endOfLine.index); -} - -const getLines = function(input) { - return input.split(END_OF_LINE); -} - -const skipFirstLine = function(input) { - const endOfLine = END_OF_LINE.exec(input); - return !endOfLine ? input : input.substring(endOfLine.index + 1); -} - -const getBlockText = function(input) { - const endOfBlock = END_OF_BLOCK.exec(input); - return !endOfBlock ? input : input.substring(0, endOfBlock.index); -} - -const nthIndexOf = function(input, pattern, n) { - let i = -1; - while (n-- > 0 && i++ < input.length) { - i = input.indexOf(pattern, i); - if (i < 0) break; - } - return i; -} - -const truncateMatchingParenthesis = function(str) { - let truncateIndex = 0; - let count = 0; - for (let i = 0; i < str.length; i++) { - if (str.charAt(i) === '(') count++; - if (str.charAt(i) === ')' && --count === 0) truncateIndex = i; - } - return (truncateIndex > 0) ? str.substring(truncateIndex + 1) : str; -} - -function createSearchableString(linkableText, query, filesToInclude, isRegex=false) { - const searchOptions = JSON.stringify({ query: query, filesToInclude: filesToInclude, isRegex: isRegex}); - return `[${linkableText}](${vscode.Uri.parse(`command:workbench.action.findInFiles?${encodeURIComponent(searchOptions)}`)})`; -} - -module.exports = { - getLineText, getLines, skipFirstLine, getBlockText, nthIndexOf, truncateMatchingParenthesis, createSearchableString -}; diff --git a/package-lock.json b/package-lock.json index 6242117..4b57000 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,119 @@ { "name": "runescriptlanguage", - "version": "0.1.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "runescriptlanguage", - "version": "0.1.0", + "version": "0.3.0", + "dependencies": { + "runescript-lsp": "file:../runescript-lsp", + "vscode-languageclient": "^9.0.1" + }, "engines": { "vscode": "^1.75.0" } + }, + "../runescript-lsp": { + "version": "0.1.0", + "dependencies": { + "runescript-parser": "^0.1.4", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "devDependencies": { + "@types/node": "^22.13.5", + "eslint": "^9.19.0", + "rimraf": "^6.0.1", + "typescript": "^5.7.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/runescript-lsp": { + "resolved": "../runescript-lsp", + "link": true + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", + "license": "MIT", + "dependencies": { + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" + }, + "engines": { + "vscode": "^1.82.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" } } } diff --git a/package.json b/package.json index 0637b21..d2762ee 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "publisher": "2004scape", "repository": "https://github.com/LostCityRS/RuneScriptLanguage", "description": "Syntax highlighting for RuneScript.", - "version": "0.2.4", + "version": "0.3.0", "icon": "icons/icon-min.png", + "type": "commonjs", "engines": { "vscode": "^1.75.0" }, @@ -13,10 +14,30 @@ "Programming Languages", "Snippets" ], - "main": "./client/runescript-language.js", + "main": "./src/runescript-extension.js", "activationEvents": [ ], "contributes": { + "configuration": { + "title": "RuneScript", + "properties": { + "runescript.enableHover": { + "type": "boolean", + "default": true, + "description": "Enable/disable RuneScript hover information." + }, + "runescript.enableDiagnostics": { + "type": "boolean", + "default": true, + "description": "Enable/disable RuneScript diagnostics." + }, + "runescript.enableDevMode": { + "type": "boolean", + "default": false, + "description": "Enable/disable RuneScript extension development mode." + } + } + }, "languages": [ { "id": "runescript", @@ -542,8 +563,12 @@ "commands": [ { "command": "RuneScriptLanguage.rebuildCache", - "title": "Runescript: Rebuild Workspace Cache" + "title": "Runescript: Rescan Workspace" } ] + }, + "dependencies": { + "runescript-lsp": "file:../runescript-lsp", + "vscode-languageclient": "^9.0.1" } } diff --git a/src/clientState.js b/src/clientState.js new file mode 100644 index 0000000..f21aa5e --- /dev/null +++ b/src/clientState.js @@ -0,0 +1,11 @@ +let client; + +function setClient(nextClient) { + client = nextClient; +} + +function getClient() { + return client; +} + +module.exports = { setClient, getClient }; diff --git a/src/commands.js b/src/commands.js new file mode 100644 index 0000000..7ad52d0 --- /dev/null +++ b/src/commands.js @@ -0,0 +1,21 @@ +const { getClient } = require("./clientState"); + +const vscode = require('vscode'); + +function registerCommands(context) { + context.subscriptions.push( + vscode.commands.registerCommand('RuneScriptLanguage.rebuildCache', executeWorkspaceRescan) + ); +} + +async function executeWorkspaceRescan() { + const editor = vscode.window.activeTextEditor; + const folder = editor ? vscode.workspace.getWorkspaceFolder(editor.document.uri) : undefined; + const workspaceFolder = folder?.uri.toString(); + await getClient().sendRequest("workspace/executeCommand", { + command: "runescript.rescanWorkspace", + arguments: workspaceFolder ? [workspaceFolder] : [] + }); +} + +module.exports = { registerCommands }; diff --git a/src/devModeHighlights.js b/src/devModeHighlights.js new file mode 100644 index 0000000..d061f28 --- /dev/null +++ b/src/devModeHighlights.js @@ -0,0 +1,39 @@ +const { getClient } = require("./clientState"); +const vscode = require('vscode'); + +const decorationType = vscode.window.createTextEditorDecorationType({ + backgroundColor: 'rgba(80, 200, 120, 0.20)' +}); + +function registerDevModeHighlights() { + getClient().onNotification("runescript/decorations", recieveDecorations); +} + +function recieveDecorations({ uri, ranges }) { + const editor = vscode.window.visibleTextEditors.find((e) => e.document.uri.toString() === uri); + if (!editor) return; + + const vscodeRanges = ranges.map(r => + new vscode.Range( + new vscode.Position(r.start.line, r.start.character), + new vscode.Position(r.end.line, r.end.character) + ) + ); + + editor.setDecorations(decorationType, vscodeRanges); +} + +async function requestDecorations(editor) { + const uri = editor.document.uri.toString(); + const res = await getClient().sendRequest("runescript/getDecorations", { uri }); + const ranges = res?.ranges ?? []; + + editor.setDecorations(decorationType, ranges.map(r => + new vscode.Range( + new vscode.Position(r.start.line, r.start.character), + new vscode.Position(r.end.line, r.end.character) + ) + )); +} + +module.exports = { requestDecorations, registerDevModeHighlights }; diff --git a/src/events.js b/src/events.js new file mode 100644 index 0000000..dcc68a8 --- /dev/null +++ b/src/events.js @@ -0,0 +1,28 @@ +const { requestDecorations } = require('./devModeHighlights'); +const { getClient } = require("./clientState"); + +const vscode = require('vscode'); + +function registerEventHandlers(context) { + const gitBranchWatcher = vscode.workspace.createFileSystemWatcher('**/.git/HEAD'); + gitBranchWatcher.onDidCreate(onGitBranchChange); + + context.subscriptions.push( + gitBranchWatcher, + vscode.window.onDidChangeActiveTextEditor(changeActiveTextEditorHandler) + ); +} + +function onGitBranchChange(uri) { + const folder = vscode.workspace.getWorkspaceFolder(uri); + getClient().sendNotification("runescript/gitBranchChanged", { + workspaceUri: folder?.uri.toString() + }); +} + +async function changeActiveTextEditorHandler(editor) { + if (!editor) return; + requestDecorations(editor); +} + +module.exports = { registerEventHandlers }; diff --git a/src/runescript-extension.js b/src/runescript-extension.js new file mode 100644 index 0000000..26144b5 --- /dev/null +++ b/src/runescript-extension.js @@ -0,0 +1,75 @@ +const path = require('path'); +const vscode = require('vscode'); +const { LanguageClient, TransportKind } = require('vscode-languageclient/node'); +const { registerCommands } = require('./commands'); +const { registerDevModeHighlights } = require('./devModeHighlights'); +const { registerEventHandlers } = require('./events'); +const { setClient } = require('./clientState'); + +const outputChannelName = 'RuneScript LSP'; +const languages = ['runescript','locconfig','objconfig','npcconfig','dbtableconfig','dbrowconfig','paramconfig', + 'structconfig','enumconfig','varpconfig','varbitconfig','varnconfig','varsconfig','invconfig','seqconfig', + 'spotanimconfig','mesanimconfig','idkconfig','huntconfig','constants','interface','pack','floconfig']; + +let outputChannel; +let client; + +function activate(context) { + const devMode = context.extensionMode === vscode.ExtensionMode.Development; + outputChannel = vscode.window.createOutputChannel(outputChannelName); + context.subscriptions.push(outputChannel); + + const serverModule = devMode + ? process.env.RUNESCRIPT_LSP_PATH ?? path.resolve(context.extensionPath, '..', 'runescript-lsp', 'dist', 'server.js') + : context.asAbsolutePath(path.join('node_modules', 'runescript-lsp', 'dist', 'server.js')); + + const serverOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: { execArgv: ['--nolazy', '--inspect=6009'] } + } + }; + + const clientOptions = { + documentSelector: languages.map((language) => ({ scheme: 'file', language })), + outputChannel: outputChannel, + synchronize: { + configurationSection: 'runescript', + fileEvents: vscode.workspace.createFileSystemWatcher('**/*.{rs2,cs2,obj,loc,npc,dbtable,dbrow,param,struct,enum,varp,varbit,varn,vars,inv,seq,spotanim,mesanim,idk,hunt,constant,if,flo,pack,order,opt,jm2}') + } + }; + + if (devMode) { + outputChannel.appendLine(`Server module: ${serverModule}`) + clientOptions.traceOutputChannel = outputChannel; + } + + client = new LanguageClient( + 'runescriptLanguageServer', + 'RuneScript Language Server', + serverOptions, + clientOptions + ); + setClient(client); + + client.onDidChangeState((event) => { + if (devMode) outputChannel.appendLine(`Client state: ${event.newState}`); + }); + + registerCommands(context); + registerDevModeHighlights(); + registerEventHandlers(context); + + context.subscriptions.push(client.start()); +} + +function deactivate() { + if (!client) { + return undefined; + } + return client.stop(); +} + +module.exports = { activate, deactivate };