Refactor extension and create asset generator

Separates the extension.ts code in multiple files and adds an asset
generator code to provide the `tasks.json` and `launch.json` files (can
be triggered manually with a command).

- The build task generated uses the Godot CLI to build the solution (as
described in #18), I think this is the cleanest solution since we let
Godot handle the building of the project, but the user must configure
the path to the Godot executable.
- The Launch debug configuration is now created with the build
`preLaunchTask` automatically so users won't have to set it up manually
(fixes #18).
- Adds snippets to `tasks.json` to add debug configuration easily
after the file has been created.
- Adds a command to generate the `tasks.json` and `launch.json` manually
for convenience (asks the user if they want to override their current
configuration first).
This commit is contained in:
Raul Santos
2021-07-23 23:12:43 +02:00
parent 3de6294bef
commit 253cbf9522
14 changed files with 868 additions and 115 deletions

View File

@@ -19,7 +19,9 @@
],
"activationEvents": [
"workspaceContains:**/project.godot",
"onDebugResolve:godot"
"onDebugResolve:godot",
"onCommand:godot.csharp.selectProject",
"onCommand:godot.csharp.generateAssets"
],
"main": "./dist/extension.bundled.js",
"scripts": {
@@ -33,7 +35,10 @@
"webpack-watch": "webpack --mode development --watch"
},
"dependencies": {
"async-file": "^2.0.2",
"chokidar": "^3.4.0",
"fs-extra": "^10.0.0",
"jsonc-parser": "^3.0.0",
"promise-socket": "^6.0.3",
"vscode-debugprotocol": "^1.40.0"
},
@@ -42,10 +47,11 @@
"ms-vscode.mono-debug"
],
"devDependencies": {
"@types/fs-extra": "^9.0.12",
"@types/glob": "^7.1.1",
"@types/mocha": "^5.2.6",
"@types/node": "^10.12.21",
"@types/vscode": "^1.28.0",
"@types/node": "^16.4.1",
"@types/vscode": "^1.62.0",
"glob": "^7.1.4",
"make": "^0.8.1",
"mocha": "^6.1.4",
@@ -65,16 +71,29 @@
}
],
"contributes": {
"commands": [
{
"command": "godot.csharp.generateAssets",
"title": "Generate Assets for Build and Debug",
"category": "C# Godot"
}
],
"breakpoints": [
{
"language": "csharp"
},
{
"language": "fsharp"
}
],
"debuggers": [
{
"type": "godot-mono",
"label": "C# Godot",
"enableBreakpointsFor": {
"languageIds": [
"csharp",
"fsharp"
]
},
"languages": [
"csharp",
"fsharp"
],
"program": "./dist/GodotDebugSession/GodotDebugSession.exe",
"osx": {
"runtime": "mono"
@@ -82,30 +101,43 @@
"linux": {
"runtime": "mono"
},
"initialConfigurations": [
"configurationSnippets": [
{
"name": "Play in Editor",
"type": "godot-mono",
"mode": "playInEditor",
"request": "launch"
"label": "C# Godot: Play in Editor Configuration",
"description": "Launch a C# Godot App from the open editor with a debugger.",
"body": {
"name": "Play in Editor",
"type": "godot-mono",
"mode": "playInEditor",
"request": "launch"
}
},
{
"name": "Launch",
"type": "godot-mono",
"request": "launch",
"mode": "executable",
"executable": "${workspaceRoot}/Godot.exe",
"executableArguments": [
"--path",
"${workspaceRoot}"
]
"label": "C# Godot: Launch Configuration",
"description": "Launch a C# Godot App with a debugger.",
"body": {
"name": "Launch",
"type": "godot-mono",
"request": "launch",
"mode": "executable",
"preLaunchTask": "build",
"executable": "${1:<insert-godot-executable-path-here>}",
"executableArguments": [
"--path",
"${workspaceRoot}"
]
}
},
{
"name": "Attach",
"type": "godot-mono",
"request": "attach",
"address": "localhost",
"port": 23685
"label": "C# Godot: Attach Configuration",
"description": "Attach a debugger to a C# Godot App.",
"body": {
"name": "Attach",
"type": "godot-mono",
"request": "attach",
"address": "localhost",
"port": 23685
}
}
],
"configurationAttributes": {

View File

@@ -0,0 +1,42 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import {addTasksJsonIfNecessary} from './tasks';
import {addLaunchJsonIfNecessary} from './debug';
import {getVscodeFolder} from '../vscode-utils';
export class AssetsGenerator {
public readonly vscodeFolder: string;
public readonly tasksJsonPath: string;
public readonly launchJsonPath: string;
private constructor(vscodeFolder: string)
{
this.vscodeFolder = vscodeFolder;
this.tasksJsonPath = path.join(vscodeFolder, 'tasks.json');
this.launchJsonPath = path.join(vscodeFolder, 'launch.json');
}
public static Create(vscodeFolder: string): AssetsGenerator;
public static Create(vscodeFolder: string | undefined = undefined): AssetsGenerator | undefined
{
vscodeFolder = vscodeFolder ?? getVscodeFolder();
if (!vscodeFolder)
{
return undefined;
}
return new AssetsGenerator(vscodeFolder);
}
public async addTasksJsonIfNecessary(): Promise<void> {
return addTasksJsonIfNecessary(this.tasksJsonPath);
}
public async addLaunchJsonIfNecessary(): Promise<void> {
return addLaunchJsonIfNecessary(this.launchJsonPath);
}
public async hasExistingAssets(): Promise<boolean> {
return (await fs.pathExists(this.tasksJsonPath)) || (await fs.pathExists(this.launchJsonPath));
}
}

View File

@@ -0,0 +1,107 @@
import * as vscode from 'vscode';
import * as jsonc from 'jsonc-parser';
import * as fs from 'fs-extra';
import {getFormattingOptions, replaceCommentPropertiesWithComments, updateJsonWithComments} from '../json-utils';
export function createLaunchConfiguration():
{version: string, configurations: vscode.DebugConfiguration[]}
{
return {
version: '2.0.0',
configurations: _createDebugConfigurations(),
};
}
export function createDebugConfigurationsArray(): vscode.DebugConfiguration[]
{
const configurations = _createDebugConfigurations();
// Remove comments
configurations.forEach(configuration => {
for (const key in configuration)
{
if (Object.prototype.hasOwnProperty.call(configuration, key))
{
if (key.startsWith('OS-COMMENT'))
{
delete configuration[key];
}
}
}
});
return configurations;
}
function _createDebugConfigurations(): vscode.DebugConfiguration[]
{
return [
createPlayInEditorDebugConfiguration(),
createLaunchDebugConfiguration(),
createAttachDebugConfiguration(),
];
}
export function createPlayInEditorDebugConfiguration(): vscode.DebugConfiguration
{
return {
name: 'Play in Editor',
type: 'godot-mono',
mode: 'playInEditor',
request: 'launch',
};
}
export function createLaunchDebugConfiguration(): vscode.DebugConfiguration
{
return {
name: 'Launch',
type: 'godot-mono',
request: 'launch',
mode: 'executable',
preLaunchTask: 'build',
executable: '<insert-godot-executable-path-here>',
'OS-COMMENT1': 'See which arguments are available here:',
'OS-COMMENT2': 'https://docs.godotengine.org/en/stable/getting_started/editor/command_line_tutorial.html',
executableArguments: [
'--path',
'${workspaceRoot}',
],
};
}
export function createAttachDebugConfiguration()
{
return {
name: 'Attach',
type: 'godot-mono',
request: 'attach',
address: 'localhost',
port: 23685,
};
}
export async function addLaunchJsonIfNecessary(launchJsonPath: string): Promise<void>
{
const launchConfiguration = createLaunchConfiguration();
const formattingOptions = getFormattingOptions();
let text: string;
const exists = await fs.pathExists(launchJsonPath);
if (!exists) {
// when launch.json does not exist, create it and write all the content directly
const launchJsonText = JSON.stringify(launchConfiguration);
const launchJsonTextFormatted = jsonc.applyEdits(launchJsonText, jsonc.format(launchJsonText, undefined, formattingOptions));
text = launchJsonTextFormatted;
} else {
// when launch.json exists replace or append our configurations
const ourConfigs = launchConfiguration.configurations ?? [];
const content = fs.readFileSync(launchJsonPath).toString();
const updatedJson = updateJsonWithComments(content, ourConfigs, 'configurations', 'name', formattingOptions);
text = updatedJson;
}
const textWithComments = replaceCommentPropertiesWithComments(text);
await fs.writeFile(launchJsonPath, textWithComments);
}

View File

@@ -0,0 +1,3 @@
export * from './tasks';
export * from './debug';
export * from './assets-generator';

View File

@@ -0,0 +1,48 @@
import * as tasks from 'vscode-tasks';
import * as jsonc from 'jsonc-parser';
import * as fs from 'fs-extra';
import {getFormattingOptions, replaceCommentPropertiesWithComments, updateJsonWithComments} from '../json-utils';
export function createTasksConfiguration(): tasks.TaskConfiguration
{
return {
version: '2.0.0',
tasks: [createBuildTaskDescription()],
};
}
export function createBuildTaskDescription(): tasks.TaskDescription
{
return {
label: 'build',
command: '<insert-godot-executable-path-here>',
type: 'process',
args: ['--build-solutions', '--path', '${workspaceRoot}', '--no-window', '-q'],
problemMatcher: '$msCompile',
};
}
export async function addTasksJsonIfNecessary(tasksJsonPath: string): Promise<void>
{
const tasksConfiguration = createTasksConfiguration();
const formattingOptions = getFormattingOptions();
let text: string;
const exists = await fs.pathExists(tasksJsonPath);
if (!exists) {
// when tasks.json does not exist create it and write all the content directly
const tasksJsonText = JSON.stringify(tasksConfiguration);
const tasksJsonTextFormatted = jsonc.applyEdits(tasksJsonText, jsonc.format(tasksJsonText, undefined, formattingOptions));
text = tasksJsonTextFormatted;
} else {
// when tasks.json exists just update the tasks node
const ourConfigs = tasksConfiguration.tasks ?? [];
const content = fs.readFileSync(tasksJsonPath).toString();
const updatedJson = updateJsonWithComments(content, ourConfigs, 'tasks', 'label', formattingOptions);
text = updatedJson;
}
const textWithComments = replaceCommentPropertiesWithComments(text);
await fs.writeFile(tasksJsonPath, textWithComments);
}

51
src/assets-provider.ts Normal file
View File

@@ -0,0 +1,51 @@
import * as vscode from 'vscode';
import * as fs from 'fs-extra';
import {getVscodeFolder} from './vscode-utils';
import {AssetsGenerator} from './assets-generator';
export async function addAssets(): Promise<void>
{
const vscodeFolder = getVscodeFolder();
if (!vscodeFolder)
{
vscode.window.showErrorMessage('Cannot generate C# Godot assets for build and debug. No workspace folder was selected.');
return;
}
const generator = AssetsGenerator.Create(vscodeFolder);
const doGenerateAssets = await shouldGenerateAssets(generator);
if (!doGenerateAssets)
{
return; // user cancelled
}
// Make sure .vscode folder exists, generator will fail to create tasks.json and launch.json if the folder does not exist.
await fs.ensureDir(vscodeFolder);
const promises = [
generator.addTasksJsonIfNecessary(),
generator.addLaunchJsonIfNecessary(),
];
await Promise.all(promises);
}
async function shouldGenerateAssets(generator: AssetsGenerator): Promise<boolean>
{
if (await generator.hasExistingAssets()) {
const yesItem = {title: 'Yes'};
const cancelItem = {title: 'Cancel', isCloseAffordance: true};
const selection = await vscode.window.showWarningMessage('Replace existing build and debug assets?', cancelItem, yesItem);
if (selection === yesItem)
{
return true;
} else {
// The user clicked cancel
return false;
}
} else {
// The assets don't exist, so we're good to go.
return true;
}
}

44
src/configuration.ts Normal file
View File

@@ -0,0 +1,44 @@
import * as vscode from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';
// Too lazy so we're re-using mono-debug extension settings for now...
const configuration = vscode.workspace.getConfiguration('mono-debug');
type ExceptionConfigurations = { [exception: string]: DebugProtocol.ExceptionBreakMode; };
const DEFAULT_EXCEPTIONS: ExceptionConfigurations = {
'System.Exception': 'never',
'System.SystemException': 'never',
'System.ArithmeticException': 'never',
'System.ArrayTypeMismatchException': 'never',
'System.DivideByZeroException': 'never',
'System.IndexOutOfRangeException': 'never',
'System.InvalidCastException': 'never',
'System.NullReferenceException': 'never',
'System.OutOfMemoryException': 'never',
'System.OverflowException': 'never',
'System.StackOverflowException': 'never',
'System.TypeInitializationException': 'never',
};
export function getModel(): ExceptionConfigurations {
let model = DEFAULT_EXCEPTIONS;
if (configuration) {
const exceptionOptions = configuration.get('exceptionOptions');
if (exceptionOptions) {
model = <ExceptionConfigurations>exceptionOptions;
}
}
return model;
}
export function convertToExceptionOptions(model: ExceptionConfigurations): DebugProtocol.ExceptionOptions[] {
const exceptionItems: DebugProtocol.ExceptionOptions[] = [];
for (let exception in model) {
exceptionItems.push({
path: [{ names: [exception] }],
breakMode: model[exception],
});
}
return exceptionItems;
}

51
src/debug-provider.ts Normal file
View File

@@ -0,0 +1,51 @@
import * as vscode from 'vscode';
import * as configuration from './configuration';
import * as fs from 'fs-extra';
import {getVscodeFolder} from './vscode-utils';
import {AssetsGenerator, createDebugConfigurationsArray} from './assets-generator';
export class GodotMonoDebugConfigProvider implements vscode.DebugConfigurationProvider {
private godotProjectPath: string;
constructor(godotProjectPath: string) {
this.godotProjectPath = godotProjectPath;
}
public async provideDebugConfigurations(
folder: vscode.WorkspaceFolder | undefined,
token?: vscode.CancellationToken
): Promise<vscode.DebugConfiguration[]>
{
const vscodeFolder = getVscodeFolder();
if (!folder || !folder.uri || !vscodeFolder)
{
vscode.window.showErrorMessage('Cannot create C# Godot debug configurations. No workspace folder was selected.');
return [];
}
const generator = AssetsGenerator.Create(vscodeFolder);
// Make sure .vscode folder exists, addTasksJsonIfNecessary will fail to create tasks.json if the folder does not exist.
await fs.ensureDir(vscodeFolder);
// Add a tasks.json
await generator.addTasksJsonIfNecessary();
return createDebugConfigurationsArray();
}
public async resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
debugConfiguration: vscode.DebugConfiguration,
token?: vscode.CancellationToken
): Promise<vscode.DebugConfiguration | undefined>
{
if (!debugConfiguration.__exceptionOptions) {
debugConfiguration.__exceptionOptions = configuration.convertToExceptionOptions(configuration.getModel());
}
debugConfiguration['godotProjectDir'] = this.godotProjectPath;
return debugConfiguration;
}
}

View File

@@ -1,9 +1,10 @@
import * as vscode from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';
import { Client, Peer, MessageContent, MessageStatus, ILogger, IMessageHandler } from './godot-tools-messaging/client';
import * as completion_provider from './completion-provider';
import * as debug_provider from './debug-provider';
import * as assets_provider from './assets-provider';
import { fixPathForGodot } from './godot-utils';
import { findProjectFiles, ProjectLocation, promptForProject } from './project-select'
import { findProjectFiles, ProjectLocation, promptForProject } from './project-select';
let client: Client;
let codeCompletionProvider: vscode.Disposable;
@@ -89,40 +90,46 @@ class MessageHandler implements IMessageHandler {
}
export async function activate(context: vscode.ExtensionContext) {
let foundProjects: ProjectLocation[] = await findProjectFiles();
const foundProjects: ProjectLocation[] = await findProjectFiles();
// No project.godot files found. The extension doesn't need to do anything more.
if (foundProjects.length == 0) {
if (foundProjects.length === 0) {
return;
}
// Setup the status bar / project selector and prompt for project if necessary
const commandId = 'csharpGodot.selectProject';
const commandId = 'godot.csharp.selectProject';
statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
statusBarItem.command = commandId;
statusBarItem.show();
context.subscriptions.push(statusBarItem);
context.subscriptions.push(vscode.commands.registerCommand(commandId, async () => {
let project = await promptForProject(); // project.godot
const project = await promptForProject(); // project.godot
if (project !== undefined) {
setupProject(project, context);
}
}));
// One project.godot files found. Use it.
if (foundProjects.length == 1) {
if (foundProjects.length === 1) {
setupProject(foundProjects[0], context);
}
// Multiple project.godot files found. Prompt the user for which one they want to use.
else {
let project = await promptForProject();
const project = await promptForProject();
if (project !== undefined) {
setupProject(project, context);
}
}
// Setup generate assets command
const generateAssetsCommand = vscode.commands.registerCommand('godot.csharp.generateAssets', async () => {
await assets_provider.addAssets();
});
context.subscriptions.push(generateAssetsCommand);
}
function setupProject(project: ProjectLocation, context: vscode.ExtensionContext) {
let statusBarPath:string = project.relativeProjectPath === '.' ? './' : project.relativeProjectPath;
const statusBarPath:string = project.relativeProjectPath === '.' ? './' : project.relativeProjectPath;
statusBarItem.text = `$(folder) Godot Project: ${statusBarPath}`;
// Setup client
if (client !== undefined) {
@@ -132,17 +139,17 @@ function setupProject(project: ProjectLocation, context: vscode.ExtensionContext
'VisualStudioCode',
fixPathForGodot(project.absoluteProjectPath),
new MessageHandler(),
new Logger()
new Logger(),
);
client.start();
// Setup debug provider
if (debugConfigProvider) {
if (debugConfigProvider !== undefined) {
debugConfigProvider.dispose();
}
debugConfigProvider = vscode.debug.registerDebugConfigurationProvider(
'godot-mono',
new GodotMonoDebugConfigProvider(project.absoluteProjectPath)
new debug_provider.GodotMonoDebugConfigProvider(project.absoluteProjectPath)
);
context.subscriptions.push(debugConfigProvider);
@@ -165,64 +172,3 @@ export function deactivate() {
client.dispose();
}
class GodotMonoDebugConfigProvider implements vscode.DebugConfigurationProvider {
godotProjectPath: string;
constructor(godotProjectPath: string) {
this.godotProjectPath = godotProjectPath;
}
public async resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
debugConfiguration: vscode.DebugConfiguration,
token?: vscode.CancellationToken
): Promise<vscode.DebugConfiguration | undefined> {
if (!debugConfiguration.__exceptionOptions) {
debugConfiguration.__exceptionOptions = convertToExceptionOptions(getModel());
}
debugConfiguration['godotProjectDir'] = this.godotProjectPath;
return debugConfiguration;
}
}
// Too lazy so we're re-using mono-debug extension settings for now...
const configuration = vscode.workspace.getConfiguration('mono-debug');
type ExceptionConfigurations = { [exception: string]: DebugProtocol.ExceptionBreakMode; };
const DEFAULT_EXCEPTIONS: ExceptionConfigurations = {
'System.Exception': 'never',
'System.SystemException': 'never',
'System.ArithmeticException': 'never',
'System.ArrayTypeMismatchException': 'never',
'System.DivideByZeroException': 'never',
'System.IndexOutOfRangeException': 'never',
'System.InvalidCastException': 'never',
'System.NullReferenceException': 'never',
'System.OutOfMemoryException': 'never',
'System.OverflowException': 'never',
'System.StackOverflowException': 'never',
'System.TypeInitializationException': 'never'
};
function getModel(): ExceptionConfigurations {
let model = DEFAULT_EXCEPTIONS;
if (configuration) {
const exceptionOptions = configuration.get('exceptionOptions');
if (exceptionOptions) {
model = <ExceptionConfigurations>exceptionOptions;
}
}
return model;
}
function convertToExceptionOptions(model: ExceptionConfigurations): DebugProtocol.ExceptionOptions[] {
const exceptionItems: DebugProtocol.ExceptionOptions[] = [];
for (let exception in model) {
exceptionItems.push({
path: [{ names: [exception] }],
breakMode: model[exception]
});
}
return exceptionItems;
}

View File

@@ -286,7 +286,7 @@ export class Client implements Disposable {
await socket.connect(this.metadata!.port, 'localhost');
}
catch (err) {
this.logger.logError('Failed to connect to Godot Ide Server', err);
this.logger.logError('Failed to connect to Godot Ide Server', err as Error);
continue;
}

64
src/json-utils.ts Normal file
View File

@@ -0,0 +1,64 @@
import * as vscode from 'vscode';
import * as os from 'os';
import * as jsonc from 'jsonc-parser';
import { FormattingOptions, ModificationOptions } from 'jsonc-parser';
export function getFormattingOptions(): FormattingOptions {
const editorConfig = vscode.workspace.getConfiguration('editor');
const tabSize = editorConfig.get<number>('tabSize') ?? 4;
const insertSpaces = editorConfig.get<boolean>('insertSpaces') ?? true;
const filesConfig = vscode.workspace.getConfiguration('files');
const eolSetting = filesConfig.get<string>('eol');
const eol = !eolSetting || eolSetting === 'auto' ? os.EOL : '\n';
const formattingOptions: FormattingOptions = {
insertSpaces: insertSpaces,
tabSize: tabSize,
eol: eol,
};
return formattingOptions;
}
export function replaceCommentPropertiesWithComments(text: string): string {
// replacing dummy properties OS-COMMENT with the normal comment syntax
const regex = /["']OS-COMMENT\d*["']\s*\:\s*["'](.*)["']\s*?,/gi;
const withComments = text.replace(regex, '// $1');
return withComments;
}
export function updateJsonWithComments(text: string, replacements: any[], nodeName: string, keyName: string, formattingOptions: FormattingOptions) : string {
const modificationOptions : ModificationOptions = {
formattingOptions
};
// parse using jsonc because there are comments
// only use this to determine what to change
// we will modify it as text to keep existing comments
const parsed = jsonc.parse(text);
const items = parsed[nodeName];
const itemKeys : string[] = items.map((i: { [x: string]: string; }) => i[keyName]);
let modified = text;
// count how many items we inserted to ensure we are putting items at the end
// in the same order as they are in the replacements array
let insertCount = 0;
replacements.map((replacement: { [x: string]: string; }) => {
const index = itemKeys.indexOf(replacement[keyName]);
const found = index >= 0;
const modificationIndex = found ? index : items.length + insertCount++;
const edits = jsonc.modify(modified, [nodeName, modificationIndex], replacement, modificationOptions);
const updated = jsonc.applyEdits(modified, edits);
// we need to carry out the changes one by one, because we are inserting into the json
// and so we cannot just figure out all the edits from the original text, instead we need to apply
// changes one by one
modified = updated;
});
return replaceCommentPropertiesWithComments(modified);
}

View File

@@ -2,34 +2,34 @@ import * as vscode from 'vscode';
import * as path from 'path';
export interface ProjectLocation{
relativeFilePath: string
absoluteFilePath: string
relativeProjectPath: string
absoluteProjectPath: string
relativeFilePath: string;
absoluteFilePath: string;
relativeProjectPath: string;
absoluteProjectPath: string;
}
export async function findProjectFiles(): Promise<ProjectLocation[]> {
let projectFiles = await vscode.workspace.findFiles("**/project.godot");
const projectFiles = await vscode.workspace.findFiles("**/project.godot");
return projectFiles.map((x) => {
return {
relativeFilePath: vscode.workspace.asRelativePath(x),
absoluteFilePath: x.path,
relativeProjectPath: path.dirname(vscode.workspace.asRelativePath(x)),
absoluteProjectPath: path.dirname(x.path)
}
absoluteProjectPath: path.dirname(x.path),
};
});
}
export async function promptForProject(): Promise<ProjectLocation | undefined> {
let godotProjectFiles = await findProjectFiles();
let selectionOptions = godotProjectFiles?.map((x) => {
const godotProjectFiles = await findProjectFiles();
const selectionOptions = godotProjectFiles?.map((x) => {
return {
label: x.relativeFilePath,
...x
}
...x,
};
});
return vscode.window.showQuickPick(selectionOptions, {
title: 'Select a Godot project',
placeHolder: 'Select a Godot project'
placeHolder: 'Select a Godot project',
});
}
}

12
src/vscode-utils.ts Normal file
View File

@@ -0,0 +1,12 @@
import * as vscode from 'vscode';
import * as path from 'path';
export function getVscodeFolder(): string | undefined
{
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) {
return undefined;
}
return path.join(workspaceFolders[0].uri.fsPath, '.vscode');
}

353
typings/vscode-tasks.d.ts vendored Normal file
View File

@@ -0,0 +1,353 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Copied from http://code.visualstudio.com/docs/editor/tasks_appendix
declare module "vscode-tasks" {
export interface TaskConfiguration extends BaseTaskConfiguration {
/**
* The configuration's version number
*/
version: string;
/**
* Windows specific task configuration
*/
windows?: BaseTaskConfiguration;
/**
* Mac specific task configuration
*/
osx?: BaseTaskConfiguration;
/**
* Linux specific task configuration
*/
linux?: BaseTaskConfiguration;
}
export interface BaseTaskConfiguration {
/**
* The type of a custom task. Tasks of type "shell" are executed
* inside a shell (e.g. bash, cmd, powershell, ...)
*/
type?: "shell" | "process";
/**
* The command to be executed. Can be an external program or a shell
* command.
*/
command?: string;
/**
* Specifies whether a global command is a background task.
*/
isBackground?: boolean;
/**
* The command options used when the command is executed. Can be omitted.
*/
options?: CommandOptions;
/**
* The arguments passed to the command. Can be omitted.
*/
args?: string[];
/**
* The presentation options.
*/
presentation?: PresentationOptions;
/**
* The problem matcher to be used if a global command is executed (e.g. no tasks
* are defined). A tasks.json file can either contain a global problemMatcher
* property or a tasks property but not both.
*/
problemMatcher?: string | ProblemMatcher | (string | ProblemMatcher)[];
/**
* The configuration of the available tasks. A tasks.json file can either
* contain a global problemMatcher property or a tasks property but not both.
*/
tasks?: TaskDescription[];
}
/**
* Options to be passed to the external program or shell
*/
export interface CommandOptions {
/**
* The current working directory of the executed program or shell.
* If omitted Ticino's current workspace root is used.
*/
cwd?: string;
/**
* The environment of the executed program or shell. If omitted
* the parent process' environment is used.
*/
env?: { [key: string]: string; };
/**
* Configuration of the shell when task type is `shell`
*/
shell: {
/**
* The shell to use.
*/
executable: string;
/**
* The arguments to be passed to the shell executable to run in command mode
* (e.g ['-c'] for bash or ['/S', '/C'] for cmd.exe).
*/
args?: string[];
}
}
/**
* The description of a task.
*/
export interface TaskDescription {
/**
* The task's name
*/
label: string;
/**
* The type of a custom task. Tasks of type "shell" are executed
* inside a shell (e.g. bash, cmd, powershell, ...)
*/
type: "shell" | "process";
/**
* The command to execute. If the type is "shell" it should be the full
* command line including any additional arguments passed to the command.
*/
command: string;
/**
* Whether the executed command is kept alive and runs in the background.
*/
isBackground?: boolean;
/**
* Additional arguments passed to the command. Should be used if type
* is "process".
*/
args?: string[];
/**
* Tasks V1 isBuildCommand is used to detect if the tasks is in a build group.
*/
isBuildCommand?: boolean;
/**
* Defines the group to which this tasks belongs
*/
group?: "build" | "string";
/**
* The presentation options.
*/
presentation?: PresentationOptions;
/**
* The problem matcher(s) to use to capture problems in the tasks
* output.
*/
problemMatcher?: string | ProblemMatcher | (string | ProblemMatcher)[];
}
export interface PresentationOptions {
/**
* Controls whether the task output is reveal in the user interface.
* Defaults to `always`.
*/
reveal?: "never" | "silent" | "always";
/**
* Controls whether the command associated with the task is echoed
* in the user interface.
*/
echo?: boolean;
/**
* Controls whether the panel showing the task output is taking focus.
*/
focus?: boolean;
/**
* Controls if the task panel is used for this task only (dedicated),
* shared between tasks (shared) or if a new panel is created on
* every task execution (new). Defaults to `shared`
*/
panel?: "shared" | "dedicated" | "new";
}
/**
* A description of a problem matcher that detects problems
* in build output.
*/
export interface ProblemMatcher {
/**
* The name of a base problem matcher to use. If specified the
* base problem matcher will be used as a template and properties
* specified here will replace properties of the base problem
* matcher
*/
base?: string;
/**
* The owner of the produced VS Code problem. This is typically
* the identifier of a VS Code language service if the problems are
* to be merged with the one produced by the language service
* or 'external'. Defaults to 'external' if omitted.
*/
owner?: string;
/**
* The severity of the VS Code problem produced by this problem matcher.
*
* Valid values are:
* "error": to produce errors.
* "warning": to produce warnings.
* "info": to produce infos.
*
* The value is used if a pattern doesn't specify a severity match group.
* Defaults to "error" if omitted.
*/
severity?: string;
/**
* Defines how filename reported in a problem pattern
* should be read. Valid values are:
* - "absolute": the filename is always treated absolute.
* - "relative": the filename is always treated relative to
* the current working directory. This is the default.
* - ["relative", "path value"]: the filename is always
* treated relative to the given path value.
*/
fileLocation?: string | string[];
/**
* The name of a predefined problem pattern, the inline definition
* of a problem pattern or an array of problem patterns to match
* problems spread over multiple lines.
*/
pattern?: string | ProblemPattern | ProblemPattern[];
/**
* Additional information used to detect when a background task (like a watching task in Gulp)
* is active.
*/
background?: BackgroundMatcher;
}
/**
* A description to track the start and end of a background task.
*/
export interface BackgroundMatcher {
/**
* If set to true the watcher is in active mode when the task
* starts. This is equals of issuing a line that matches the
* beginPattern.
*/
activeOnStart?: boolean;
/**
* If matched in the output the start of a background task is signaled.
*/
beginsPattern?: string;
/**
* If matched in the output the end of a background task is signaled.
*/
endsPattern?: string;
}
export interface ProblemPattern {
/**
* The regular expression to find a problem in the console output of an
* executed task.
*/
regexp: string;
/**
* The match group index of the filename.
*/
file: number;
/**
* The match group index of the problems's location. Valid location
* patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn).
* If omitted the line and column properties are used.
*/
location?: number;
/**
* The match group index of the problem's line in the source file.
* Can only be omitted if location is specified.
*/
line?: number;
/**
* The match group index of the problem's column in the source file.
*/
column?: number;
/**
* The match group index of the problem's end line in the source file.
*
* Defaults to undefined. No end line is captured.
*/
endLine?: number;
/**
* The match group index of the problem's end column in the source file.
*
* Defaults to undefined. No end column is captured.
*/
endColumn?: number;
/**
* The match group index of the problem's severity.
*
* Defaults to undefined. In this case the problem matcher's severity
* is used.
*/
severity?: number;
/**
* The match group index of the problem's code.
*
* Defaults to undefined. No code is captured.
*/
code?: number;
/**
* The match group index of the message. Defaults to 0.
*/
message: number;
/**
* Specifies if the last pattern in a multi line problem matcher should
* loop as long as it does match a line consequently. Only valid on the
* last problem pattern in a multi line problem matcher.
*/
loop?: boolean;
}
}