| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import * as vscode from 'vscode' |
| import * as fs from 'fs' |
| import { getConfig } from '../utils' |
| import * as os from 'os' |
| |
| const defaultConf = getConfig('Wizard Config', 'launch', 'dfdl') |
| |
| // Function that will activate/open the launch config wizard |
| export async function activate(ctx: vscode.ExtensionContext) { |
| ctx.subscriptions.push( |
| vscode.commands.registerCommand('launch.config', async () => { |
| await createWizard(ctx) |
| }) |
| ) |
| } |
| |
| // Function to get config values |
| function getConfigValues(data, configIndex) { |
| return data && configIndex !== -1 |
| ? data.configurations[configIndex] |
| : defaultConf |
| } |
| |
| /** |
| * Function used for: |
| * Creating the launch.json with the desired config settings |
| * Updating the launch.json configs to have the desired config settings |
| * Updating an already avaiable config inside of launch.json |
| */ |
| async function createUpdateConfigFile(data, updateOrCreate) { |
| let rootPath = vscode.workspace.workspaceFolders |
| ? vscode.workspace.workspaceFolders[0].uri.fsPath |
| : vscode.Uri.parse('').fsPath |
| |
| if (!fs.existsSync(`${rootPath}/.vscode`)) { |
| fs.mkdirSync(`${rootPath}/.vscode`) |
| } |
| |
| const launchPath = |
| os.platform() === 'win32' |
| ? `/${rootPath}/.vscode/launch.json` |
| : `${rootPath}/.vscode/launch.json` |
| |
| // Create launch.json if it doesn't exist already |
| if (!fs.existsSync(`${rootPath}/.vscode/launch.json`)) { |
| fs.writeFileSync(`${rootPath}/.vscode/launch.json`, data) |
| vscode.window.showTextDocument(vscode.Uri.parse(launchPath)) |
| return |
| } |
| |
| let newConf = JSON.parse(data).configurations[0] |
| let fileData = JSON.parse( |
| fs.readFileSync(`${rootPath}/.vscode/launch.json`).toString() |
| ) |
| |
| if (updateOrCreate.toLowerCase() === 'create') { |
| let alreadyExists = false |
| |
| fileData.configurations.forEach((element) => { |
| if (element.name === newConf.name) { |
| alreadyExists = true |
| } |
| }) |
| |
| if (alreadyExists) { |
| // Config wanted to create already exists in launch.json |
| vscode.window.showWarningMessage( |
| 'The conf trying to be saved already exists in the launch.json' |
| ) |
| return |
| } |
| |
| // Add new config to launch.json |
| fileData.configurations.push(newConf) |
| fs.writeFileSync( |
| `${rootPath}/.vscode/launch.json`, |
| JSON.stringify(fileData, null, 4) |
| ) |
| } else if (updateOrCreate.toLowerCase() === 'update') { |
| // Update selected config in launch.json |
| let configIndex = -1 |
| |
| for (var i = 0; i < fileData.configurations.length; i++) { |
| if (fileData.configurations[i].name === newConf.name) { |
| configIndex = i |
| break |
| } |
| } |
| |
| // Error handling to make sure a proper config specified |
| if (configIndex !== -1) { |
| fileData.configurations[configIndex] = newConf |
| fs.writeFileSync( |
| `${rootPath}/.vscode/launch.json`, |
| JSON.stringify(fileData, null, 4) |
| ) |
| } else { |
| vscode.window.showErrorMessage('Invalid Config Selected!') |
| } |
| } |
| |
| vscode.window.showTextDocument(vscode.Uri.parse(launchPath)) |
| } |
| |
| // Function to update the config values in the webview panel |
| async function updateWebViewConfigValues(configIndex) { |
| let rootPath = vscode.workspace.workspaceFolders |
| ? vscode.workspace.workspaceFolders[0].uri.fsPath |
| : vscode.Uri.parse('').fsPath |
| |
| let fileData = |
| configIndex === -1 |
| ? null |
| : JSON.parse( |
| fs.readFileSync(`${rootPath}/.vscode/launch.json`).toString() |
| ) |
| |
| return getConfigValues(fileData, configIndex) |
| } |
| |
| // Function to create file picker for wizard |
| async function openFilePicker(description) { |
| let rootPath = vscode.workspace.workspaceFolders |
| ? vscode.workspace.workspaceFolders[0].uri.fsPath |
| : vscode.Uri.parse('').fsPath |
| |
| let canSelectMany = description.includes('jar files/folder') ? true : false |
| let canSelectFolders = description.includes('jar files/folder') ? true : false |
| |
| let chosenFile = await vscode.window |
| .showOpenDialog({ |
| canSelectMany: canSelectMany, |
| openLabel: description, |
| canSelectFiles: true, |
| canSelectFolders: canSelectFolders, |
| title: description, |
| }) |
| .then(async (fileUri) => { |
| if (fileUri && fileUri[0]) { |
| let stats = fs.statSync(fileUri[0].fsPath) |
| |
| if (stats.isDirectory()) { |
| let basePath = fileUri[0].fsPath |
| let fileString = '' |
| |
| fs.readdirSync(basePath).forEach((file) => { |
| if (file.includes('.jar')) { |
| fileString += `${basePath}/${file},` |
| } |
| }) |
| |
| return fileString.substring(0, fileString.length - 1) // make sure to remove comma at the end of the string |
| } |
| |
| if (fileUri.length === 1) { |
| return fileUri[0].fsPath |
| } else { |
| let files = '' |
| |
| fileUri.forEach((file) => { |
| if (file.fsPath.includes('.jar')) { |
| files += `${file.fsPath},` |
| } |
| }) |
| |
| return files.substring(0, files.length - 1) // make sure to remove comma at the end of the string |
| } |
| } |
| |
| return '' |
| }) |
| |
| if (chosenFile.includes(rootPath)) { |
| let regExp = new RegExp(rootPath, 'g') |
| chosenFile = chosenFile.replace(regExp, '${workspaceFolder}') |
| } |
| |
| return chosenFile |
| } |
| |
| // Function that will create webview |
| async function createWizard(ctx: vscode.ExtensionContext) { |
| let launchWizard = new LaunchWizard(ctx) |
| let panel = launchWizard.getPanel() |
| panel.webview.html = launchWizard.getWebViewContent() |
| |
| panel.webview.onDidReceiveMessage( |
| async (message) => { |
| switch (message.command) { |
| case 'saveConfig': |
| await createUpdateConfigFile(message.data, message.updateOrCreate) |
| panel.dispose() |
| return |
| case 'updateConfigValue': |
| let configValues = await updateWebViewConfigValues( |
| message.configIndex |
| ) |
| |
| panel.webview.postMessage({ |
| command: 'updateConfValues', |
| configValues: configValues, |
| }) |
| return |
| case 'openFilePicker': |
| let result = await openFilePicker(message.description) |
| panel.webview.postMessage({ |
| command: `${message.id}Update`, |
| value: result, |
| }) |
| return |
| } |
| }, |
| undefined, |
| ctx.subscriptions |
| ) |
| } |
| |
| // Class for creating launch wizard webview |
| class LaunchWizard { |
| ctx: vscode.ExtensionContext |
| panel: vscode.WebviewPanel | undefined = undefined |
| extensionUri: vscode.Uri = vscode.Uri.parse('') |
| |
| constructor(ctx: vscode.ExtensionContext) { |
| this.ctx = ctx |
| |
| if (vscode.workspace.workspaceFolders) { |
| this.extensionUri = vscode.workspace.workspaceFolders[0].uri |
| } |
| } |
| |
| // Method to create/get the webview panel |
| getPanel() { |
| if (!this.panel) { |
| this.panel = vscode.window.createWebviewPanel( |
| 'launchWizard', |
| 'Launch Config Wizard', |
| vscode.ViewColumn.Active, |
| this.getWebViewOptions(this.extensionUri) |
| ) |
| |
| this.panel.onDidDispose( |
| () => { |
| this.panel = undefined |
| }, |
| null, |
| this.ctx.subscriptions |
| ) |
| } |
| |
| return this.panel |
| } |
| |
| // Method for creating web view option that allow our custom script to run |
| getWebViewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions { |
| return { |
| enableScripts: true, |
| localResourceRoots: [ |
| vscode.Uri.parse(this.ctx.asAbsolutePath('./src/launchWizard')), |
| ], |
| } |
| } |
| |
| // Method to set html for the hex view |
| getWebViewContent() { |
| const scriptUri = vscode.Uri.parse( |
| this.ctx.asAbsolutePath('./src/launchWizard/launchWizard.js') |
| ) |
| const styleUri = vscode.Uri.parse( |
| this.ctx.asAbsolutePath('./src/styles/styles.css') |
| ) |
| const scriptData = fs.readFileSync(scriptUri.fsPath) |
| const styleData = fs.readFileSync(styleUri.fsPath) |
| |
| const nonce = this.getNonce() |
| |
| let rootPath = vscode.workspace.workspaceFolders |
| ? vscode.workspace.workspaceFolders[0].uri.fsPath |
| : vscode.Uri.parse('').fsPath |
| |
| let configSelect = '' |
| let newConfig = fs.existsSync(`${rootPath}/.vscode/launch.json`) |
| ? false |
| : true |
| let configIndex = fs.existsSync(`${rootPath}/.vscode/launch.json`) ? 0 : -1 |
| let fileData = JSON.parse('{}') |
| |
| if (fs.existsSync(`${rootPath}/.vscode/launch.json`)) { |
| fileData = JSON.parse( |
| fs.readFileSync(`${rootPath}/.vscode/launch.json`).toString() |
| ) |
| |
| fileData.configurations.forEach((element) => { |
| configSelect += `<option value="${element.name}">${element.name}</option>` |
| }) |
| } |
| |
| let defaultValues = getConfigValues(fileData, configIndex) |
| |
| let nameVisOrHiddenStyle = newConfig |
| ? 'margin-top: 10px; visibility: visible;' |
| : 'visibility: hidden;' |
| |
| configSelect += `<option value="New Config">New Config</option>` |
| |
| let openHexView = defaultValues.openHexView ? 'checked' : '' |
| let openInfosetDiffView = defaultValues.openInfosetDiffView ? 'checked' : '' |
| let openInfosetView = defaultValues.openInfosetView ? 'checked' : '' |
| let stopOnEntry = defaultValues.stopOnEntry ? 'checked' : '' |
| let trace = defaultValues.trace ? 'checked' : '' |
| let useExistingServer = defaultValues.useExistingServer ? 'checked' : '' |
| let daffodilDebugClasspathAction = |
| defaultValues.daffodilDebugClasspathAction === 'append' |
| ? defaultConf.daffodilDebugClasspath |
| : 'replace' |
| let replaceCheck = |
| daffodilDebugClasspathAction !== 'append' ? 'checked' : '' |
| let appendCheck = daffodilDebugClasspathAction === 'append' ? 'checked' : '' |
| |
| let infosetOutputTypeSelect = '' |
| let infosetOutputTypes = ['none', 'console', 'file'] |
| let infosetOutputType = defaultValues.infosetOutput['type'] |
| ? defaultValues.infosetOutput['type'] |
| : defaultValues.infosetOutputType |
| let infosetOutputPath = defaultValues.infosetOutput['path'] |
| ? defaultValues.infosetOutput['path'] |
| : defaultValues.infosetOutputFilePath |
| let infosetPathVisOrHiddenStyle = |
| infosetOutputType === 'file' |
| ? 'margin-top: 10px; visibility: visible;' |
| : 'visibility: hidden;' |
| |
| infosetOutputTypes.forEach((type) => { |
| if (type === infosetOutputType) { |
| infosetOutputTypeSelect += `<option selected value="${type}">${type}</option>` |
| } else { |
| infosetOutputTypeSelect += `<option value="${type}">${type}</option>` |
| } |
| }) |
| |
| return ` |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content=width=device-width, initial-scale=1.0"> |
| <title>Launch Config Wizard</title> |
| </head> |
| <body> |
| <style> |
| .container { |
| display: block; |
| position: relative; |
| margin: 0px; |
| margin-top: -10px; |
| padding-left: 35px; |
| margin-bottom: 25px; |
| cursor: pointer; |
| font-size: 14px; |
| font-style: italic; |
| -webkit-user-select: none; |
| -moz-user-select: none; |
| -ms-user-select: none; |
| user-select: none; |
| } |
| ${styleData} |
| </style> |
| <script nonce="${nonce}"> |
| ${scriptData} |
| </script> |
| <h2 style="color: inherit;">Daffodil Debugger Config Settings</h2> |
| |
| <div id="configSelectionDropDown" class="setting-div"> |
| <p>Launch Config:</p> |
| <p class="setting-description">Launch config to be updated in the launch.json. 'New Config' will allow for a new one to be made.</p> |
| <select onChange="updateSelectedConfig()" class="file-input" style="width: 200px;" id="configSelected"> |
| ${configSelect} |
| </select> |
| |
| <p style="${nameVisOrHiddenStyle}" id="nameLabel" class="setting-description"> |
| New Config Name: <input class="file-input" value="${defaultValues.name}" id="name"/> |
| </p> |
| </div> |
| |
| <div id="daffodilDebugClasspathDiv" class="setting-div"> |
| <p>Daffodil Debugger Classpath:</p> |
| <p class="setting-description">Additional classpaths to be added to the debugger.</p> |
| |
| <label class="container" style="margin-top: 10px; margin-bottom: 0px;">Replace value in input box with selected files. |
| <input type="checkbox" id="daffodilDebugClasspathReplace" ${replaceCheck} onclick="daffodilDebugClassAction('replace')"> |
| <span class="checkmark"></span> |
| </label> |
| |
| <label class="container" style="margin-top: 10px; margin-bottom: 10px;">Append selected files to value in input box. |
| <input type="checkbox" id="daffodilDebugClasspathAppend" ${appendCheck} onclick="daffodilDebugClassAction('append')"> |
| <span class="checkmark"></span> |
| </label> |
| |
| <input class="file-input" value="${defaultValues.daffodilDebugClasspath}" id="daffodilDebugClasspath"/> |
| <button id="daffodilDebugClasspathBrowse" class="browse-button" type="button" onclick="filePicker('daffodilDebugClasspath', 'Select jar files/folder with desired jars')">Browse</button> |
| </div> |
| |
| <div id="dataDiv" class="setting-div"> |
| <p>Data:</p> |
| <p class="setting-description">Absolute path to the input data file.</p> |
| <input class="file-input" value="${defaultValues.data}" id="data"/> |
| <button id="dataBrowse" class="browse-button" type="button" onclick="filePicker('data', 'Select input data file to debug')">Browse</button> |
| </div> |
| |
| <div id="debugServerDiv" class="setting-div"> |
| <p>Debug Server:</p> |
| <p class="setting-description">Port debug server running on.</p> |
| <input class="file-input" value="${defaultValues.debugServer}" id="debugServer"/> |
| </div> |
| |
| <div id="infosetOutputTypeDiv" class="setting-div"> |
| <p>Infoset Output Type:</p> |
| <p class="setting-description">Destination for final Infoset (file | 'console' | 'none')</p> |
| <select onChange="updateInfosetOutputType()" class="file-input" style="width: 200px;" id="infosetOutputType"> |
| ${infosetOutputTypeSelect} |
| </select> |
| |
| <p id="infosetOutputFilePathLabel" style="${infosetPathVisOrHiddenStyle}" class="setting-description"> |
| Output Infoset Path: <input class="file-input" value="${infosetOutputPath}" id="infosetOutputFilePath"/> |
| </p> |
| </div> |
| |
| <div id="openHexViewDiv" class="setting-div" onclick="check('openHexView')"> |
| <p>Open Hex View:</p> |
| <label class="container">Open hexview on debug start. |
| <input type="checkbox" id="openHexView" ${openHexView}> |
| <span class="checkmark"></span> |
| </label> |
| </div> |
| |
| <div id="openInfosetDiffViewDiv" class="setting-div" onclick="check('openInfosetDiffView')"> |
| <p>Open Infoset Diff View:</p> |
| <label class="container">Open hexview on debug start. |
| <input type="checkbox" id="openInfosetDiffView" ${openInfosetDiffView}> |
| <span class="checkmark"></span> |
| </label> |
| </div> |
| |
| <div id="openInfosetViewDiv" class="setting-div" onclick="check('openInfosetView')"> |
| <p>Open Infoset View:</p> |
| <label class="container">Open hexview on debug start. |
| <input type="checkbox" id="openInfosetView" ${openInfosetView}> |
| <span class="checkmark"></span> |
| </label> |
| </div> |
| |
| <div id="programDiv" class="setting-div"> |
| <p>Program:</p> |
| <p class="setting-description">Absolute path to the DFDL schema file.</p> |
| <input class="file-input" value="${defaultValues.program}" id="program"/> |
| <button id="programBrowse" class="browse-button" type="button" onclick="filePicker('program', 'Select DFDL schema to debug')">Browse</button> |
| </div> |
| |
| <div id="stopOnEntryDiv" class="setting-div" onclick="check('stopOnEntry')"> |
| <p>Stop On Entry:</p> |
| <label class="container">Automatically stop after launch. |
| <input type="checkbox" id="stopOnEntry" ${stopOnEntry}> |
| <span class="checkmark"></span> |
| </label> |
| </div> |
| |
| <div id="traceDiv" class="setting-div" onclick="check('trace')"> |
| <p>Trace:</p> |
| <label class="container">Enable logging of the Debug Adapter Protocol. |
| <input type="checkbox" id="trace" ${trace}> |
| <span class="checkmark"></span> |
| </label> |
| </div> |
| |
| <div id="useExistingServerDiv" class="setting-div" onclick="check('useExistingServer')"> |
| <p>Use Existing Server:</p> |
| <label class="container">Enable connection to running DAP Server. |
| <input type="checkbox" id="useExistingServer" ${useExistingServer}> |
| <span class="checkmark"></span> |
| </label> |
| </div> |
| |
| <button class="save-button" type="button" onclick="save()">Save</button> |
| </body> |
| </html>` |
| } |
| |
| // Method to get nonce, helps with running custom script |
| getNonce() { |
| let text = '' |
| const possible = |
| 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' |
| for (let i = 0; i < 32; i++) { |
| text += possible.charAt(Math.floor(Math.random() * possible.length)) |
| } |
| return text |
| } |
| } |