450 lines
17 KiB
TypeScript
450 lines
17 KiB
TypeScript
// The module 'vscode' contains the VS Code extensibility API
|
|
// Import the module and reference it with the alias vscode in your code below
|
|
import * as vscode from 'vscode';
|
|
import { ScriptCompiler, ScriptCompilerOptions, CompilationError, ICompilationLogger } from 'dcs-script-compiler';
|
|
import { Logger } from './logger';
|
|
import * as luaAddonsManager from './lua-addons-manager';
|
|
|
|
const luaWorkSpaceSettingKey = "Lua.workspace";
|
|
const librarySettingsKey = "library";
|
|
const diagnosticCollection = vscode.languages.createDiagnosticCollection('lua-transpiler');
|
|
|
|
const logger = new Logger();
|
|
|
|
const extensionName = 'dutchies-dcs-scripting-tools';
|
|
const publisherName = 'dutchie031';
|
|
const extensionSettingsFilter = `@ext:${publisherName}.${extensionName}`;
|
|
|
|
// State tracking for addon updates
|
|
let extensionPath: string | undefined;
|
|
let workspaceRoot: string | undefined;
|
|
let currentExtensionVersion: string | undefined;
|
|
let installedAddonPaths: Map<string, string> = new Map();
|
|
let isUpdatingAddons = false;
|
|
|
|
//TODO:
|
|
// - ENABLE/DISABLE with settings instead of commands (or both)
|
|
|
|
// This method is called when your extension is activated
|
|
// Your extension is activated the very first time the command is executed
|
|
export async function activate(context: vscode.ExtensionContext) {
|
|
|
|
extensionPath = context.extensionPath;
|
|
workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
|
|
// Initialize extension version
|
|
try {
|
|
currentExtensionVersion = await luaAddonsManager.getExtensionVersion(extensionPath);
|
|
logger.debug(`Extension version: ${currentExtensionVersion}`);
|
|
} catch (err) {
|
|
logger.error(`Failed to determine extension version: ${err instanceof Error ? err.message : String(err)}`);
|
|
}
|
|
|
|
context.subscriptions.push(
|
|
vscode.commands.registerCommand('dutchies-dcs-scripting-tools.enable', async () => {
|
|
await enableIntellisense();
|
|
})
|
|
);
|
|
|
|
context.subscriptions.push(
|
|
vscode.commands.registerCommand('dutchies-dcs-scripting-tools.disable', async () => {
|
|
await disableIntellisense();
|
|
})
|
|
);
|
|
|
|
context.subscriptions.push(
|
|
vscode.commands.registerCommand('dutchies-dcs-scripting-tools.openSettings', async () => {
|
|
await vscode.commands.executeCommand("workbench.action.openSettings", `${extensionSettingsFilter}`);
|
|
}));
|
|
|
|
context.subscriptions.push(
|
|
vscode.commands.registerCommand('dutchies-dcs-scripting-tools.compileLuaScripts', async () => {
|
|
try{
|
|
const start = Date.now();
|
|
await compileLuaScripts();
|
|
const end = Date.now();
|
|
vscode.window.showInformationMessage(`Lua scripts compiled successfully in ${(end - start) / 1000} seconds.`);
|
|
}catch(err){
|
|
vscode.window.showErrorMessage('Error compiling Lua scripts: ' + (err as Error).message);
|
|
}
|
|
})
|
|
);
|
|
|
|
vscode.workspace.onDidSaveTextDocument(async(document) => {
|
|
logger.info(`Document saved: ${document.uri.fsPath} | Language: ${document.languageId}`);
|
|
if (document.languageId === 'lua') {
|
|
const config = vscode.workspace.getConfiguration(extensionName);
|
|
const compileAt = config.get<string>('compileAt') || "undefined";
|
|
if (compileAt === 'onSave') {
|
|
await compileLuaScripts();
|
|
}
|
|
}
|
|
});
|
|
|
|
vscode.workspace.onDidChangeConfiguration(async(event) => {
|
|
if (event.affectsConfiguration(`${extensionName}.dcsTypes`)) {
|
|
const config = vscode.workspace.getConfiguration(extensionName);
|
|
const dcsTypesEnabled = config.get<boolean>('dcsTypes') || false;
|
|
if (dcsTypesEnabled) {
|
|
await updateLuaAddons();
|
|
} else {
|
|
await disableIntellisense();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Check and update lua-addons on activation if dcsTypes is enabled
|
|
await checkAndUpdateAddonsOnStartup();
|
|
|
|
logger.info('Dutchies DCS Scripting Tools extension activated');
|
|
}
|
|
|
|
// This method is called when your extension is deactivated
|
|
export async function deactivate()
|
|
{
|
|
// Clean up addon paths from settings on deactivation
|
|
try {
|
|
await removeVersionedPluginPathsFromSettings();
|
|
} catch (err) {
|
|
logger.error(`Error cleaning up on deactivation: ${err instanceof Error ? err.message : String(err)}`);
|
|
}
|
|
logger.info('Dutchies DCS Scripting Tools extension deactivated');
|
|
}
|
|
|
|
class CompilationLogger implements ICompilationLogger {
|
|
|
|
constructor(
|
|
private readonly logger: Logger) {
|
|
}
|
|
info(message: string): void {
|
|
this.logger.info(message);
|
|
}
|
|
error(message: string): void {
|
|
this.logger.error(message);
|
|
}
|
|
writeLine(message: string): void {
|
|
this.logger.log(message);
|
|
}
|
|
}
|
|
|
|
async function compileLuaScripts() {
|
|
logger.clear();
|
|
logger.info("Compiling...");
|
|
|
|
const config = vscode.workspace.getConfiguration(extensionName);
|
|
const sourcePath = config.get<string>('luaSrcDirectory') || '${workspaceFolder}/src';
|
|
const outputPath = config.get<string>('luaOutputPath') || '${workspaceFolder}/dist';
|
|
|
|
const resolvedSourcePath = sourcePath.replace('${workspaceFolder}', vscode.workspace.workspaceFolders?.[0].uri.fsPath || '');
|
|
const resolvedOutputPath = outputPath.replace('${workspaceFolder}', vscode.workspace.workspaceFolders?.[0].uri.fsPath || '');
|
|
|
|
diagnosticCollection.clear();
|
|
const errorsByFile = new Map<string, CompilationError[]>();
|
|
|
|
const options: ScriptCompilerOptions = {
|
|
sourcePath: resolvedSourcePath,
|
|
outputPath: resolvedOutputPath,
|
|
minify: false,
|
|
onError: (error: CompilationError) => {
|
|
const errors = errorsByFile.get(error.filePath) || [];
|
|
errors.push(error);
|
|
errorsByFile.set(error.filePath, errors);
|
|
}
|
|
};
|
|
|
|
const compilationLogger = new CompilationLogger(logger);
|
|
const compiler = new ScriptCompiler(options, compilationLogger);
|
|
|
|
const includeDevScript = config.get<boolean>('includeDevelopmentScript') || false;
|
|
|
|
try {
|
|
await compiler.compile(includeDevScript);
|
|
} catch (err) {
|
|
vscode.window.showErrorMessage('Compilation failed: ' + (err as Error).message);
|
|
logger.error('Compilation failed: ' + (err as Error).message + '\n' + (err as Error).stack);
|
|
}
|
|
|
|
// Update diagnostics
|
|
diagnosticCollection.clear();
|
|
for (const [filePath, errors] of errorsByFile) {
|
|
const uri = vscode.Uri.file(filePath);
|
|
const diagnostics = errors.map(error => {
|
|
const range = new vscode.Range(error.line, error.charStart ?? 0, error.line, error.charEnd ?? 0);
|
|
const diagnostic = new vscode.Diagnostic(range, error.message, vscode.DiagnosticSeverity.Error);
|
|
diagnostic.source = 'DCS Lua Transpiler';
|
|
return diagnostic;
|
|
});
|
|
diagnosticCollection.set(uri, diagnostics);
|
|
}
|
|
}
|
|
|
|
async function enableIntellisense() {
|
|
|
|
const config = vscode.workspace.getConfiguration(extensionName);
|
|
if (config.get<boolean>("dcsTypes") === false) {
|
|
config.update("dcsTypes", true, vscode.ConfigurationTarget.Workspace);
|
|
}
|
|
|
|
await updateLuaAddons();
|
|
await vscode.commands.executeCommand(
|
|
"lua.startServer"
|
|
);
|
|
vscode.window.showInformationMessage(`DCS-Types installed. Version ${currentExtensionVersion}.`);
|
|
}
|
|
|
|
async function disableIntellisense() {
|
|
const config = vscode.workspace.getConfiguration(extensionName);
|
|
if (config.get<boolean>("dcsTypes") === true) {
|
|
config.update("dcsTypes", false, vscode.ConfigurationTarget.Workspace);
|
|
}
|
|
|
|
await removeVersionedPluginPathsFromSettings();
|
|
|
|
// Clean up all addon versions from workspace
|
|
if (workspaceRoot) {
|
|
try {
|
|
const discoveredAddons = await luaAddonsManager.discoverLuaAddons(extensionPath || '');
|
|
for (const addonName of discoveredAddons.keys()) {
|
|
await luaAddonsManager.deleteAllVersionsOfAddon(workspaceRoot, addonName);
|
|
logger.debug(`Deleted all versions of ${addonName} from workspace.`);
|
|
}
|
|
} catch (err) {
|
|
logger.error(`Failed to clean up addons: ${err instanceof Error ? err.message : String(err)}`);
|
|
}
|
|
}
|
|
|
|
await vscode.commands.executeCommand(
|
|
"lua.startServer"
|
|
);
|
|
vscode.window.showInformationMessage('Dutchies DCS Scripting Tools disabled.');
|
|
}
|
|
|
|
/**
|
|
* Updates lua-addons by copying them from the extension bundle to the workspace
|
|
* with versioned naming, cleaning old versions, and updating Lua settings
|
|
*/
|
|
async function updateLuaAddons(): Promise<void> {
|
|
// Prevent concurrent update operations
|
|
if (isUpdatingAddons) {
|
|
logger.debug('Addon update already in progress, skipping.');
|
|
return;
|
|
}
|
|
|
|
if (!extensionPath || !workspaceRoot || !currentExtensionVersion) {
|
|
logger.warn('Cannot update addons: extension path, workspace root, or version not available.');
|
|
return;
|
|
}
|
|
|
|
isUpdatingAddons = true;
|
|
|
|
try {
|
|
logger.info('Starting lua-addons update...');
|
|
|
|
// Discover bundled addons
|
|
const discoveredAddons = await luaAddonsManager.discoverLuaAddons(extensionPath);
|
|
|
|
if (discoveredAddons.size === 0) {
|
|
logger.info('No lua-addons found in extension bundle.');
|
|
isUpdatingAddons = false;
|
|
return;
|
|
}
|
|
|
|
logger.debug(`Discovered ${discoveredAddons.size} lua-addon(s): ${Array.from(discoveredAddons.keys()).join(', ')}`);
|
|
|
|
// Copy addons with versioned names
|
|
const copiedAddons = await luaAddonsManager.copyLuaAddons(
|
|
extensionPath,
|
|
workspaceRoot,
|
|
currentExtensionVersion,
|
|
discoveredAddons
|
|
);
|
|
|
|
// Store paths for settings management
|
|
installedAddonPaths = copiedAddons;
|
|
logger.debug(`Copied ${copiedAddons.size} addon(s) to workspace.`);
|
|
|
|
// Clean up old versions
|
|
for (const addonName of discoveredAddons.keys()) {
|
|
await luaAddonsManager.deleteOldVersions(workspaceRoot, addonName, currentExtensionVersion);
|
|
logger.debug(`Cleaned old versions of ${addonName}.`);
|
|
}
|
|
|
|
// Update Lua settings with new paths
|
|
await addVersionedPluginPathsToSettings(copiedAddons);
|
|
|
|
logger.info(`Lua-addons update completed successfully (version ${currentExtensionVersion}).`);
|
|
|
|
} catch (err) {
|
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
logger.error(`Failed to update lua-addons: ${errorMsg}`);
|
|
vscode.window.showErrorMessage(`Failed to update Dcs Lua Types: ${errorMsg}`);
|
|
} finally {
|
|
isUpdatingAddons = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks on extension startup if lua-addons need to be updated
|
|
* Runs only if dcsTypes is enabled in the workspace
|
|
*/
|
|
async function checkAndUpdateAddonsOnStartup(): Promise<void> {
|
|
if (!extensionPath || !workspaceRoot || !currentExtensionVersion) {
|
|
logger.debug('Skipping addon version check on startup: missing initialization data.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const config = vscode.workspace.getConfiguration(extensionName);
|
|
const dcsTypesEnabled = config.get<boolean>('dcsTypes') || false;
|
|
|
|
if (!dcsTypesEnabled) {
|
|
logger.debug('dcsTypes not enabled, skipping addon check on startup.');
|
|
return;
|
|
}
|
|
|
|
logger.debug('Checking lua-addons versions on startup...');
|
|
|
|
// Get installed addons
|
|
const installedAddons = await luaAddonsManager.getInstalledAddons(workspaceRoot);
|
|
const discoveredAddons = await luaAddonsManager.discoverLuaAddons(extensionPath);
|
|
|
|
// Check each discovered addon
|
|
let updateNeeded = false;
|
|
for (const addonName of discoveredAddons.keys()) {
|
|
const installed = installedAddons.get(addonName);
|
|
const installedVersion = installed?.version;
|
|
|
|
if (luaAddonsManager.requiresUpdate(installedVersion, currentExtensionVersion)) {
|
|
logger.info(
|
|
`Version mismatch for ${addonName}: installed=${installedVersion || 'none'}, current=${currentExtensionVersion}`
|
|
);
|
|
updateNeeded = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (updateNeeded) {
|
|
logger.info('Lua-addons update needed, prompting user...');
|
|
const userChoice = await vscode.window.showInformationMessage(
|
|
'DCS-Types are not up to date with the current extension version. Do you want to update them?',
|
|
{ modal: false },
|
|
'Update'
|
|
);
|
|
|
|
if (userChoice === 'Update') {
|
|
await updateLuaAddons();
|
|
vscode.window.showInformationMessage(`DCS-Types updated to version ${currentExtensionVersion}.`);
|
|
} else {
|
|
logger.info('User declined DCS-Types update.');
|
|
}
|
|
} else {
|
|
logger.debug('Lua-addons versions are current, no update needed.');
|
|
// Ensure paths are in settings even if no update needed
|
|
const pathsMap = new Map<string, string>();
|
|
for (const [name, info] of installedAddons.entries()) {
|
|
pathsMap.set(name, info.path);
|
|
}
|
|
installedAddonPaths = pathsMap;
|
|
await addVersionedPluginPathsToSettings(installedAddonPaths);
|
|
}
|
|
|
|
} catch (err) {
|
|
logger.error(
|
|
`Error checking lua-addons on startup: ${err instanceof Error ? err.message : String(err)}`
|
|
);
|
|
// Don't fail activation if check fails; user can manually enable/disable
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts an absolute path to a workspace-relative path using ${workspaceFolder}
|
|
*/
|
|
function convertToWorkspaceFolderPath(absolutePath: string): string {
|
|
if (!workspaceRoot) {
|
|
return absolutePath;
|
|
}
|
|
// Normalize paths for comparison (handle both forward and back slashes)
|
|
const normalizedAbsolute = absolutePath.replace(/\\/g, '/');
|
|
const normalizedWorkspace = workspaceRoot.replace(/\\/g, '/');
|
|
|
|
if (normalizedAbsolute.startsWith(normalizedWorkspace)) {
|
|
const relativePath = normalizedAbsolute.substring(normalizedWorkspace.length);
|
|
// Remove leading slash if present
|
|
const cleanPath = relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
|
|
return `\${workspaceFolder}/${cleanPath}`;
|
|
}
|
|
return absolutePath;
|
|
}
|
|
|
|
/**
|
|
* Adds versioned addon paths to the Lua workspace library settings
|
|
* @param addonPaths - Map of addon name to installed path
|
|
*/
|
|
async function addVersionedPluginPathsToSettings(addonPaths?: Map<string, string>): Promise<void> {
|
|
const pathsToAdd = addonPaths || installedAddonPaths;
|
|
|
|
if (pathsToAdd.size === 0) {
|
|
logger.debug('No addon paths to add to settings.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const luaSettings = vscode.workspace.getConfiguration(luaWorkSpaceSettingKey);
|
|
let librarySettings = luaSettings.get<string[]>(librarySettingsKey) || [];
|
|
|
|
// Add each addon path if not already present
|
|
for (const addonPath of pathsToAdd.values()) {
|
|
const workspaceFolderPath = convertToWorkspaceFolderPath(addonPath);
|
|
if (!librarySettings.includes(workspaceFolderPath)) {
|
|
librarySettings.push(workspaceFolderPath);
|
|
logger.debug(`Added addon path to settings: ${workspaceFolderPath}`);
|
|
}
|
|
}
|
|
|
|
await luaSettings.update(librarySettingsKey, librarySettings, vscode.ConfigurationTarget.Workspace);
|
|
} catch (err) {
|
|
logger.error(`Failed to add addon paths to settings: ${err instanceof Error ? err.message : String(err)}`);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes all addon paths from the Lua workspace library settings
|
|
*/
|
|
async function removeVersionedPluginPathsFromSettings(): Promise<void> {
|
|
try {
|
|
const luaSettings = vscode.workspace.getConfiguration(luaWorkSpaceSettingKey);
|
|
let librarySettings = luaSettings.get<string[]>(librarySettingsKey) || [];
|
|
|
|
if (workspaceRoot) {
|
|
// Get all installed addons to know what paths to remove
|
|
const installedAddons = await luaAddonsManager.getInstalledAddons(workspaceRoot);
|
|
|
|
for (const addonInfo of installedAddons.values()) {
|
|
// Try both absolute and workspace-relative paths for compatibility
|
|
const absolutePath = addonInfo.path;
|
|
const relativePath = convertToWorkspaceFolderPath(absolutePath);
|
|
|
|
// Remove absolute path if present
|
|
let index = librarySettings.indexOf(absolutePath);
|
|
if (index !== -1) {
|
|
librarySettings.splice(index, 1);
|
|
logger.debug(`Removed addon path from settings: ${absolutePath}`);
|
|
}
|
|
|
|
// Remove relative path if present
|
|
index = librarySettings.indexOf(relativePath);
|
|
if (index !== -1) {
|
|
librarySettings.splice(index, 1);
|
|
logger.debug(`Removed addon path from settings: ${relativePath}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
await luaSettings.update(librarySettingsKey, librarySettings, vscode.ConfigurationTarget.Workspace);
|
|
} catch (err) {
|
|
logger.error(`Failed to remove addon paths from settings: ${err instanceof Error ? err.message : String(err)}`);
|
|
throw err;
|
|
}
|
|
}
|