blob: 3f7e03f39347a41d5d39b0d39113e0ce0350083c [file] [log] [blame]
/*---------------------------------------------------------------------------------------------
* 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 vscode from 'vscode'
import * as path from 'path'
import * as hexView from '../hexView'
import {
WorkspaceFolder,
DebugConfiguration,
ProviderResult,
CancellationToken,
} from 'vscode'
import { DaffodilDebugSession } from './daffodilDebug'
import { getDebugger, getDataFileFromFolder } from '../daffodilDebugger'
import { FileAccessor } from './daffodilRuntime'
import * as fs from 'fs'
import * as infoset from '../infoset'
import { getConfig, getCurrentConfig, setCurrentConfig } from '../utils'
import * as launchWizard from '../launchWizard/launchWizard'
import * as omegaEditClient from '../omega_edit/client'
import * as dfdlLang from '../language/dfdl'
/** 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]) {
return fileUri[0].fsPath
}
return ''
})
}
return file
}
// Function for setting up the commands for Run and Debug file
function createDebugRunFileConfigs(
resource: vscode.Uri,
runOrDebug: String,
runLast = false
) {
let targetResource = resource
let noDebug = runOrDebug === 'run' ? true : false
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 {
vscode.debug.startDebugging(
undefined,
getConfig(
'Run File',
'launch',
'dfdl',
targetResource.fsPath,
false,
false,
{ type: 'file', path: '${workspaceFolder}/' + infosetFile }
),
{ 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')
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.debugEditorContents',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'debug')
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.debugLastEditorContents',
(resource: vscode.Uri) => {
createDebugRunFileConfigs(resource, 'debug', true)
}
),
vscode.commands.registerCommand(
'extension.dfdl-debug.toggleFormatting',
(_) => {
const ds = vscode.debug.activeDebugSession
if (ds) {
ds.customRequest('toggleFormatting')
}
}
),
vscode.commands.registerCommand('toggle.experimental', async (_) => {
vscode.commands.executeCommand(
'setContext',
'experimentalFeaturesEnabled',
true
)
omegaEditClient.activate(context)
vscode.window.showInformationMessage(
'DFDL: Experimental Features Enabled!'
)
})
)
context.subscriptions.push(
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'
)
}
)
)
context.subscriptions.push(
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'
)
}
)
)
// register a configuration provider for 'dfdl' debug type
const provider = new DaffodilConfigurationProvider(context)
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider('dfdl', provider)
)
// register a dynamic configuration provider for 'dfdl' debug type
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
'dfdl',
{
provideDebugConfigurations(
folder: WorkspaceFolder | undefined
): ProviderResult<DebugConfiguration[]> {
if (!vscode.workspace.workspaceFolders) {
return [
getConfig(
'Daffodil Launch',
'launch',
'dfdl',
'${file}',
false,
false,
{ 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(
'Daffodil Launch',
'launch',
'dfdl',
'${file}',
false,
false,
{ 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)
}
// override VS Code's default implementation of the debug hover
context.subscriptions.push(
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"
context.subscriptions.push(
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
},
})
)
dfdlLang.activate(context)
infoset.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('Launch', 'launch', 'dfdl')
}
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
setCurrentConfig(config)
return getDebugger(this.context, config).then((result) => {
return config
})
})
}
return getDebugger(this.context, config).then((result) => {
setCurrentConfig(config)
return 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}'`
}
}
},
}
class InlineDebugAdapterFactory
implements vscode.DebugAdapterDescriptorFactory
{
context: vscode.ExtensionContext
hexViewer: hexView.DebuggerHexView
constructor(context: vscode.ExtensionContext) {
this.context = context
this.hexViewer = new hexView.DebuggerHexView(context)
}
createDebugAdapterDescriptor(
_session: vscode.DebugSession
): ProviderResult<vscode.DebugAdapterDescriptor> {
return new vscode.DebugAdapterInlineImplementation(
new DaffodilDebugSession(workspaceFileAccessor)
)
}
}