pss-langserver
Version:
A Language server for the Portable Stimulus Standard
275 lines (273 loc) • 11.5 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/>.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatDocument = formatDocument;
exports.formatFileHeader = formatFileHeader;
const formattingHelper_1 = __importDefault(require("./formattingHelper"));
function formatDocument(fileName, text, tabspace, author, patterns, formatHeader, maxColumns) {
/* First format curly braces */
let doc = formatCurlyBraces(text);
/* Then add spaces after commas */
doc = formatCommas(doc);
/* Format multi-line comments: */
doc = formatMultilineComments(doc);
/* Then format semicolons */
doc = addNewlinesAfterSemicolons(doc);
/* Then start by formatting patterns - beautification */
doc = (0, formattingHelper_1.default)(doc, patterns);
/* Add spaces when using keywords */
doc = doc.replace(/\b(if|for|while|repeat)(?=\()/g, '$1 ');
/* Process line by line */
let lines = doc.split('\n');
const formattedLines = [];
let indentLevel = 0; /* Start with indentLevel of 0 */
let isInBlockComment = false;
for (let line of lines) {
/* Keep empty newlines as it is */
if (line.trim() === '') {
formattedLines.push(''); // Keep the empty line as-is
continue;
}
line = line.trim();
/* Format specific syntax */
line = formatOperators(line);
line = formatSingleLineComments(line);
/* Handle closing braces */
if (line.startsWith('}') && !isInBlockComment && !(/\/\//.test(line))) {
indentLevel = Math.max(indentLevel - tabspace, 0);
}
/* Check if comment block is encountered */
if (line.startsWith("/*")) {
isInBlockComment = true;
}
if (line.endsWith("*/")) {
isInBlockComment = false;
if (!line.startsWith("/*")) {
line = line.replace(/^(?!.*\/\*).*?\*\/$/, (match) => match.replace(/(\*\/)/, ' $1'));
}
}
/* Check if still in comment */
if (isInBlockComment) {
if (line.startsWith("*")) {
line = ` ${line}`; // Add an extra space
}
}
/* Add indentation */
const indentedLine = `${' '.repeat(indentLevel)}${line}`;
/* Wrap the line if necessary */
const wrappedLines = wrapLine(indentedLine, indentLevel, tabspace, maxColumns);
formattedLines.push(...wrappedLines);
/* Handle opening braces */
if (line.endsWith('{') && !isInBlockComment && !(/\/\/|\/\*/.test(line))) {
indentLevel += tabspace;
}
}
let formattedFile = formattedLines.join('\n');
if (formatHeader) {
formatFileHeader(formattedFile, fileName, "sad", "dsa", author);
}
return formattedFile;
}
function wrapLine(line, indentLevel, tabspace, maxColumns) {
if (maxColumns === 0 || line.length <= maxColumns) {
return [line];
}
const words = line.split(' ');
const wrappedLines = [];
let currentLine = '';
const baseIndent = ' '.repeat(indentLevel);
const doubleIndent = ' '.repeat(indentLevel * 2);
for (const word of words) {
const separator = currentLine ? ' ' : '';
if (currentLine.length + separator.length + word.length > maxColumns) {
if (currentLine) {
wrappedLines.push(currentLine.trimEnd());
}
currentLine = doubleIndent + word;
}
else {
currentLine += separator + word;
}
}
if (currentLine) {
wrappedLines.push(currentLine.trimEnd());
}
/* Ensure the first line retains the original indent */
if (wrappedLines.length > 0) {
wrappedLines[0] = baseIndent + wrappedLines[0].trimStart();
}
return wrappedLines;
}
function formatCurlyBraces(input) {
// Remove newline before `{` and move it back to the previous line
input = input.replace(/\n\s*{/g, ' {');
// Ensure there is exactly 1 space before the opening `{`
input = input.replace(/\s*{/g, ' {');
// Ensure there is always a newline after the opening `{` without removing existing newlines
input = input.replace(/({)(?!\n)/g, '$1\n');
// Ensure `}` is on its own line and add a newline before it if needed
input = input.replace(/([^\n])(\s*})(?!\n)/g, (match, p1, p2) => {
// Only add newline if there isn't already a newline before the closing brace
return /\n/.test(match) ? match : p1 + '\n' + p2;
});
// Add a newline after `}` only if there is no newline already
input = input.replace(/}(?!\n)(?!\s*\n)(?!;)/g, '}\n');
return input;
}
function addNewlinesAfterSemicolons(input) {
// Step 1: Add newline after semicolon if not followed by newline or comment (single-line or multi-line)
input = input.replace(/;(?!\s*(?:\n|\/\/|\/\*[^*]*\*\/))(?=\s*(?!\n))(?=\s*(?![^*]*\*\/)[^]*\n)/g, ';\n');
return input; // No need for .trim() to avoid stripping empty lines
}
function formatCommas(input) {
// Add a space after every comma if there isn't already one
return input.replace(/,\s*/g, ', ');
}
function formatMultilineComments(documentText) {
return documentText.replace(/(\/\*\*?)([\s\S]*?)\*\//g, (match, openingBlock, commentBody) => {
const opening = openingBlock;
const closing = '*/';
// Handle single-line comments
if (!commentBody.includes('\n')) {
return `${opening} ${commentBody.trim()} ${closing}`.replace(/\s+/g, ' ');
}
// Handle multiline comments
const newlineIndex = commentBody.indexOf('\n');
let firstPart = '';
let rest = commentBody;
// Split into first part (before first newline) and the rest
if (newlineIndex !== -1) {
firstPart = commentBody.substring(0, newlineIndex).trim();
rest = commentBody.substring(newlineIndex + 1);
}
else {
firstPart = commentBody.trim();
rest = '';
}
// Check if first part is all asterisks
if (/^\*+$/.test(firstPart)) {
let formattedComment = `${opening} ${firstPart}`;
// Process the remaining lines if any
if (rest) {
const lines = rest.split('\n');
const formattedLines = lines.map((line) => {
const trimmedLine = line.trim();
if (trimmedLine === '' || trimmedLine === '*') {
return null;
}
else if (trimmedLine.startsWith('*')) {
return `* ${trimmedLine.slice(1).trim()}`;
}
else {
return `* ${trimmedLine}`;
}
}).filter((line) => line !== null);
formattedComment += '\n' + formattedLines.join('\n');
}
formattedComment += '\n' + closing;
return formattedComment;
}
else {
// First part is not all asterisks, format with * on each line
const lines = commentBody.split('\n');
const formattedLines = lines.map((line) => {
const trimmedLine = line.trim();
if (trimmedLine === '' || trimmedLine === '*') {
return null;
}
else if (trimmedLine.startsWith('*')) {
return `* ${trimmedLine.slice(1).trim()}`;
}
else {
return `* ${trimmedLine}`;
}
}).filter((line) => line !== null);
return `${opening}\n${formattedLines.join('\n')}\n${closing}`;
}
});
}
function formatOperators(input) {
const operatorRegex = /([^\s])([\+\-\*\/\%\^=<>!&|]+)([^\s])/g;
function formatExpression(expression) {
return expression.replace(operatorRegex, (match, left, ops, right) => {
if ((left === '/' && (ops === '*' || ops === '**')) || (ops === '*' && right === '/')) {
return match;
}
if (ops.length === 1) {
return `${left} ${ops} ${right}`;
}
else {
return match;
}
});
}
function formatNested(content) {
let previousContent;
do {
previousContent = content;
content = content.replace(/\(([^()]+)\)/g, (match, innerContent) => {
return `(${formatExpression(innerContent)})`;
});
} while (content !== previousContent);
return formatExpression(content);
}
return input.replace(/\/\*[\s\S]*?\*\//g, match => match) // Ignore multiline comments
.replace(/\/\/[^\n]*/g, match => match) // Ignore single-line comments
.replace(/['"`][^'"`]*['"`]/g, match => match) // Ignore strings
.replace(/\bhttps?:\/\/[^\s)]+/g, match => match) // Ignore URLs
.replace(/[^\s()]+/g, formatNested);
}
function formatSingleLineComments(line) {
// Ensure there is a space before `//`, but ignore URLs starting with `://`
line = line.replace(/([^:])\/\/(?! )/g, '$1 // '); // Add a space before `//` if not preceded by a colon
// Ensure there is a space after `//`, but ignore if it's part of a URL (contains colon before `//`)
line = line.replace(/([^:])\/\/(?! )/g, '$1 // '); // Ensures space after `//` if not already present and not part of a URL
return line;
}
function formatFileHeader(content, fileName, creationDate, lastModifiedDate, author) {
const headerRegex = /^\/\*\*[\s\S]*?\*\/\n?/; // Match the header block only at the top of the file
const lastModifiedRegex = /(Last Modified on: ).*/;
// If a header already exists, update "Last Modified on:"
if (headerRegex.test(content)) {
return content.replace(headerRegex, (header) => {
// Update or add the "Last Modified on:" field in the existing header
if (lastModifiedRegex.test(header)) {
return header.replace(lastModifiedRegex, `$1${lastModifiedDate}`);
}
else {
// Add "Last Modified on:" if it doesn't exist
const headerLines = header.split('\n');
headerLines.splice(headerLines.length - 1, 0, ` * Last Modified on: ${lastModifiedDate}`);
return headerLines.join('\n');
}
});
}
// If no header exists, add a new one at the top
const newHeader = `/**
* @file ${fileName}
* @author ${author}
* @brief
* @date ${creationDate}
* Last Modified on: ${lastModifiedDate}
*/
`;
return newHeader + content;
}