UNPKG

pss-langserver

Version:

A Language server for the Portable Stimulus Standard

197 lines (196 loc) 9.85 kB
"use strict"; /* * Copyright (C) 2025 Darshan(@thisisthedarshan) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.generateSemanticTokens = generateSemanticTokens; exports.createSemanticTokensFor = createSemanticTokensFor; exports.generateSemanticTokensAdvanced = generateSemanticTokensAdvanced; const node_1 = require("vscode-languageserver/node"); const dataTypes_1 = require("../definitions/dataTypes"); const helpers_1 = require("../parser/helpers"); const semanticTokenDefinitions_1 = require("../definitions/semanticTokenDefinitions"); function getSemanticTokenInfo(type) { return semanticTokenDefinitions_1.semanticTokenTypes[type]; } function encodeSemanticTokens(tokens) { const encodedTokens = []; let prevLine = 0; let prevChar = 0; for (const token of tokens) { const deltaLine = token.line - prevLine; const deltaChar = token.line === prevLine ? token.startChar - prevChar : token.startChar; encodedTokens.push(deltaLine, deltaChar, token.length, token.tokenType, token.tokenModifiers); prevLine = token.line; prevChar = token.startChar; } return encodedTokens; } /* Provides semantic tokens to the client */ function generateSemanticTokens(file, ast) { let semTokensFromMeta = new node_1.SemanticTokensBuilder(); findSemanticTokens(file, semanticTokenDefinitions_1.semanticTokensBuiltin, semTokensFromMeta); ast.flatMap(astObject => { Object.entries(astObject).map(([keyword, info]) => { let modifiers = 0; let objectType = info.objectType; let semanticTokensForAstObj = semanticTokenDefinitions_1.semanticTokenTypes[dataTypes_1.objType[objectType]]; if (Array.isArray(semanticTokensForAstObj.tokenModifiers)) { semanticTokensForAstObj.tokenModifiers.forEach(token => { modifiers += (0, helpers_1.mapTokenModifiers)(token.toString()); }); } semTokensFromMeta.push(info.onLine.lineNumber, info.onLine.columnNumber, keyword.length, typeof semanticTokensForAstObj.tokenType !== "number" ? (0, helpers_1.mapTokenTypes)(semanticTokensForAstObj.tokenType.toString()) : semanticTokensForAstObj.tokenType, Array.isArray(semanticTokensForAstObj.tokenModifiers) ? modifiers : typeof semanticTokensForAstObj.tokenModifiers !== "number" ? (0, helpers_1.mapTokenModifiers)(semanticTokensForAstObj.tokenModifiers.toString()) : semanticTokensForAstObj.tokenModifiers); }); }); return semTokensFromMeta.build(); } function getTokensForNode(pssNode) { const returnval = []; const objectType = pssNode.type; const semanticTokensForAstObj = semanticTokenDefinitions_1.semanticTokenTypes[objectType] || { tokenType: 0, tokenModifiers: 0 }; // Fallback const onInfo = pssNode.definedOn || { lineNumber: 0, columnNumber: 0 }; // Fallback let modifiers = 0; if (Array.isArray(semanticTokensForAstObj.tokenModifiers)) { modifiers = semanticTokensForAstObj.tokenModifiers.reduce((sum, token) => sum + (0, helpers_1.mapTokenModifiers)(token.toString()), 0); } else { modifiers = typeof semanticTokensForAstObj.tokenModifiers === "number" ? semanticTokensForAstObj.tokenModifiers : (0, helpers_1.mapTokenModifiers)(semanticTokensForAstObj.tokenModifiers.toString()); } returnval.push({ line: onInfo.lineNumber, char: onInfo.columnNumber, length: pssNode.name.length, tokenType: typeof semanticTokensForAstObj.tokenType === "number" ? semanticTokensForAstObj.tokenType : (0, helpers_1.mapTokenTypes)(semanticTokensForAstObj.tokenType.toString()), tokenModifiers: modifiers, }); pssNode.children.forEach(child => { returnval.push(...getTokensForNode(child)); }); return returnval; } function createSemanticTokensFor(file) { let semTokensFromMeta = new node_1.SemanticTokensBuilder(); findSemanticTokens(file, semanticTokenDefinitions_1.semanticTokensBuiltin, semTokensFromMeta); return semTokensFromMeta.build(); } function generateSemanticTokensAdvanced(ast) { let semTokensFromMeta = new node_1.SemanticTokensBuilder(); ast.forEach(item => { const sematicDataArray = getTokensForNode(item); sematicDataArray.forEach(sematicData => { semTokensFromMeta.push(sematicData.line, sematicData.char, sematicData.length, sematicData.tokenType, sematicData.tokenModifiers); }); }); return semTokensFromMeta.build(); } function findSemanticTokens(text, keywordArray, tokens) { const lines = text.split('\n'); /* Track comment state across lines */ let inMultilineComment = false; /* Process each line */ lines.forEach((line, lineIndex) => { let processedLine = line; let offsetMap = []; /* Build offset map for original positions */ for (let i = 0; i < line.length; i++) { offsetMap[i] = i; } /* Handle multiline comments */ if (inMultilineComment) { const endCommentIndex = line.indexOf('*/'); if (endCommentIndex !== -1) { /* End of multiline comment found */ processedLine = line.substring(endCommentIndex + 2); inMultilineComment = false; /* Adjust offset map for removed comment part */ offsetMap = offsetMap.slice(endCommentIndex + 2); } else { /* Entire line is within a comment */ return; } } /* Handle single line comments and start of multiline comments */ let singleLineCommentIndex = processedLine.indexOf('//'); let multilineCommentStartIndex = processedLine.indexOf('/*'); /* Handle single line comments */ if (singleLineCommentIndex !== -1) { if (multilineCommentStartIndex === -1 || singleLineCommentIndex < multilineCommentStartIndex) { processedLine = processedLine.substring(0, singleLineCommentIndex); } } /* Handle start of multiline comments */ if (multilineCommentStartIndex !== -1) { if (singleLineCommentIndex === -1 || multilineCommentStartIndex < singleLineCommentIndex) { const endCommentIndex = processedLine.indexOf('*/', multilineCommentStartIndex + 2); if (endCommentIndex !== -1) { /* Multiline comment ends on the same line */ processedLine = processedLine.substring(0, multilineCommentStartIndex) + processedLine.substring(endCommentIndex + 2); } else { /* Multiline comment continues to next line */ processedLine = processedLine.substring(0, multilineCommentStartIndex); inMultilineComment = true; } } } /* Skip empty lines after comment removal */ if (processedLine.trim() === '') { return; } /* Check each keyword */ for (const [keyword, info] of Object.entries(keywordArray)) { let startIndex = 0; /* Find all occurrences of the keyword in the line */ while (startIndex < processedLine.length) { const keywordIndex = processedLine.indexOf(keyword, startIndex); let modifiers = 0; if (keywordIndex === -1) { break; /* No more occurrences */ } /* Check for word boundaries to avoid partial matches */ const beforeChar = keywordIndex > 0 ? processedLine[keywordIndex - 1] : ' '; const afterChar = keywordIndex + keyword.length < processedLine.length ? processedLine[keywordIndex + keyword.length] : ' '; const isWordBoundaryBefore = /\W/.test(beforeChar); const isWordBoundaryAfter = /\W/.test(afterChar); if (Array.isArray(info.tokenModifiers)) { info.tokenModifiers.forEach(token => { modifiers += (0, helpers_1.mapTokenModifiers)(token.toString()); }); } if (isWordBoundaryBefore && isWordBoundaryAfter) { /* Map position back to original line using offset map */ const originalIndex = offsetMap[keywordIndex] || keywordIndex; /* Add token */ tokens.push(lineIndex, originalIndex, keyword.length, typeof info.tokenType !== "number" ? (0, helpers_1.mapTokenTypes)(info.tokenType.toString()) : info.tokenType, Array.isArray(info.tokenModifiers) ? modifiers : typeof info.tokenModifiers !== "number" ? (0, helpers_1.mapTokenModifiers)(info.tokenModifiers.toString()) : info.tokenModifiers); } /* Move to the next position */ startIndex = keywordIndex + keyword.length; } } }); return tokens.build(); }