dcs-lua-runner/src/extension.ts
2024-05-11 23:22:53 -04:00

277 lines
13 KiB
TypeScript

import * as vscode from 'vscode';
import axios from 'axios';
import * as fs from 'fs';
import * as path from 'path';
async function runLua(lua: string, outputChannel: vscode.OutputChannel, filename: string = 'none') {
const lua_base64 = Buffer.from(lua).toString('base64');
const config = vscode.workspace.getConfiguration('dcsLuaRunner');
const returnDisplay = config.get('returnDisplay') === 'Output Panel (Scrolling Plain Text)' ? 'output' : 'file' as string;
const returnDisplayFormat = config.get('returnDisplayFormat') === 'JSON' ? 'json' : 'lua' as string;
const runCodeLocally = config.get('runCodeLocally') as boolean;
const runInMissionEnv = config.get('runInMissionEnv') as boolean;
const serverAddressMission = runCodeLocally ? '127.0.0.1' : config.get('serverAddress') as string;
const serverAddressGUI = (runCodeLocally || !config.get('serverAddressGUI')) ? serverAddressMission : config.get('serverAddressGUI') as string;
const serverAddress = runInMissionEnv ? serverAddressMission : serverAddressGUI;
const serverPortMission = runCodeLocally ? 12080 : config.get('serverPort') as number
const serverPortGUI = (runCodeLocally || !config.get('serverPortGUI')) ? (serverPortMission + 1) : config.get('serverPortGUI') as number;
const serverPort = runInMissionEnv ? serverPortMission : serverPortGUI;
const useHttps = runCodeLocally ? false : config.get('useHttps') as boolean;
const authUsername = config.get('webAuthUsername') as string;
const authPassword = config.get('webAuthPassword') as string;
const protocol = useHttps ? 'https' : 'http';
const envName = runInMissionEnv ? 'Mission' : 'GUI';
const logInfo = `${envName}@${serverAddress}:${serverPort} <- ${filename}`
const displayOutput = async (output: Object) => {
if (returnDisplay === 'output') {
outputChannel.show(true);
outputChannel.appendLine(`[DCS] ${new Date().toLocaleString()} (${logInfo}):\n${format(JSON.stringify(output, null, 4))}`);
} else if (returnDisplay === 'file') {
const activeEditor = vscode.window.activeTextEditor;
const workspaceFolder = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : '';
const vscodeFolderPath = `${workspaceFolder}/.vscode`;
const filePath = `${vscodeFolderPath}/dcs_lua_output.${returnDisplayFormat}`;
// Create the .vscode directory if it doesn't exist
if (!fs.existsSync(vscodeFolderPath)) {
fs.mkdirSync(vscodeFolderPath);
}
// Create the file if it doesn't exist
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, '');
}
const document = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath));
const viewColumn = vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn === vscode.ViewColumn.Two ? vscode.ViewColumn.Two : vscode.ViewColumn.Beside;
const editor = await vscode.window.showTextDocument(document, viewColumn);
await editor.edit(editBuilder => {
editBuilder.replace(new vscode.Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end), format(JSON.stringify(output, null, 4)));
});
await document.save();
if (activeEditor) {
vscode.window.showTextDocument(activeEditor.document, activeEditor.viewColumn);
}
}
}
const format = (jsonString: string): string => {
if (returnDisplayFormat === 'json') {
return jsonString;
}
let luaString = jsonString;
// Replace JSON syntax with Lua syntax
luaString = luaString.replace(/null/g, 'nil'); // Replace null with nil
luaString = luaString.replace(/"((?:[^"\\]|\\.)+)":/g, '["$1"] ='); // Replace "key": with ["key"] =
luaString = luaString.replace(/\["_([0-9]+)"\]\s*=/g, '[$1] ='); // Replace ["_n"] = with [n] =
luaString = luaString.replace(/\[\]/g, '{}'); // Replace [] with {}
luaString = luaString.replace(/\[\n/g, '{\n'); // Replace [ followed by a line break with { followed by a line break
luaString = luaString.replace(/]\n/g, '}\n'); // Replace ] followed by a line break with } followed by a line break
luaString = luaString.replace(/],/g, '},'); // Replace ], with },
luaString = luaString.replace(/]\s*$/g, '}'); // Replace ] at the end of the string with }
if (returnDisplay === 'file') {
luaString = `return --${logInfo}\n` + luaString;
}
return luaString;
}
try {
const response = await axios.get(`${protocol}://${serverAddress}:${serverPort}/${lua_base64}?env=default`, {
auth: {
username: authUsername,
password: authPassword
},
timeout: 3000
});
if (response.data.hasOwnProperty('result')) {
displayOutput(response.data.result);
} else {
displayOutput("SUCCESSFUL EXECUTION - NO RETURN VALUE");
}
} catch (error: any) {
if (error.response && error.response.status === 500) {
vscode.window.showErrorMessage('Internal server error occurred.');
displayOutput(error.response.data.error);
} else {
vscode.window.showErrorMessage(`Error: ${error}`);
}
}
}
function getCurrentFileLua() {
const editor = vscode.window.activeTextEditor;
if (editor) {
const document = editor.document;
if (document.languageId === 'lua') {
const lua = document.getText();
const filename = path.basename(document.uri.fsPath);
return { lua, filename };
} else {
vscode.window.showErrorMessage('The active file is not a Lua file.');
}
} else {
vscode.window.showErrorMessage('No active file.');
}
return null;
}
export function activate(context: vscode.ExtensionContext) {
// context.globalState.update('firstRunDone', false);
const isFirstRun = !context.globalState.get('firstRunDone');
if (isFirstRun) {
context.globalState.update('firstRunDone', true);
const installMdPath = vscode.Uri.file(context.asAbsolutePath('INSTALL.md'));
vscode.commands.executeCommand('markdown.showPreview', installMdPath);
}
const config = vscode.workspace.getConfiguration('dcsLuaRunner');
let outputChannel = vscode.window.createOutputChannel("DCS Lua Runner");
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.open-settings', () => {
vscode.commands.executeCommand('workbench.action.openSettings', 'dcsLuaRunner');
}));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.get-theatre', async () => {
const lua = 'return env.mission.theatre';
await runLua(lua, outputChannel, 'env.mission.theatre');
}));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.run-file', async () => {
const currentFileLua = getCurrentFileLua();
if (currentFileLua) {
const { lua, filename } = currentFileLua;
await runLua(lua, outputChannel, filename);
}
}));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.run-selected', async () => {
const editor = vscode.window.activeTextEditor;
if (editor && editor.selection) {
const document = editor.document;
const selection = editor.selection;
const lua = document.getText(selection);
const start = selection.start.line + 1;
const end = selection.end.line + 1;
const lineNumbers = start === end ? start : `${start}-${end}`;
const filename = path.basename(document.uri.fsPath) + ':' + lineNumbers;
await runLua(lua, outputChannel, filename);
}
}));
const displayRunTarget = () => {
const config = vscode.workspace.getConfiguration('dcsLuaRunner');
const runCodeLocally = config.get('runCodeLocally') as boolean;
const runInMissionEnv = config.get('runInMissionEnv') as boolean;
const runEnv = runInMissionEnv ? 'mission' : 'GUI';
const serverAddressMission = runCodeLocally ? '127.0.0.1' : config.get('serverAddress') as string;
const serverAddressGUI = (runCodeLocally || !config.get('serverAddressGUI')) ? serverAddressMission : config.get('serverAddressGUI') as string;
const serverAddress = runInMissionEnv ? serverAddressMission : serverAddressGUI;
const serverPortMission = runCodeLocally ? 12080 : config.get('serverPort') as number
const serverPortGUI = (runCodeLocally || !config.get('serverPortGUI')) ? (serverPortMission + 1) : config.get('serverPortGUI') as number;
const serverPort = runInMissionEnv ? serverPortMission : serverPortGUI;
const useHttps = runCodeLocally ? false : config.get('useHttps') as boolean;
const protocol = useHttps ? 'https' : 'http';
if (config.get('returnDisplay') === 'Console Output') {
outputChannel.show(true);
outputChannel.appendLine(`[DCS] Settings: Run code in ${runEnv} environment on ${protocol}://${serverAddress}:${serverPort}.`);
} else {
vscode.window.showInformationMessage(`Run code in ${runEnv} environment on ${protocol}://${serverAddress}:${serverPort}`);
}
};
const updateSetting = async (setting: string, targetState: boolean) => {
const config = vscode.workspace.getConfiguration('dcsLuaRunner');
if (setting === 'runCodeLocally' && targetState === false && !config.get('serverAddress')) {
vscode.window.showErrorMessage('Remote DCS server address not set.', 'Open Settings').then((choice) => {
if (choice === 'Open Settings') {
vscode.commands.executeCommand('workbench.action.openSettings', 'dcsLuaRunner');
}
});
await config.update(setting, true, vscode.ConfigurationTarget.Global);
return;
} else {
await config.update(setting, targetState, vscode.ConfigurationTarget.Global);
displayRunTarget();
}
};
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-local', () => updateSetting('runCodeLocally', true)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-local-button', () => updateSetting('runCodeLocally', true)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-remote', () => updateSetting('runCodeLocally', false)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-remote-button', () => updateSetting('runCodeLocally', false)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-missionEnv', () => updateSetting('runInMissionEnv', true)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-missionEnv-button', () => updateSetting('runInMissionEnv', true)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-guiEnv', () => updateSetting('runInMissionEnv', false)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.set-guiEnv-button', () => updateSetting('runInMissionEnv', false)));
context.subscriptions.push(vscode.commands.registerCommand('dcs-lua-runner.show-setup-guide', () => {
const installMdPath = vscode.Uri.file(context.asAbsolutePath('INSTALL.md'));
vscode.commands.executeCommand('markdown.showPreview', installMdPath);
}));
// Update the 'luaFileActive' context when the active editor changes
vscode.window.onDidChangeActiveTextEditor(editor => {
if (editor) {
vscode.commands.executeCommand('setContext', 'luaFileActive', editor.document.languageId === 'lua');
}
});
// Set the 'luaFileActive' context for the current active editor
if (vscode.window.activeTextEditor) {
vscode.commands.executeCommand('setContext', 'luaFileActive', vscode.window.activeTextEditor.document.languageId === 'lua');
}
let statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
// statusBarItem.tooltip = 'DCS Lua Runner: Click to change settings';
statusBarItem.show();
statusBarItem.command = 'extension.showQuickPick';
context.subscriptions.push(statusBarItem);
function updateStatusBarItem() {
const config = vscode.workspace.getConfiguration('dcsLuaRunner');
const editor = vscode.window.activeTextEditor;
const isLuaFile = editor && editor.document.languageId === 'lua';
const runCodeLocally = config.get('runCodeLocally') ? 'Local' : 'Remote';
const runInMissionEnv = config.get('runInMissionEnv') ? 'Mission' : 'GUI';
if (isLuaFile) {
statusBarItem.text = `DCS: ${runCodeLocally}, Env: ${runInMissionEnv}`;
statusBarItem.show();
} else {
statusBarItem.hide();
}
}
vscode.commands.registerCommand('extension.showQuickPick', () => {
const items = [
{ label: 'DCS Lua: Set Run Code on Local Machine', command: 'dcs-lua-runner.set-local' },
{ label: 'DCS Lua: Set Run Code on Remote Server', command: 'dcs-lua-runner.set-remote' },
{ label: 'DCS Lua: Set Run Code in Mission Environment', command: 'dcs-lua-runner.set-missionEnv' },
{ label: 'DCS Lua: Set Run Code in GUI Environment', command: 'dcs-lua-runner.set-guiEnv' },
{ label: 'DCS Lua: Open Settings', command: 'dcs-lua-runner.open-settings' },
{ label: 'DCS Lua: Show Setup Guide', command: 'dcs-lua-runner.show-setup-guide'}
];
vscode.window.showQuickPick(items).then(selection => {
// the user picked an item from the list
if (!selection) {
return;
}
// execute the selected command
vscode.commands.executeCommand(selection.command);
});
});
// Update the status bar when the configuration changes or the active text editor changes
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(updateStatusBarItem));
context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(updateStatusBarItem));
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(updateStatusBarItem));
context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(updateStatusBarItem));
updateStatusBarItem();
}
export function deactivate() {}