pss-langserver
Version:
A Language server for the Portable Stimulus Standard
438 lines (437 loc) • 17.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCompletionKind = getCompletionKind;
exports.getFunctionContext = getFunctionContext;
exports.isInsideMultilineComment = isInsideMultilineComment;
exports.getDotNotationChain = getDotNotationChain;
exports.getCommentCompletions = getCommentCompletions;
exports.getCompletionsForChain = getCompletionsForChain;
exports.getCompletionsForFunction = getCompletionsForFunction;
exports.getScopeNotationChain = getScopeNotationChain;
exports.getCompletionsForScopedItems = getCompletionsForScopedItems;
/*
* 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/>.
*/
const node_1 = require("vscode-languageserver/node");
const dataTypes_1 = require("../definitions/dataTypes");
const helpers_1 = require("../parser/helpers");
function getCompletionKind(type) {
switch (type) {
/* Structural elements */
case dataTypes_1.objType.COMPONENT:
return node_1.CompletionItemKind.Class;
case dataTypes_1.objType.ACTION:
case dataTypes_1.objType.ACTIVITY:
return node_1.CompletionItemKind.Module;
case dataTypes_1.objType.PACKAGE:
return node_1.CompletionItemKind.Module;
case dataTypes_1.objType.MONITOR:
return node_1.CompletionItemKind.Interface;
case dataTypes_1.objType.CONSTRAINT:
return node_1.CompletionItemKind.Event;
/* Function types */
case dataTypes_1.objType.FUNCTION:
case dataTypes_1.objType.PROCEDURAL_FUNCTION:
return node_1.CompletionItemKind.Function;
case dataTypes_1.objType.ACTION_HANDLE:
return node_1.CompletionItemKind.Method;
case dataTypes_1.objType.CALL_SUPER:
return node_1.CompletionItemKind.Method;
/* Data structures */
case dataTypes_1.objType.ENUM:
return node_1.CompletionItemKind.Enum;
case dataTypes_1.objType.BUFFER:
case dataTypes_1.objType.STRUCT:
return node_1.CompletionItemKind.Struct;
case dataTypes_1.objType.STRUCT_ITEM:
return node_1.CompletionItemKind.Field;
case dataTypes_1.objType.RESOURCE_OBJECT:
return node_1.CompletionItemKind.Class;
/* Register types */
case dataTypes_1.objType.REGISTER_BODY_ITEM:
return node_1.CompletionItemKind.Field;
case dataTypes_1.objType.REGISTER_GROUP:
case dataTypes_1.objType.REGISTER_COMP:
case dataTypes_1.objType.REGISTER_DEF:
case dataTypes_1.objType.REGISTER:
return node_1.CompletionItemKind.Property;
/* Memory and address elements */
case dataTypes_1.objType.MEMORY_SPACE:
case dataTypes_1.objType.MEMORY_REGION:
case dataTypes_1.objType.MEMORY_CLAIM:
case dataTypes_1.objType.ADDRESS_SPACE:
case dataTypes_1.objType.ADDRESS_REGION:
case dataTypes_1.objType.ADDRESS_CLAIM:
return node_1.CompletionItemKind.Reference;
/* State and streams */
case dataTypes_1.objType.STATE:
return node_1.CompletionItemKind.Variable;
case dataTypes_1.objType.STREAM:
case dataTypes_1.objType.POOLS:
return node_1.CompletionItemKind.Variable;
/* Primitive types */
case dataTypes_1.objType.CHANDLE:
return node_1.CompletionItemKind.Variable;
case dataTypes_1.objType.DATA:
return node_1.CompletionItemKind.Value;
case dataTypes_1.objType.TYPEDEF:
return node_1.CompletionItemKind.TypeParameter;
case dataTypes_1.objType.INTEGER:
case dataTypes_1.objType.BIT:
case dataTypes_1.objType.STRING:
case dataTypes_1.objType.BOOL:
case dataTypes_1.objType.FLOAT32:
case dataTypes_1.objType.FLOAT64:
return node_1.CompletionItemKind.Keyword;
/* Collection types */
case dataTypes_1.objType.REF:
return node_1.CompletionItemKind.Reference;
case dataTypes_1.objType.ARRAY:
case dataTypes_1.objType.LIST:
case dataTypes_1.objType.MAP:
case dataTypes_1.objType.SET:
case dataTypes_1.objType.INSTANCE:
return node_1.CompletionItemKind.Value;
/* Template and var args */
case dataTypes_1.objType.TEMPLATE_ITEM:
return node_1.CompletionItemKind.TypeParameter;
case dataTypes_1.objType.VARARGS:
return node_1.CompletionItemKind.Variable;
/* Executable blocks */
case dataTypes_1.objType.EXEC_PRESOLVE:
case dataTypes_1.objType.EXEC_POSTSOLVE:
case dataTypes_1.objType.EXEC_PREBODY:
case dataTypes_1.objType.EXEC_BODY:
case dataTypes_1.objType.EXEC_HEADER:
case dataTypes_1.objType.EXEC_DECLARATION:
case dataTypes_1.objType.EXEC_RUNSTART:
case dataTypes_1.objType.EXEC_RUNEND:
case dataTypes_1.objType.EXEC_INITDOWN:
case dataTypes_1.objType.EXEC_INITUP:
case dataTypes_1.objType.EXEC_INIT:
case dataTypes_1.objType.EXEC_TARGET:
case dataTypes_1.objType.EXEC_FILE:
return node_1.CompletionItemKind.Snippet;
/* Misc */
case dataTypes_1.objType.IMPORT:
return node_1.CompletionItemKind.File;
case dataTypes_1.objType.ASSIGNMENT:
return node_1.CompletionItemKind.Operator;
/* Default cases */
case dataTypes_1.objType.NONE:
case dataTypes_1.objType.UNKNOWN:
default:
return node_1.CompletionItemKind.Text;
}
}
// Convert Position to character offset in the text
function getOffsetFromPosition(text, position) {
const lines = text.split('\n');
let offset = 0;
for (let i = 0; i < position.line; i++) {
offset += lines[i].length + 1; // +1 for newline
}
offset += position.character;
return offset;
}
// Check if cursor is inside a /** ... */ comment
function isInsideMultilineComment(text, position) {
const offset = getOffsetFromPosition(text, position);
const textUpToCursor = text.substring(0, offset);
const commentStarts = (textUpToCursor.match(/\/\*\*/g) || []).length;
const commentEnds = (textUpToCursor.match(/\*\//g) || []).length;
return commentStarts > commentEnds;
}
// Extract double-colon (scope) notation chain (e.g., ['abc'] for 'abc::', ['abc', 'def'] for 'abc::Def::')
function getScopeNotationChain(text, position) {
const offset = getOffsetFromPosition(text, position);
// Ensure the last two characters before the cursor are "::"
if (offset < 2 || text.slice(offset - 2, offset) !== '::') {
return null;
}
let chain = [];
let current = '';
let i = offset - 3; // Start before the last '::'
while (i >= 0) {
const char = text[i];
if (/[a-zA-Z0-9_]/.test(char)) {
current = char + current;
}
else if (text.slice(i - 1, i + 1) === '::' && current !== '') {
chain.unshift(current.toLowerCase());
current = '';
i -= 1; // Skip the previous ':'
}
else {
break;
}
i--;
}
if (current !== '') {
chain.unshift(current.toLowerCase());
}
return chain.length > 0 ? chain : null;
}
// Extract dot notation chain (e.g., ['abc'] for 'abc.', ['abc', 'def'] for 'abc.def.')
function getDotNotationChain(text, position) {
const offset = getOffsetFromPosition(text, position);
if (offset === 0 || text[offset - 1] !== '.') {
return null;
}
let chain = [];
let current = '';
let i = offset - 2; // Before the dot
while (i >= 0) {
const char = text[i];
if (/[a-zA-Z0-9_]/.test(char)) {
current = char + current;
}
else if (char === '.' && current !== '') {
chain.unshift(current);
current = '';
}
else {
break;
}
i--;
}
if (current !== '') {
chain.unshift(current);
}
return chain.length > 0 ? chain : null;
}
// Detect if cursor is inside a function call and get context
function getFunctionContext(text, position) {
const offset = getOffsetFromPosition(text, position);
const textUpToCursor = text.substring(0, offset);
let depth = 0;
let parameterIndex = -1;
let functionName = null;
let i = 0;
while (i < textUpToCursor.length) {
const char = textUpToCursor[i];
if (char === '(') {
let j = i - 1;
while (j >= 0 && /[a-zA-Z0-9_]/.test(textUpToCursor[j])) {
j--;
}
const identifier = textUpToCursor.substring(j + 1, i);
if (identifier) {
functionName = identifier;
parameterIndex = 0;
}
depth++;
}
else if (char === ')') {
depth--;
if (depth === 0) {
functionName = null;
parameterIndex = -1;
}
}
else if (char === ',' && depth === 1) {
parameterIndex++;
}
i++;
}
if (depth > 0 && functionName) {
return { functionName, parameterIndex };
}
return null;
}
// This returns the keywords that can be used in comments - like brief, file, etc.
function getCommentCompletions() {
const items = ["brief", "param", "return", "returns", "deprecated", "author", "date", "version", "see", "attention", "file", "todo", "example"];
const descriptions = [
"Provides a brief summary of a function, class, or file. Used in index and summary views.",
"Describes a parameter for a function or method, usually followed by the parameter name and description.",
"Describes the return value of a function/action.",
"Same as 'return'; an alternative keyword to describe what a function/action returns.",
"Marks the function, class, or element as deprecated. Indicates that it should not be used anymore.",
"Specifies the author of the file, function, or class.",
"Indicates the creation or modification date of the file or item.",
"States the version number of the documented item, typically used for tracking or release notes.",
"Provides a cross-reference to another item such as a related function, class, or file.",
"Highlights important information that the reader should pay close attention to.",
"Used in file-level comments to document the purpose or content of the file.",
"Marks a to-do item or something that needs to be implemented, fixed, or improved.",
"Links to or embeds an example of how to use a class, function, or feature."
];
var autoCompletions = [];
items.forEach((item, i) => {
autoCompletions.push({
label: item,
kind: node_1.CompletionItemKind.Property,
detail: descriptions[i]
});
});
return autoCompletions;
}
// This Function
function getCompletionsWithinStruct(remainingChain, currentStruct, ast) {
let completions = [];
if (remainingChain.length === 0) {
currentStruct.children.forEach(child => {
completions.push({
label: child.name,
kind: node_1.CompletionItemKind.EnumMember
});
});
return completions;
}
const memberName = remainingChain[0];
const member = currentStruct.children.find(child => child.name === memberName);
if (!member) {
return completions;
}
const memberTypeNode = member;
const memberType = memberTypeNode.instanceType;
if (!memberType) {
return completions;
}
const memberStruct = (0, helpers_1.getNodeFromNameArray)(ast, memberType, dataTypes_1.objType.ASSIGNMENT);
console.warn("memberStruct", memberStruct);
if (!memberStruct) {
return completions;
}
return getCompletionsWithinStruct(remainingChain.slice(1), memberStruct, ast);
}
// This function returns auto-completion for chain items (Like structs, buffer items, etc.)
function getCompletionsForChain(chain, ast) {
let completions = [];
if (chain.length == 0) {
return completions;
}
const baseName = chain[0];
const parent = (0, helpers_1.getNodeFromNameArray)(ast, baseName, dataTypes_1.objType.ASSIGNMENT);
if (!parent) {
return completions;
}
/* Test if its address space or address region - as they require different auto-completions */
if (parent.type === dataTypes_1.objType.ADDRESS_SPACE || parent.type === dataTypes_1.objType.ADDRESS_REGION || parent.type === dataTypes_1.objType.ADDRESS_CLAIM) {
/* We can return proper auto-completions as required - and they don't typically have 2nd or 3rd chain item, unless its trait */
if (chain.length === 1) {
switch (parent.type) {
case dataTypes_1.objType.ADDRESS_SPACE:
completions.push({
label: "add_nonallocatable_region",
kind: node_1.CompletionItemKind.Function
});
completions.push({
label: "add_region",
kind: node_1.CompletionItemKind.Function
});
break;
case dataTypes_1.objType.ADDRESS_REGION:
completions.push({
label: "size",
kind: node_1.CompletionItemKind.Property
});
completions.push({
label: "addr",
kind: node_1.CompletionItemKind.Property
});
completions.push({
label: "trait",
kind: node_1.CompletionItemKind.Property
});
break;
}
return completions;
}
else if (chain[1] === 'trait') {
const node = parent;
const traitsStruct = node.traitsStruct;
const traitNode = (0, helpers_1.getNodeFromNameArray)(ast, traitsStruct, dataTypes_1.objType.ASSIGNMENT);
if (traitNode) {
traitNode.children.forEach(child => {
completions.push({
label: child.name,
kind: node_1.CompletionItemKind.Property
});
});
}
return completions;
}
return completions;
}
const parentNode = parent;
const structName = parentNode.instanceType;
const superParent = (0, helpers_1.getNodeFromNameArray)(ast, structName, dataTypes_1.objType.ASSIGNMENT);
if (!superParent) {
return completions;
}
if (chain.length === 1) {
superParent.children.forEach(child => {
completions.push({
label: child.name,
kind: node_1.CompletionItemKind.EnumMember
});
});
return completions;
}
else {
return getCompletionsWithinStruct(chain.slice(1), superParent, ast);
}
}
// This function provides auto-completions for function parameters
function getCompletionsForFunction(name, index, ast) {
let completions = [];
let node = (0, helpers_1.getNodeFromNameArray)(ast, name, dataTypes_1.objType.FUNCTION_CALL);
if (!node) {
return completions;
}
const funcNode = node;
const params = funcNode.parameters;
if (index > (params.length - 1)) {
return [];
}
const type = params[index].paramType;
const allTypeNodes = (0, helpers_1.collectAllInstanceNodes)(ast, type);
allTypeNodes.forEach(instanceNode => {
completions.push({
label: instanceNode.name,
kind: node_1.CompletionItemKind.Variable
});
});
return completions;
}
function getCompletionsForScopedItems(name, ast) {
const node = (0, helpers_1.getNodeFromNameArray)(ast, name[name.length - 1]);
let completions = [];
if (node) {
switch (node.type) {
case dataTypes_1.objType.ENUM:
const enumNode = node;
enumNode.enumItems.forEach(item => {
completions.push({
label: item.name,
kind: node_1.CompletionItemKind.EnumMember
});
});
break;
default:
node.children.forEach(child => {
completions.push({
label: child.name,
kind: node_1.CompletionItemKind.TypeParameter
});
});
break;
}
}
return completions;
}