I am developing a code formatter and snippet support for Mount&Blade: Warband Script Language which is a game and I have encountered serious problems which my formatter doesn't work at all. Snippets works as intended. I explained what I intended to do at the bottom of this post. Here is my work for
extension.js
const acorn = require('acorn');
const escodegen = require('escodegen');
const vscode = require('vscode');
function formatWarbandScriptLanguageCode(code) {
const parsedAst = acorn.parse(code, { ecmaVersion: 5 });
let indentationLevel = 0;
traverse(parsedAst, {
enter(node, parent) {
if (node.type === 'CallExpression') {
const operationNames = [
'try_begin',
'try_for_range',
'try_for_range_backwards',
'try_for_parties',
'try_for_agents',
'try_for_prop_instances',
'try_for_players',
'try_for_dict_keys',
];
if (operationNames.includes(node.callee.name)) {
// Insert a newline before the operation call
if (parent.body.indexOf(node) === 0) {
const newlineNode = {
type: 'WhiteSpace',
value: '\n' + ' '.repeat(indentationLevel), // Adjust the desired indentation
};
parent.body.unshift(newlineNode);
}
// Add a tab indentation to the arguments of the operation
node.arguments.forEach(arg => {
if (arg.type === 'ArrayExpression') {
arg.elements.forEach(element => {
element.loc.indent += 1; // Adjust the indentation level
});
}
});
indentationLevel++;
}
}
},
leave(node) {
if (node.type === 'CallExpression') {
const operationNames = [
'try_begin',
'try_for_range',
'try_for_range_backwards',
'try_for_parties',
'try_for_agents',
'try_for_prop_instances',
'try_for_players',
'try_for_dict_keys',
];
if (operationNames.includes(node.callee.name)) {
indentationLevel--;
}
}
},
});
const formattedCode = escodegen.generate(parsedAst);
return formattedCode;
}
function activate(context) {
console.log('M&B Warband API extension is now active.');
// ... other activation code ...
let disposable = vscode.commands.registerCommand('mbap.formatWarbandScript', () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const document = editor.document;
const text = document.getText();
// Format the code
const formattedCode = formatWarbandScriptLanguageCode(text);
// Apply the formatted code
const edit = new vscode.TextEdit(
new vscode.Range(0, 0, document.lineCount, 0),
formattedCode
);
const workspaceEdit = new vscode.WorkspaceEdit();
workspaceEdit.set(document.uri, [edit]);
vscode.workspace.applyEdit(workspaceEdit);
});
context.subscriptions.push(disposable);
}
// This method is called when your extension is deactivated
function deactivate() {
console.log('M&B Warband API extension is now deactivated.');
}
module.exports = {
activate,
deactivate
};
package.json
{
"name": "mbap",
"displayName": "M&B: Warband API",
"description": "Mount & Blade: Warband language support for Microsoft Visual Studio Code by Azremen and Sart",
"publisher": "Azremen",
"version": "0.1.18",
"homepage": "https://github.com/Azremen/MB-Warband-API-VSC/blob/main/README.md",
"engines": {
"vscode": "^1.62.0"
},
"repository": {
"type": "git",
"url": "https://github.com/Azremen/MB-Warband-API-VSC.git"
},
"categories": [
"Programming Languages"
],
"main": "./extension.js",
"activationEvents": [
"onCommand:mbap.formatWarbandScript"
],
"contributes": {
"languages": [
{
"id": "warbandsl",
"aliases": [
"Warband Script Language"
],
"extensions": [
".py"
],
"configuration": "./language-configuration.json"
}
],
"commands": [
{
"command": "mbap.formatWarbandScript",
"title": "Format Warband Script Language Code"
}
],
"snippets": [
{
"language": "warbandsl",
"path": "./snippets/mbap.code-snippets"
}
],
"keybindings": [
{
"command": "mbap.formatWarbandScript",
"key": "alt+shift+f",
"when": "editorTextFocus && resourceLangId == warbandsl"
}
]
},
"configuration": {
"title": "Warband Script Language Formatter",
"properties": {
"warbandsl.indentation": {
"type": "string",
"enum": [
"tabs",
"spaces"
],
"default": "spaces",
"description": "Indentation style for Warband Script Language Formatter."
},
"warbandsl.tabSize": {
"type": "number",
"default": 4,
"description": "Number of spaces or tabs to use for indentation."
}
}
},
"dependencies": {
"acorn": "^8.0.1",
"escodegen": "^2.0.0"
}
}
Visual Studio Code gives unexpected token(1:5)
error with a pop-up on combination of keyboard keys of alt+shift+f
which it is supposed to format the desired file. Visual Studio Code tries to run mbap.formatWarbandScript
command while this is happening as far as I understand.
I was intending it to move onto the next line after operationNames
array for each element and then insert a tab. After they are done there is a 'try_end'
line which is supposed to align with those. I can't test it out since it doesn't work at all.
After reading comments and simplifying my problem I've come to conclusion as I have reworked my project entirely here is my work:
extension.js
const vscode = require('vscode');
const { execFileSync } = require('child_process');
const os = require('os');
const fs = require('fs');
function formatAndSaveDocument() {
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
const document = activeEditor.document;
const operationNames = [
"try_begin",
"try_for_range",
"try_for_range_backwards",
"try_for_parties",
"try_for_agents",
"try_for_prop_instances",
"try_for_players",
"try_for_dict_keys",
"else_try",
];
const originalContent = document.getText();
// Create a temporary file to store the original content
const tempFilePath = `${os.tmpdir()}/temp_mbap_script.py`;
fs.writeFileSync(tempFilePath, originalContent, 'utf-8');
// Use black command-line tool to format the content and get the formatted code
const blackCmd = `black --quiet ${tempFilePath}`;
execFileSync(blackCmd, {
encoding: 'utf-8',
shell: true
});
// Read the black-formatted content from the temporary file
const blackFormattedCode = fs.readFileSync(tempFilePath, 'utf-8');
// Delete the temporary file
fs.unlinkSync(tempFilePath);
// Apply your custom formatting with adjusted indentation levels for specific operation names
const customFormattedLines = [];
let currentIndentationLevel = 0;
for (const line of blackFormattedCode.split('\n')) {
const trimmedLine = line.trim();
if (trimmedLine.includes("try_end") || trimmedLine.includes("else_try")) {
currentIndentationLevel--;
}
const customIndentation = '\t'.repeat(Math.max(0, currentIndentationLevel));
const customFormattedLine = customIndentation + line;
customFormattedLines.push(customFormattedLine);
if (operationNames.some(op => trimmedLine.includes(op))) {
currentIndentationLevel++;
}
}
const customFormattedCode = customFormattedLines.join('\n');
const edit = new vscode.WorkspaceEdit();
edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), customFormattedCode);
vscode.workspace.applyEdit(edit).then(success => {
if (success) {
vscode.window.showInformationMessage('Formatted and saved the document.');
} else {
vscode.window.showErrorMessage('An error occurred while formatting and saving the document.');
}
});
}
}
function checkAndInstallBlack() {
const terminal = vscode.window.createTerminal('Install Black');
// Check if Black is installed
terminal.sendText('pip show black', true);
// When the terminal output is ready, check if Black is installed
terminal.onDidWriteData(data => {
if (data.includes("Package(s) not found: black")) {
// Black is not installed, so install it
terminal.sendText('pip install black', true);
}
// Dispose of the terminal
terminal.dispose();
});
terminal.show();
}
function activate(context) {
// Register the formatAndSaveDocument command
const disposable = vscode.commands.registerCommand('mbap.formatWarbandScript', formatAndSaveDocument);
// Run the checkAndInstallBlack function when the extension is activated
checkAndInstallBlack();
// Add the disposable to the context for cleanup
context.subscriptions.push(disposable);
}
exports.activate = activate;
I tried to add comment lines as I have progressed. I hope this helps anyone who is trying to do something like that in any way.
For those who are interested in what I did, It was put the file in a default formatter for python externally and get it in a temporary file. After formatting it in temporary file get the contents then delete the temporary file and proceed with my logic of formatting on that formatted version with keeping indentation on It's lines but adjust my operationNames
accordingly to my liking. In extra, try_end
was end of if statement in the Warband Scripting Language so I did an exception for that as you may have noticed with else_try
. Then I pushed these to the format and run those for the document. If I explained wrong please let me know in the comments. Therefore thank you all!