src/adapter/activateDaffodilDebug.ts (431 lines of code) (raw):
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict'
import * as fs from 'fs'
import * as path from 'path'
import * as vscode from 'vscode'
import {
CancellationToken,
DebugConfiguration,
ProviderResult,
WorkspaceFolder,
} from 'vscode'
import { getDataFileFromFolder, getDebugger } from '../daffodilDebugger'
import * as infoset from '../infoset'
import * as dfdlLang from '../language/dfdl'
import * as launchWizard from '../launchWizard/launchWizard'
import * as dataEditClient from '../dataEditor/dataEditorClient'
import { getConfig, getCurrentConfig, setCurrentConfig } from '../utils'
import { FileAccessor } from './daffodilRuntime'
import { TDMLConfig } from '../classes/tdmlConfig'
import { handleDebugEvent } from './daffodilEvent'
import { InlineDebugAdapterFactory } from './extension'
import * as dfdlExt from '../language/semantics/dfdlExt'
/** Method to file path for program and data
* Details:
* Required so that the vscode api commands:
* - extension.dfdl-debug.getProgramName
* - extension.dfdl-debug.getDataName
* can be sent a file instead of always opening up a prompt.
* Always makes it so the vscode api commands above are able
* to be tested inside of the test suite
*/
async function getFile(fileRequested, label, title) {
let file = ''
if (fileRequested && fs.existsSync(fileRequested)) {
file = fileRequested
} else if (fileRequested && !fs.existsSync(fileRequested)) {
file = ''
} else {
file = await vscode.window
.showOpenDialog({
canSelectMany: false,
openLabel: label,
canSelectFiles: true,
canSelectFolders: false,
title: title,
})
.then((fileUri) => {
if (fileUri && fileUri[0]) {
let path = fileUri[0].fsPath
if (
process.platform === 'win32' &&
path.length > 2 &&
path.charCodeAt(0) > 97 &&
path.charCodeAt(0) <= 122 &&
path.charAt(1) === ':'
) {
path = path.charAt(0).toUpperCase() + path.slice(1)
}
return path
}
return ''
})
}
return file
}
// Function for setting up the commands for Run and Debug file
function createDebugRunFileConfigs(
resource: vscode.Uri,
runOrDebug: String,
tdmlAction: String | undefined,
runLast = false
) {
let targetResource = resource
let noDebug = runOrDebug === 'run'
if (!targetResource && vscode.window.activeTextEditor) {
targetResource = vscode.window.activeTextEditor.document.uri
}
if (targetResource) {
let infosetFile = `${
path.basename(targetResource.fsPath).split('.')[0]
}-infoset.xml`
vscode.window.showInformationMessage(infosetFile)
let currentConfig = getCurrentConfig()
if (runLast && currentConfig) {
vscode.debug.startDebugging(undefined, currentConfig, {
noDebug: noDebug,
})
} else {
var tdmlConfig = <TDMLConfig>{}
if (tdmlAction) {
tdmlConfig.action = tdmlAction
}
vscode.debug.startDebugging(
undefined,
getConfig({
name: 'Run File',
request: 'launch',
type: 'dfdl',
program: targetResource.fsPath,
data: false,
debugServer: false,
infosetFormat: 'xml',
infosetOutput: {
type: 'file',
path: '${workspaceFolder}/' + infosetFile,
},
tdmlConfig: tdmlConfig,
}),
{ noDebug: noDebug }
)
}
vscode.debug.onDidTerminateDebugSession(async () => {
if (!vscode.workspace.workspaceFolders) {
return
}
vscode.workspace
.openTextDocument(
`${vscode.workspace.workspaceFolders[0].uri.fsPath}/${infosetFile}`
)
.then((doc) => {
vscode.window.showTextDocument(doc)
})
})
}
}
export function activateDaffodilDebug(
context: vscode.ExtensionContext,
factory?: vscode.DebugAdapterDescriptorFactory
) {
context.subscriptions.push(
vscode.commands.registerCommand(
'extension.dfdl-debug.runEditorContents',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'run', undefined)
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.debugEditorContents',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'debug', undefined)
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.debugLastEditorContents',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'debug', undefined, true)
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.generateTDML',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'run', 'generate')
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.appendTDML',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'run', 'append')
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.executeTDML',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'run', 'execute')
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.toggleFormatting',
(_) => {
const ds = vscode.debug.activeDebugSession
if (ds) {
ds.customRequest('toggleFormatting')
}
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.getProgramName',
async (fileRequested = null) => {
// Open native file explorer to allow user to select data file from anywhere on their machine
return await getFile(
fileRequested,
'Select DFDL schema to debug',
'Select DFDL schema to debug'
)
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.getDataName',
async (fileRequested = null) => {
// Open native file explorer to allow user to select data file from anywhere on their machine
return await getFile(
fileRequested,
'Select input data file to debug',
'Select input data file to debug'
)
}
)
)
context.subscriptions.push(
vscode.commands.registerCommand(
'extension.dfdl-debug.getTDMLName',
async (_) => {
return await vscode.window
.showInputBox({
prompt: 'Test Case Name: ',
value: 'Default Test Case',
})
.then((value) => {
return value
})
}
)
)
context.subscriptions.push(
vscode.commands.registerCommand(
'extension.dfdl-debug.getTDMLDescription',
async (_) => {
return await vscode.window
.showInputBox({
prompt: 'Test Case Description: ',
value: 'Generated by DFDL VSCode Extension',
})
.then((value) => {
return value
})
}
)
)
context.subscriptions.push(
vscode.commands.registerCommand(
'extension.dfdl-debug.getTDMLPath',
async (_) => {
return await vscode.window
.showInputBox({
prompt: 'TDML File: ',
value: '${workspaceFolder}/infoset.tdml',
})
.then((value) => {
return value
})
}
)
)
context.subscriptions.push(
vscode.commands.registerCommand(
'extension.dfdl-debug.getValidatedTDMLPath',
async (fileRequested = null) => {
// Open native file explorer to allow user to select data file from anywhere on their machine
return await getFile(
fileRequested,
'Select TDML File',
'Select TDML File'
)
}
)
)
// register a configuration provider for 'dfdl' debug type
const provider = new DaffodilConfigurationProvider(context)
context.subscriptions.push(
// register a configuration provider for 'dfdl' debug type
vscode.debug.registerDebugConfigurationProvider('dfdl', provider),
// register a dynamic configuration provider for 'dfdl' debug type
vscode.debug.registerDebugConfigurationProvider(
'dfdl',
{
provideDebugConfigurations(
folder: WorkspaceFolder | undefined
): ProviderResult<DebugConfiguration[]> {
if (!vscode.workspace.workspaceFolders) {
return [
getConfig({
name: 'Daffodil Launch',
request: 'launch',
type: 'dfdl',
program: '${file}',
data: false,
debugServer: false,
infosetFormat: 'xml',
infosetOutput: {
type: 'file',
path: '${file}-infoset.xml',
},
}),
]
}
let targetResource = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.document.uri
: vscode.workspace.workspaceFolders[0].uri
let infosetFile = `${
path.basename(targetResource.fsPath).split('.')[0]
}-infoset.xml`
return [
getConfig({
name: 'Daffodil Launch',
request: 'launch',
type: 'dfdl',
program: '${file}',
data: false,
debugServer: false,
infosetFormat: 'xml',
infosetOutput: {
type: 'file',
path: '${workspaceFolder}/' + infosetFile,
},
}),
]
},
},
vscode.DebugConfigurationProviderTriggerKind.Dynamic
)
)
if (!factory) {
factory = new InlineDebugAdapterFactory(context)
}
context.subscriptions.push(
vscode.debug.registerDebugAdapterDescriptorFactory('dfdl', factory)
)
if ('dispose' in factory) {
context.subscriptions.push(factory as vscode.Disposable)
}
context.subscriptions.push(
// override VS Code's default implementation of the debug hover
vscode.languages.registerEvaluatableExpressionProvider('xml', {
provideEvaluatableExpression(
document: vscode.TextDocument,
position: vscode.Position
): vscode.ProviderResult<vscode.EvaluatableExpression> {
const wordRange = document.getWordRangeAtPosition(position)
return wordRange
? new vscode.EvaluatableExpression(wordRange)
: undefined
},
}),
// override VS Code's default implementation of the "inline values" feature"
vscode.languages.registerInlineValuesProvider('xml', {
provideInlineValues(
document: vscode.TextDocument,
viewport: vscode.Range,
context: vscode.InlineValueContext
): vscode.ProviderResult<vscode.InlineValue[]> {
const allValues: vscode.InlineValue[] = []
for (
let l = viewport.start.line;
l <= context.stoppedLocation.end.line;
l++
) {
const line = document.lineAt(l)
var regExp = /local_[ifso]/gi // match variables of the form local_i, local_f, Local_i, LOCAL_S...
do {
var m = regExp.exec(line.text)
if (m) {
const varName = m[0]
const varRange = new vscode.Range(
l,
m.index,
l,
m.index + varName.length
)
// value found via variable lookup
allValues.push(
new vscode.InlineValueVariableLookup(varRange, varName, false)
)
}
} while (m)
}
return allValues
},
})
)
context.subscriptions.push(
vscode.debug.onDidReceiveDebugSessionCustomEvent(handleDebugEvent)
)
dfdlLang.activate(context)
dfdlExt.activate(context)
infoset.activate(context)
dataEditClient.activate(context)
launchWizard.activate(context)
}
class DaffodilConfigurationProvider
implements vscode.DebugConfigurationProvider
{
context: vscode.ExtensionContext
constructor(context: vscode.ExtensionContext) {
this.context = context
}
/**
* Massage a debug configuration just before a debug session is being launched,
* e.g. add all missing attributes to the debug configuration.
*/
resolveDebugConfiguration(
folder: WorkspaceFolder | undefined,
config: DebugConfiguration,
token?: CancellationToken
): ProviderResult<DebugConfiguration> {
// if launch.json is missing or empty
if (!config.type && !config.request && !config.name) {
config = getConfig({ name: 'Launch', request: 'launch', type: 'dfdl' })
}
if (!config.debugServer) {
config.debugServer = 4711
}
if (!config.program) {
return vscode.window
.showInformationMessage('Cannot find a program to debug')
.then((_) => {
return undefined // abort launch
})
}
let dataFolder = config.data
if (
dataFolder.includes('${workspaceFolder}') &&
vscode.workspace.workspaceFolders &&
dataFolder.split('.').length === 1
) {
dataFolder = vscode.workspace.workspaceFolders[0].uri.fsPath
}
if (
!dataFolder.includes('${command:AskForProgramName}') &&
!dataFolder.includes('${command:AskForDataName}') &&
!dataFolder.includes('${workspaceFolder}') &&
vscode.workspace.workspaceFolders &&
dataFolder !== vscode.workspace.workspaceFolders[0].uri.fsPath &&
dataFolder.split('.').length === 1 &&
fs.lstatSync(dataFolder).isDirectory()
) {
return getDataFileFromFolder(dataFolder).then((dataFile) => {
config.data = dataFile
return getDebugger(this.context, config).then((updatedConfig) => {
return setCurrentConfig(updatedConfig ?? config)
})
})
}
return getDebugger(this.context, config).then((updatedConfig) => {
return setCurrentConfig(updatedConfig ?? config)
})
}
}
export const workspaceFileAccessor: FileAccessor = {
async readFile(path: string) {
try {
const uri = vscode.Uri.file(path)
const bytes = await vscode.workspace.fs.readFile(uri)
const contents = Buffer.from(bytes).toString('utf8')
return contents
} catch (e) {
try {
const uri = vscode.Uri.parse(path)
const bytes = await vscode.workspace.fs.readFile(uri)
const contents = Buffer.from(bytes).toString('utf8')
return contents
} catch (e) {
return `cannot read '${path}'`
}
}
},
}