pss-langserver
Version:
A Language server for the Portable Stimulus Standard
197 lines (196 loc) • 9.85 kB
JavaScript
;
/*
* 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();
}