mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-14 12:11:43 +01:00
Merge branch 'master' into joh/bulkEditPreview
This commit is contained in:
@@ -39,8 +39,8 @@ jobs:
|
||||
name: Run Hygiene Checks
|
||||
- run: yarn monaco-compile-check
|
||||
name: Run Monaco Editor Checks
|
||||
- run: yarn valid-globals-check
|
||||
name: Run Valid Globals Checks
|
||||
- run: yarn valid-layers-check
|
||||
name: Run Valid Layers Checks
|
||||
- run: yarn compile
|
||||
name: Compile Sources
|
||||
- run: yarn download-builtin-extensions
|
||||
@@ -71,8 +71,8 @@ jobs:
|
||||
name: Run Hygiene Checks
|
||||
- run: yarn monaco-compile-check
|
||||
name: Run Monaco Editor Checks
|
||||
- run: yarn valid-globals-check
|
||||
name: Run Valid Globals Checks
|
||||
- run: yarn valid-layers-check
|
||||
name: Run Valid Layers Checks
|
||||
- run: yarn compile
|
||||
name: Compile Sources
|
||||
- run: yarn download-builtin-extensions
|
||||
@@ -100,8 +100,8 @@ jobs:
|
||||
name: Run Hygiene Checks
|
||||
- run: yarn monaco-compile-check
|
||||
name: Run Monaco Editor Checks
|
||||
- run: yarn valid-globals-check
|
||||
name: Run Valid Globals Checks
|
||||
- run: yarn valid-layers-check
|
||||
name: Run Valid Layers Checks
|
||||
- run: yarn compile
|
||||
name: Compile Sources
|
||||
- run: yarn download-builtin-extensions
|
||||
|
||||
Vendored
+2
-8
@@ -141,10 +141,7 @@
|
||||
"type": "chrome",
|
||||
"request": "attach",
|
||||
"name": "Attach to VS Code",
|
||||
"port": 9222,
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
"port": 9222
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
@@ -170,10 +167,7 @@
|
||||
"--inspect=5875",
|
||||
"--no-cached-data"
|
||||
],
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
|
||||
@@ -30,8 +30,8 @@ steps:
|
||||
yarn monaco-compile-check
|
||||
displayName: Run Monaco Editor Checks
|
||||
- script: |
|
||||
yarn valid-globals-check
|
||||
displayName: Run Valid Globals Checks
|
||||
yarn valid-layers-check
|
||||
displayName: Run Valid Layers Checks
|
||||
- script: |
|
||||
yarn compile
|
||||
displayName: Compile Sources
|
||||
|
||||
@@ -38,8 +38,8 @@ steps:
|
||||
yarn monaco-compile-check
|
||||
displayName: Run Monaco Editor Checks
|
||||
- script: |
|
||||
yarn valid-globals-check
|
||||
displayName: Run Valid Globals Checks
|
||||
yarn valid-layers-check
|
||||
displayName: Run Valid Layers Checks
|
||||
- script: |
|
||||
yarn compile
|
||||
displayName: Compile Sources
|
||||
|
||||
@@ -90,8 +90,8 @@ steps:
|
||||
set -e
|
||||
yarn gulp hygiene
|
||||
yarn monaco-compile-check
|
||||
yarn valid-globals-check
|
||||
displayName: Run hygiene, monaco compile & valid globals checks
|
||||
yarn valid-layers-check
|
||||
displayName: Run hygiene, monaco compile & valid layers checks
|
||||
condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
|
||||
|
||||
- script: |
|
||||
|
||||
@@ -35,8 +35,8 @@ steps:
|
||||
yarn monaco-compile-check
|
||||
displayName: Run Monaco Editor Checks
|
||||
- script: |
|
||||
yarn valid-globals-check
|
||||
displayName: Run Valid Globals Checks
|
||||
yarn valid-layers-check
|
||||
displayName: Run Valid Layers Checks
|
||||
- powershell: |
|
||||
yarn compile
|
||||
displayName: Compile Sources
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
"use strict";
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ts = require("typescript");
|
||||
const fs_1 = require("fs");
|
||||
const path_1 = require("path");
|
||||
const minimatch_1 = require("minimatch");
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
// A custom typescript linter for the specific task of detecting the use of certain globals in a
|
||||
// layer that does not allow the use. For example:
|
||||
// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement)
|
||||
// - using node.js globals in common/browser layer (e.g. process)
|
||||
//
|
||||
// Make changes to below RULES to lift certain files from these checks only if absolutely needed
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
const RULES = {
|
||||
'no-nodejs-globals': [
|
||||
{
|
||||
'target': '**/vs/**/test/{common,browser}/**',
|
||||
'allowed': [
|
||||
'process',
|
||||
'Buffer',
|
||||
'__filename',
|
||||
'__dirname'
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/workbench/api/common/extHostExtensionService.ts',
|
||||
'allowed': [
|
||||
'global' // -> safe access to 'global'
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/**/{common,browser}/**',
|
||||
'allowed': [ /* none */]
|
||||
}
|
||||
],
|
||||
'no-dom-globals': [
|
||||
{
|
||||
'target': '**/vs/base/parts/quickopen/common/quickOpen.ts',
|
||||
'allowed': [
|
||||
'HTMLElement' // quick open will be replaced with a different widget soon
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/**/test/{common,node,electron-main}/**',
|
||||
'allowed': [
|
||||
'document',
|
||||
'HTMLElement',
|
||||
'createElement'
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/**/{common,node,electron-main}/**',
|
||||
'allowed': [ /* none */]
|
||||
}
|
||||
]
|
||||
};
|
||||
const TS_CONFIG_PATH = path_1.join(__dirname, '../../', 'src', 'tsconfig.json');
|
||||
const DOM_GLOBALS_DEFINITION = 'lib.dom.d.ts';
|
||||
const DISALLOWED_DOM_GLOBALS = [
|
||||
'window',
|
||||
'document',
|
||||
'HTMLElement',
|
||||
'createElement'
|
||||
];
|
||||
const NODE_GLOBALS_DEFINITION = '@types/node';
|
||||
const DISALLOWED_NODE_GLOBALS = [
|
||||
// https://nodejs.org/api/globals.html#globals_global_objects
|
||||
'NodeJS',
|
||||
'Buffer',
|
||||
'__dirname',
|
||||
'__filename',
|
||||
'clearImmediate',
|
||||
'exports',
|
||||
'global',
|
||||
'module',
|
||||
'process',
|
||||
'setImmediate'
|
||||
];
|
||||
let hasErrors = false;
|
||||
function checkFile(program, sourceFile, rule) {
|
||||
checkNode(sourceFile);
|
||||
function checkNode(node) {
|
||||
if (node.kind !== ts.SyntaxKind.Identifier) {
|
||||
return ts.forEachChild(node, checkNode); // recurse down
|
||||
}
|
||||
const text = node.getText(sourceFile);
|
||||
if (!rule.disallowedGlobals.some(disallowedGlobal => disallowedGlobal === text)) {
|
||||
return; // only if disallowed
|
||||
}
|
||||
if (rule.allowedGlobals.some(allowed => allowed === text)) {
|
||||
return; // override
|
||||
}
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getSymbolAtLocation(node);
|
||||
if (symbol) {
|
||||
const declarations = symbol.declarations;
|
||||
if (Array.isArray(declarations) && symbol.declarations.some(declaration => {
|
||||
if (declaration) {
|
||||
const parent = declaration.parent;
|
||||
if (parent) {
|
||||
const sourceFile = parent.getSourceFile();
|
||||
if (sourceFile) {
|
||||
const fileName = sourceFile.fileName;
|
||||
if (fileName && fileName.indexOf(rule.definition) >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
||||
console.log(`build/lib/globalsLinter.ts: Cannot use global '${text}' in ${sourceFile.fileName} (${line + 1},${character + 1})`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function createProgram(tsconfigPath) {
|
||||
const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
||||
const configHostParser = { fileExists: fs_1.existsSync, readDirectory: ts.sys.readDirectory, readFile: file => fs_1.readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' };
|
||||
const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, path_1.resolve(path_1.dirname(tsconfigPath)), { noEmit: true });
|
||||
const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true);
|
||||
return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost);
|
||||
}
|
||||
//
|
||||
// Create program and start checking
|
||||
//
|
||||
const program = createProgram(TS_CONFIG_PATH);
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
let noDomGlobalsLinter = undefined;
|
||||
let noNodeJSGlobalsLinter = undefined;
|
||||
for (const rules of RULES['no-dom-globals']) {
|
||||
if (minimatch_1.match([sourceFile.fileName], rules.target).length > 0) {
|
||||
noDomGlobalsLinter = { allowed: rules.allowed };
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const rules of RULES['no-nodejs-globals']) {
|
||||
if (minimatch_1.match([sourceFile.fileName], rules.target).length > 0) {
|
||||
noNodeJSGlobalsLinter = { allowed: rules.allowed };
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!noDomGlobalsLinter && !noNodeJSGlobalsLinter) {
|
||||
continue; // no rule to run
|
||||
}
|
||||
// No DOM Globals
|
||||
if (noDomGlobalsLinter) {
|
||||
checkFile(program, sourceFile, {
|
||||
definition: DOM_GLOBALS_DEFINITION,
|
||||
disallowedGlobals: DISALLOWED_DOM_GLOBALS,
|
||||
allowedGlobals: noDomGlobalsLinter.allowed
|
||||
});
|
||||
}
|
||||
// No node.js Globals
|
||||
if (noNodeJSGlobalsLinter) {
|
||||
checkFile(program, sourceFile, {
|
||||
definition: NODE_GLOBALS_DEFINITION,
|
||||
disallowedGlobals: DISALLOWED_NODE_GLOBALS,
|
||||
allowedGlobals: noNodeJSGlobalsLinter.allowed
|
||||
});
|
||||
}
|
||||
}
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { resolve, dirname, join } from 'path';
|
||||
import { match } from 'minimatch';
|
||||
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
// A custom typescript linter for the specific task of detecting the use of certain globals in a
|
||||
// layer that does not allow the use. For example:
|
||||
// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement)
|
||||
// - using node.js globals in common/browser layer (e.g. process)
|
||||
//
|
||||
// Make changes to below RULES to lift certain files from these checks only if absolutely needed
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
|
||||
const RULES = {
|
||||
'no-nodejs-globals': [
|
||||
{
|
||||
'target': '**/vs/**/test/{common,browser}/**',
|
||||
'allowed': [ // -> less strict for test files
|
||||
'process',
|
||||
'Buffer',
|
||||
'__filename',
|
||||
'__dirname'
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/workbench/api/common/extHostExtensionService.ts',
|
||||
'allowed': [
|
||||
'global' // -> safe access to 'global'
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/**/{common,browser}/**',
|
||||
'allowed': [ /* none */]
|
||||
}
|
||||
],
|
||||
'no-dom-globals': [
|
||||
{
|
||||
'target': '**/vs/base/parts/quickopen/common/quickOpen.ts',
|
||||
'allowed': [
|
||||
'HTMLElement' // quick open will be replaced with a different widget soon
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/**/test/{common,node,electron-main}/**',
|
||||
'allowed': [ // -> less strict for test files
|
||||
'document',
|
||||
'HTMLElement',
|
||||
'createElement'
|
||||
]
|
||||
},
|
||||
{
|
||||
'target': '**/vs/**/{common,node,electron-main}/**',
|
||||
'allowed': [ /* none */]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json');
|
||||
|
||||
const DOM_GLOBALS_DEFINITION = 'lib.dom.d.ts';
|
||||
|
||||
const DISALLOWED_DOM_GLOBALS = [
|
||||
'window',
|
||||
'document',
|
||||
'HTMLElement',
|
||||
'createElement'
|
||||
];
|
||||
|
||||
const NODE_GLOBALS_DEFINITION = '@types/node';
|
||||
|
||||
const DISALLOWED_NODE_GLOBALS = [
|
||||
// https://nodejs.org/api/globals.html#globals_global_objects
|
||||
'NodeJS',
|
||||
'Buffer',
|
||||
'__dirname',
|
||||
'__filename',
|
||||
'clearImmediate',
|
||||
'exports',
|
||||
'global',
|
||||
'module',
|
||||
'process',
|
||||
'setImmediate'
|
||||
];
|
||||
|
||||
interface IRule {
|
||||
definition: string;
|
||||
disallowedGlobals: string[];
|
||||
allowedGlobals: string[];
|
||||
}
|
||||
|
||||
let hasErrors = false;
|
||||
|
||||
function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) {
|
||||
checkNode(sourceFile);
|
||||
|
||||
function checkNode(node: ts.Node): void {
|
||||
if (node.kind !== ts.SyntaxKind.Identifier) {
|
||||
return ts.forEachChild(node, checkNode); // recurse down
|
||||
}
|
||||
|
||||
const text = node.getText(sourceFile);
|
||||
|
||||
if (!rule.disallowedGlobals.some(disallowedGlobal => disallowedGlobal === text)) {
|
||||
return; // only if disallowed
|
||||
}
|
||||
|
||||
if (rule.allowedGlobals.some(allowed => allowed === text)) {
|
||||
return; // override
|
||||
}
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getSymbolAtLocation(node);
|
||||
if (symbol) {
|
||||
const declarations = symbol.declarations;
|
||||
if (Array.isArray(declarations) && symbol.declarations.some(declaration => {
|
||||
if (declaration) {
|
||||
const parent = declaration.parent;
|
||||
if (parent) {
|
||||
const sourceFile = parent.getSourceFile();
|
||||
if (sourceFile) {
|
||||
const fileName = sourceFile.fileName;
|
||||
if (fileName && fileName.indexOf(rule.definition) >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})) {
|
||||
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
||||
console.log(`build/lib/globalsLinter.ts: Cannot use global '${text}' in ${sourceFile.fileName} (${line + 1},${character + 1})`);
|
||||
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createProgram(tsconfigPath: string): ts.Program {
|
||||
const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
||||
|
||||
const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' };
|
||||
const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true });
|
||||
|
||||
const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true);
|
||||
|
||||
return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost);
|
||||
}
|
||||
|
||||
//
|
||||
// Create program and start checking
|
||||
//
|
||||
const program = createProgram(TS_CONFIG_PATH);
|
||||
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
let noDomGlobalsLinter: { allowed: string[] } | undefined = undefined;
|
||||
let noNodeJSGlobalsLinter: { allowed: string[] } | undefined = undefined;
|
||||
|
||||
for (const rules of RULES['no-dom-globals']) {
|
||||
if (match([sourceFile.fileName], rules.target).length > 0) {
|
||||
noDomGlobalsLinter = { allowed: rules.allowed };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const rules of RULES['no-nodejs-globals']) {
|
||||
if (match([sourceFile.fileName], rules.target).length > 0) {
|
||||
noNodeJSGlobalsLinter = { allowed: rules.allowed };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!noDomGlobalsLinter && !noNodeJSGlobalsLinter) {
|
||||
continue; // no rule to run
|
||||
}
|
||||
|
||||
// No DOM Globals
|
||||
if (noDomGlobalsLinter) {
|
||||
checkFile(program, sourceFile, {
|
||||
definition: DOM_GLOBALS_DEFINITION,
|
||||
disallowedGlobals: DISALLOWED_DOM_GLOBALS,
|
||||
allowedGlobals: noDomGlobalsLinter.allowed
|
||||
});
|
||||
}
|
||||
|
||||
// No node.js Globals
|
||||
if (noNodeJSGlobalsLinter) {
|
||||
checkFile(program, sourceFile, {
|
||||
definition: NODE_GLOBALS_DEFINITION,
|
||||
disallowedGlobals: DISALLOWED_NODE_GLOBALS,
|
||||
allowedGlobals: noNodeJSGlobalsLinter.allowed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
"use strict";
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ts = require("typescript");
|
||||
const fs_1 = require("fs");
|
||||
const path_1 = require("path");
|
||||
const minimatch_1 = require("minimatch");
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
// A custom typescript checker for the specific task of detecting the use of certain types in a
|
||||
// layer that does not allow such use. For example:
|
||||
// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement)
|
||||
// - using node.js globals in common/browser layer (e.g. process)
|
||||
//
|
||||
// Make changes to below RULES to lift certain files from these checks only if absolutely needed
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
// Types we assume are present in all implementations of JS VMs (node.js, browsers)
|
||||
// Feel free to add more core types as you see needed if present in node.js and browsers
|
||||
const CORE_TYPES = [
|
||||
'require',
|
||||
'atob',
|
||||
'btoa',
|
||||
'setTimeout',
|
||||
'clearTimeout',
|
||||
'setInterval',
|
||||
'clearInterval',
|
||||
'console',
|
||||
'log',
|
||||
'info',
|
||||
'warn',
|
||||
'error',
|
||||
'group',
|
||||
'groupEnd',
|
||||
'table',
|
||||
'Error',
|
||||
'String',
|
||||
'throws',
|
||||
'stack',
|
||||
'captureStackTrace',
|
||||
'stackTraceLimit',
|
||||
'TextDecoder',
|
||||
'TextEncoder',
|
||||
'encode',
|
||||
'decode',
|
||||
'self',
|
||||
'trimLeft',
|
||||
'trimRight'
|
||||
];
|
||||
const RULES = [
|
||||
// Tests: skip
|
||||
{
|
||||
target: '**/vs/**/test/**',
|
||||
skip: true // -> skip all test files
|
||||
},
|
||||
// Common: vs/base/common/platform.ts
|
||||
{
|
||||
target: '**/vs/base/common/platform.ts',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
// Safe access to postMessage() and friends
|
||||
'MessageEvent',
|
||||
'data'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts',
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
// Common: vs/workbench/api/common/extHostExtensionService.ts
|
||||
{
|
||||
target: '**/vs/workbench/api/common/extHostExtensionService.ts',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
// Safe access to global
|
||||
'global'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts',
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
// Common
|
||||
{
|
||||
target: '**/vs/**/common/**',
|
||||
allowedTypes: CORE_TYPES,
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts',
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
// Browser
|
||||
{
|
||||
target: '**/vs/**/browser/**',
|
||||
allowedTypes: CORE_TYPES,
|
||||
disallowedDefinitions: [
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
// node.js
|
||||
{
|
||||
target: '**/vs/**/node/**',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
// --> types from node.d.ts that duplicate from lib.dom.d.ts
|
||||
'URL',
|
||||
'protocol',
|
||||
'hostname',
|
||||
'port',
|
||||
'pathname',
|
||||
'search',
|
||||
'username',
|
||||
'password'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts' // no DOM
|
||||
]
|
||||
},
|
||||
// Electron (renderer): skip
|
||||
{
|
||||
target: '**/vs/**/electron-browser/**',
|
||||
skip: true // -> supports all types
|
||||
},
|
||||
// Electron (main)
|
||||
{
|
||||
target: '**/vs/**/electron-main/**',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
// --> types from electron.d.ts that duplicate from lib.dom.d.ts
|
||||
'Event',
|
||||
'Request'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts' // no DOM
|
||||
]
|
||||
}
|
||||
];
|
||||
const TS_CONFIG_PATH = path_1.join(__dirname, '../../', 'src', 'tsconfig.json');
|
||||
let hasErrors = false;
|
||||
function checkFile(program, sourceFile, rule) {
|
||||
checkNode(sourceFile);
|
||||
function checkNode(node) {
|
||||
var _a;
|
||||
if (node.kind !== ts.SyntaxKind.Identifier) {
|
||||
return ts.forEachChild(node, checkNode); // recurse down
|
||||
}
|
||||
const text = node.getText(sourceFile);
|
||||
if ((_a = rule.allowedTypes) === null || _a === void 0 ? void 0 : _a.some(allowed => allowed === text)) {
|
||||
return; // override
|
||||
}
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getSymbolAtLocation(node);
|
||||
if (symbol) {
|
||||
const declarations = symbol.declarations;
|
||||
if (Array.isArray(declarations)) {
|
||||
for (const declaration of declarations) {
|
||||
if (declaration) {
|
||||
const parent = declaration.parent;
|
||||
if (parent) {
|
||||
const parentSourceFile = parent.getSourceFile();
|
||||
if (parentSourceFile) {
|
||||
const definitionFileName = parentSourceFile.fileName;
|
||||
if (rule.disallowedDefinitions) {
|
||||
for (const disallowedDefinition of rule.disallowedDefinitions) {
|
||||
if (definitionFileName.indexOf(disallowedDefinition) >= 0) {
|
||||
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
||||
console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`);
|
||||
hasErrors = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function createProgram(tsconfigPath) {
|
||||
const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
||||
const configHostParser = { fileExists: fs_1.existsSync, readDirectory: ts.sys.readDirectory, readFile: file => fs_1.readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' };
|
||||
const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, path_1.resolve(path_1.dirname(tsconfigPath)), { noEmit: true });
|
||||
const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true);
|
||||
return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost);
|
||||
}
|
||||
//
|
||||
// Create program and start checking
|
||||
//
|
||||
const program = createProgram(TS_CONFIG_PATH);
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
for (const rule of RULES) {
|
||||
if (minimatch_1.match([sourceFile.fileName], rule.target).length > 0) {
|
||||
if (!rule.skip) {
|
||||
checkFile(program, sourceFile, rule);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { resolve, dirname, join } from 'path';
|
||||
import { match } from 'minimatch';
|
||||
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
// A custom typescript checker for the specific task of detecting the use of certain types in a
|
||||
// layer that does not allow such use. For example:
|
||||
// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement)
|
||||
// - using node.js globals in common/browser layer (e.g. process)
|
||||
//
|
||||
// Make changes to below RULES to lift certain files from these checks only if absolutely needed
|
||||
//
|
||||
// #############################################################################################
|
||||
//
|
||||
|
||||
// Types we assume are present in all implementations of JS VMs (node.js, browsers)
|
||||
// Feel free to add more core types as you see needed if present in node.js and browsers
|
||||
const CORE_TYPES = [
|
||||
'require', // from our AMD loader
|
||||
'atob',
|
||||
'btoa',
|
||||
'setTimeout',
|
||||
'clearTimeout',
|
||||
'setInterval',
|
||||
'clearInterval',
|
||||
'console',
|
||||
'log',
|
||||
'info',
|
||||
'warn',
|
||||
'error',
|
||||
'group',
|
||||
'groupEnd',
|
||||
'table',
|
||||
'Error',
|
||||
'String',
|
||||
'throws',
|
||||
'stack',
|
||||
'captureStackTrace',
|
||||
'stackTraceLimit',
|
||||
'TextDecoder',
|
||||
'TextEncoder',
|
||||
'encode',
|
||||
'decode',
|
||||
'self',
|
||||
'trimLeft',
|
||||
'trimRight'
|
||||
];
|
||||
|
||||
const RULES = [
|
||||
|
||||
// Tests: skip
|
||||
{
|
||||
target: '**/vs/**/test/**',
|
||||
skip: true // -> skip all test files
|
||||
},
|
||||
|
||||
// Common: vs/base/common/platform.ts
|
||||
{
|
||||
target: '**/vs/base/common/platform.ts',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
|
||||
// Safe access to postMessage() and friends
|
||||
'MessageEvent',
|
||||
'data'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts', // no DOM
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
|
||||
// Common: vs/workbench/api/common/extHostExtensionService.ts
|
||||
{
|
||||
target: '**/vs/workbench/api/common/extHostExtensionService.ts',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
|
||||
// Safe access to global
|
||||
'global'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts', // no DOM
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
|
||||
// Common
|
||||
{
|
||||
target: '**/vs/**/common/**',
|
||||
allowedTypes: CORE_TYPES,
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts', // no DOM
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
|
||||
// Browser
|
||||
{
|
||||
target: '**/vs/**/browser/**',
|
||||
allowedTypes: CORE_TYPES,
|
||||
disallowedDefinitions: [
|
||||
'@types/node' // no node.js
|
||||
]
|
||||
},
|
||||
|
||||
// node.js
|
||||
{
|
||||
target: '**/vs/**/node/**',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
|
||||
// --> types from node.d.ts that duplicate from lib.dom.d.ts
|
||||
'URL',
|
||||
'protocol',
|
||||
'hostname',
|
||||
'port',
|
||||
'pathname',
|
||||
'search',
|
||||
'username',
|
||||
'password'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts' // no DOM
|
||||
]
|
||||
},
|
||||
|
||||
// Electron (renderer): skip
|
||||
{
|
||||
target: '**/vs/**/electron-browser/**',
|
||||
skip: true // -> supports all types
|
||||
},
|
||||
|
||||
// Electron (main)
|
||||
{
|
||||
target: '**/vs/**/electron-main/**',
|
||||
allowedTypes: [
|
||||
...CORE_TYPES,
|
||||
|
||||
// --> types from electron.d.ts that duplicate from lib.dom.d.ts
|
||||
'Event',
|
||||
'Request'
|
||||
],
|
||||
disallowedDefinitions: [
|
||||
'lib.dom.d.ts' // no DOM
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json');
|
||||
|
||||
interface IRule {
|
||||
target: string;
|
||||
skip?: boolean;
|
||||
allowedTypes?: string[];
|
||||
disallowedDefinitions?: string[];
|
||||
}
|
||||
|
||||
let hasErrors = false;
|
||||
|
||||
function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) {
|
||||
checkNode(sourceFile);
|
||||
|
||||
function checkNode(node: ts.Node): void {
|
||||
if (node.kind !== ts.SyntaxKind.Identifier) {
|
||||
return ts.forEachChild(node, checkNode); // recurse down
|
||||
}
|
||||
|
||||
const text = node.getText(sourceFile);
|
||||
|
||||
if (rule.allowedTypes?.some(allowed => allowed === text)) {
|
||||
return; // override
|
||||
}
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getSymbolAtLocation(node);
|
||||
if (symbol) {
|
||||
const declarations = symbol.declarations;
|
||||
if (Array.isArray(declarations)) {
|
||||
for (const declaration of declarations) {
|
||||
if (declaration) {
|
||||
const parent = declaration.parent;
|
||||
if (parent) {
|
||||
const parentSourceFile = parent.getSourceFile();
|
||||
if (parentSourceFile) {
|
||||
const definitionFileName = parentSourceFile.fileName;
|
||||
if (rule.disallowedDefinitions) {
|
||||
for (const disallowedDefinition of rule.disallowedDefinitions) {
|
||||
if (definitionFileName.indexOf(disallowedDefinition) >= 0) {
|
||||
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
||||
console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`);
|
||||
|
||||
hasErrors = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createProgram(tsconfigPath: string): ts.Program {
|
||||
const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
||||
|
||||
const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' };
|
||||
const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true });
|
||||
|
||||
const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true);
|
||||
|
||||
return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost);
|
||||
}
|
||||
|
||||
//
|
||||
// Create program and start checking
|
||||
//
|
||||
const program = createProgram(TS_CONFIG_PATH);
|
||||
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
for (const rule of RULES) {
|
||||
if (match([sourceFile.fileName], rule.target).length > 0) {
|
||||
if (!rule.skip) {
|
||||
checkFile(program, sourceFile, rule);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -45,10 +45,20 @@
|
||||
"onCommand:typescript.openTsServerLog",
|
||||
"onCommand:workbench.action.tasks.runTask",
|
||||
"onCommand:_typescript.configurePlugin",
|
||||
"onCommand:_typescript.learnMoreAboutRefactorings",
|
||||
"onLanguage:jsonc"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"contributes": {
|
||||
"documentation": {
|
||||
"refactoring": [
|
||||
{
|
||||
"title": "%documentation.refactoring.title%",
|
||||
"when": "typescript.isManagedFile",
|
||||
"command": "_typescript.learnMoreAboutRefactorings"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jsonValidation": [
|
||||
{
|
||||
"fileMatch": "package.json",
|
||||
|
||||
@@ -97,5 +97,6 @@
|
||||
"codeActions.refactor.rewrite.parameters.toDestructured.title": "Convert parameters to destructured object",
|
||||
"codeActions.refactor.rewrite.property.generateAccessors.title": "Generate accessors",
|
||||
"codeActions.refactor.rewrite.property.generateAccessors.description": "Generate 'get' and 'set' accessors",
|
||||
"codeActions.source.organizeImports.title": "Organize imports"
|
||||
"codeActions.source.organizeImports.title": "Organize imports",
|
||||
"documentation.refactoring.title": "Learn more about JS/TS refactorings"
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { OpenTsServerLogCommand } from './openTsServerLog';
|
||||
import { ReloadJavaScriptProjectsCommand, ReloadTypeScriptProjectsCommand } from './reloadProject';
|
||||
import { RestartTsServerCommand } from './restartTsServer';
|
||||
import { SelectTypeScriptVersionCommand } from './selectTypeScriptVersion';
|
||||
import { LearnMoreAboutRefactoringsCommand } from './learnMoreAboutRefactorings';
|
||||
|
||||
export function registerCommands(
|
||||
commandManager: CommandManager,
|
||||
@@ -27,4 +28,5 @@ export function registerCommands(
|
||||
commandManager.register(new TypeScriptGoToProjectConfigCommand(lazyClientHost));
|
||||
commandManager.register(new JavaScriptGoToProjectConfigCommand(lazyClientHost));
|
||||
commandManager.register(new ConfigurePluginCommand(pluginManager));
|
||||
}
|
||||
commandManager.register(new LearnMoreAboutRefactoringsCommand());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Command } from '../utils/commandManager';
|
||||
|
||||
export class LearnMoreAboutRefactoringsCommand implements Command {
|
||||
public readonly id = '_typescript.learnMoreAboutRefactorings';
|
||||
|
||||
public execute() {
|
||||
vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=2114477'));
|
||||
}
|
||||
}
|
||||
@@ -547,6 +547,8 @@ export default class BufferSyncSupport extends Disposable {
|
||||
orderedFileSet.set(resource, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
this.pendingGetErr = undefined;
|
||||
}
|
||||
|
||||
// Add all open TS buffers to the geterr request. They might be visible
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.42.0",
|
||||
"distro": "57c315cb98e064a152bcc6a599ed459d67d933e2",
|
||||
"distro": "aafa05cffde354e7f58dc9fd2dcfc9aa3e81ea77",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -25,7 +25,7 @@
|
||||
"smoketest": "cd test/smoke && node test/index.js",
|
||||
"download-builtin-extensions": "node build/lib/builtInExtensions.js",
|
||||
"monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit",
|
||||
"valid-globals-check": "node build/lib/globalsLinter.js",
|
||||
"valid-layers-check": "node build/lib/layersChecker.js",
|
||||
"strict-function-types-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictFunctionTypes",
|
||||
"update-distro": "node build/npm/update-distro.js",
|
||||
"web": "node scripts/code-web.js",
|
||||
@@ -55,7 +55,7 @@
|
||||
"vscode-ripgrep": "^1.5.7",
|
||||
"vscode-sqlite3": "4.0.9",
|
||||
"vscode-textmate": "4.4.0",
|
||||
"xterm": "4.4.0-beta.13",
|
||||
"xterm": "4.4.0-beta.15",
|
||||
"xterm-addon-search": "0.4.0-beta4",
|
||||
"xterm-addon-web-links": "0.2.1",
|
||||
"xterm-addon-webgl": "0.5.0-beta.7",
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
"vscode-proxy-agent": "^0.5.2",
|
||||
"vscode-ripgrep": "^1.5.7",
|
||||
"vscode-textmate": "4.4.0",
|
||||
"xterm": "4.4.0-beta.13",
|
||||
"xterm": "4.4.0-beta.15",
|
||||
"xterm-addon-search": "0.4.0-beta4",
|
||||
"xterm-addon-web-links": "0.2.1",
|
||||
"xterm-addon-webgl": "0.5.0-beta.7",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"onigasm-umd": "2.2.5",
|
||||
"semver-umd": "^5.5.5",
|
||||
"vscode-textmate": "4.4.0",
|
||||
"xterm": "4.4.0-beta.13",
|
||||
"xterm": "4.4.0-beta.15",
|
||||
"xterm-addon-search": "0.4.0-beta4",
|
||||
"xterm-addon-web-links": "0.2.1",
|
||||
"xterm-addon-webgl": "0.5.0-beta.7"
|
||||
|
||||
@@ -46,7 +46,7 @@ xterm-addon-webgl@0.5.0-beta.7:
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0-beta.7.tgz#b7b95a362e942ad6f86fa286d7b7bd8ee3e7cf67"
|
||||
integrity sha512-v6aCvhm1C6mvaurGwUYQfyhb2cAUyuVnzf3Ob/hy5ebtyzUj4wW0N9NbqDEJk67UeMi1lV2xZqrO5gNeTpVqFA==
|
||||
|
||||
xterm@4.4.0-beta.13:
|
||||
version "4.4.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.13.tgz#f7c5fa0d2b098ce0dd8b7c96d3d5fcaee22b86ed"
|
||||
integrity sha512-ZoDOVO3w84CXekBveGw1H2lcvM4HkJG5suXesE/3S+N4DnBhBcK/vw4kdooALGoorJV2GtgA1XEA6+m4N5Sgnw==
|
||||
xterm@4.4.0-beta.15:
|
||||
version "4.4.0-beta.15"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.15.tgz#5897bf79d29d1a2496ccd54665aded28c341b1cc"
|
||||
integrity sha512-Dvz1CMCYKeoxPF7uIDznbRgUA2Mct49Bq93K2nnrDU0pDMM3Sf1t9fkEyz59wxSx5XEHVdLS80jywsz4sjXBjQ==
|
||||
|
||||
+4
-4
@@ -433,10 +433,10 @@ xterm-addon-webgl@0.5.0-beta.7:
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0-beta.7.tgz#b7b95a362e942ad6f86fa286d7b7bd8ee3e7cf67"
|
||||
integrity sha512-v6aCvhm1C6mvaurGwUYQfyhb2cAUyuVnzf3Ob/hy5ebtyzUj4wW0N9NbqDEJk67UeMi1lV2xZqrO5gNeTpVqFA==
|
||||
|
||||
xterm@4.4.0-beta.13:
|
||||
version "4.4.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.13.tgz#f7c5fa0d2b098ce0dd8b7c96d3d5fcaee22b86ed"
|
||||
integrity sha512-ZoDOVO3w84CXekBveGw1H2lcvM4HkJG5suXesE/3S+N4DnBhBcK/vw4kdooALGoorJV2GtgA1XEA6+m4N5Sgnw==
|
||||
xterm@4.4.0-beta.15:
|
||||
version "4.4.0-beta.15"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.15.tgz#5897bf79d29d1a2496ccd54665aded28c341b1cc"
|
||||
integrity sha512-Dvz1CMCYKeoxPF7uIDznbRgUA2Mct49Bq93K2nnrDU0pDMM3Sf1t9fkEyz59wxSx5XEHVdLS80jywsz4sjXBjQ==
|
||||
|
||||
yauzl@^2.9.2:
|
||||
version "2.10.0"
|
||||
|
||||
@@ -12,7 +12,7 @@ const INITIALIZE = '$initialize';
|
||||
|
||||
export interface IWorker extends IDisposable {
|
||||
getId(): number;
|
||||
postMessage(message: any, transfer: Transferable[]): void;
|
||||
postMessage(message: any, transfer: ArrayBuffer[]): void;
|
||||
}
|
||||
|
||||
export interface IWorkerCallback {
|
||||
@@ -302,7 +302,7 @@ export class SimpleWorkerServer<H extends object> {
|
||||
private _requestHandler: IRequestHandler | null;
|
||||
private _protocol: SimpleWorkerProtocol;
|
||||
|
||||
constructor(postMessage: (msg: any, transfer?: Transferable[]) => void, requestHandlerFactory: IRequestHandlerFactory<H> | null) {
|
||||
constructor(postMessage: (msg: any, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory<H> | null) {
|
||||
this._requestHandlerFactory = requestHandlerFactory;
|
||||
this._requestHandler = null;
|
||||
this._protocol = new SimpleWorkerProtocol({
|
||||
|
||||
@@ -68,9 +68,7 @@ export interface IDataSource<T> {
|
||||
export interface IRenderer<T> {
|
||||
getHeight(entry: T): number;
|
||||
getTemplateId(entry: T): string;
|
||||
// rationale: will be replaced by quickinput later
|
||||
// tslint:disable-next-line: no-dom-globals
|
||||
renderTemplate(templateId: string, container: HTMLElement, styles: any): any;
|
||||
renderTemplate(templateId: string, container: any /* HTMLElement */, styles: any): any;
|
||||
renderElement(entry: T, templateId: string, templateData: any, styles: any): void;
|
||||
disposeTemplate(templateId: string, templateData: any): void;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
@@ -14,7 +15,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { codeActionCommandId, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/types';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
@@ -83,8 +84,7 @@ export class CodeActionMenu extends Disposable {
|
||||
this._visible = true;
|
||||
this._showingActions.value = codeActions;
|
||||
|
||||
const menuActions = actionsToShow.map(action =>
|
||||
new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action)));
|
||||
const menuActions = this.getMenuActions(actionsToShow);
|
||||
|
||||
const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 };
|
||||
const resolver = this._keybindingResolver.getResolver();
|
||||
@@ -101,6 +101,24 @@ export class CodeActionMenu extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private getMenuActions(actionsToShow: readonly CodeAction[]): IAction[] {
|
||||
const allActions = actionsToShow
|
||||
.map(action => new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action)));
|
||||
|
||||
// Treat documentation actions as special
|
||||
const result: IAction[] = allActions
|
||||
.filter(action => !action.action.kind || !CodeActionKind.RefactorDocumentation.contains(new CodeActionKind(action.action.kind)));
|
||||
|
||||
const documentationActions = allActions
|
||||
.filter(action => action.action.kind && CodeActionKind.RefactorDocumentation.contains(new CodeActionKind(action.action.kind)));
|
||||
|
||||
if (documentationActions.length) {
|
||||
result.push(new Separator(), ...documentationActions);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _toCoords(position: IPosition): { x: number, y: number } {
|
||||
if (!this._editor.hasModel()) {
|
||||
return { x: 0, y: 0 };
|
||||
|
||||
@@ -14,6 +14,7 @@ export class CodeActionKind {
|
||||
public static readonly Empty = new CodeActionKind('');
|
||||
public static readonly QuickFix = new CodeActionKind('quickfix');
|
||||
public static readonly Refactor = new CodeActionKind('refactor');
|
||||
public static readonly RefactorDocumentation = new CodeActionKind('refactor.documentation');
|
||||
public static readonly Source = new CodeActionKind('source');
|
||||
public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
|
||||
public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll');
|
||||
|
||||
@@ -329,24 +329,31 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
// commands that aren't needed anymore because there is now ContextKeyExpr.OR
|
||||
CommandsRegistry.registerCommandAlias('goToNextReferenceFromEmbeddedEditor', 'goToNextReference');
|
||||
CommandsRegistry.registerCommandAlias('goToPreviousReferenceFromEmbeddedEditor', 'goToPreviousReference');
|
||||
|
||||
// close
|
||||
CommandsRegistry.registerCommandAlias('closeReferenceSearchEditor', 'closeReferenceSearch');
|
||||
CommandsRegistry.registerCommand(
|
||||
'closeReferenceSearch',
|
||||
accessor => withController(accessor, controller => controller.closeWidget())
|
||||
);
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: 'closeReferenceSearch',
|
||||
weight: KeybindingWeight.EditorContrib - 101,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
when: ContextKeyExpr.or(
|
||||
ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')),
|
||||
ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek'))
|
||||
),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
withController(accessor, controller => controller.closeWidget());
|
||||
}
|
||||
when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek'))
|
||||
});
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: 'closeReferenceSearch',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek'))
|
||||
});
|
||||
|
||||
// commands that aren't needed anymore because there is now ContextKeyExpr.OR
|
||||
CommandsRegistry.registerCommandAlias('goToNextReferenceFromEmbeddedEditor', 'goToNextReference');
|
||||
CommandsRegistry.registerCommandAlias('goToPreviousReferenceFromEmbeddedEditor', 'goToPreviousReference');
|
||||
CommandsRegistry.registerCommandAlias('closeReferenceSearchEditor', 'closeReferenceSearch');
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'openReferenceToSide',
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface ResolvedOptions {
|
||||
|
||||
export interface TunnelInformation {
|
||||
environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[];
|
||||
hideCandidatePorts?: boolean;
|
||||
}
|
||||
|
||||
export interface ResolverResult {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IGlobalState } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { values } from 'vs/base/common/map';
|
||||
|
||||
export function merge(localGloablState: IGlobalState, remoteGlobalState: IGlobalState | null, lastSyncGlobalState: IGlobalState | null): { local?: IGlobalState, remote?: IGlobalState } {
|
||||
if (!remoteGlobalState) {
|
||||
return { remote: localGloablState };
|
||||
}
|
||||
|
||||
const { local: localArgv, remote: remoteArgv } = doMerge(localGloablState.argv, remoteGlobalState.argv, lastSyncGlobalState ? lastSyncGlobalState.argv : null);
|
||||
const { local: localStorage, remote: remoteStorage } = doMerge(localGloablState.storage, remoteGlobalState.storage, lastSyncGlobalState ? lastSyncGlobalState.storage : null);
|
||||
const local: IGlobalState | undefined = localArgv || localStorage ? { argv: localArgv || localGloablState.argv, storage: localStorage || localGloablState.storage } : undefined;
|
||||
const remote: IGlobalState | undefined = remoteArgv || remoteStorage ? { argv: remoteArgv || remoteGlobalState.argv, storage: remoteStorage || remoteGlobalState.storage } : undefined;
|
||||
|
||||
return { local, remote };
|
||||
}
|
||||
|
||||
function doMerge(local: IStringDictionary<any>, remote: IStringDictionary<any>, base: IStringDictionary<any> | null): { local?: IStringDictionary<any>, remote?: IStringDictionary<any> } {
|
||||
const localToRemote = compare(local, remote);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return {};
|
||||
}
|
||||
|
||||
const baseToRemote = base ? compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
if (baseToRemote.added.size === 0 && baseToRemote.removed.size === 0 && baseToRemote.updated.size === 0) {
|
||||
// No changes found between base and remote.
|
||||
return { remote: local };
|
||||
}
|
||||
|
||||
const baseToLocal = base ? compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
if (baseToLocal.added.size === 0 && baseToLocal.removed.size === 0 && baseToLocal.updated.size === 0) {
|
||||
// No changes found between base and local.
|
||||
return { local: remote };
|
||||
}
|
||||
|
||||
const merged = objects.deepClone(local);
|
||||
|
||||
// Added in remote
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
merged[key] = remote[key];
|
||||
}
|
||||
|
||||
// Updated in Remote
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
merged[key] = remote[key];
|
||||
}
|
||||
|
||||
// Removed in remote & local
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
// Got removed in local
|
||||
if (baseToLocal.removed.has(key)) {
|
||||
delete merged[key];
|
||||
}
|
||||
}
|
||||
|
||||
return { local: merged, remote: merged };
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = Object.keys(from);
|
||||
const toKeys = Object.keys(to);
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const value1 = from[key];
|
||||
const value2 = to[key];
|
||||
if (!objects.equals(value1, value2)) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, dirname } from 'vs/base/common/resources';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
|
||||
const argvProperties: string[] = ['locale'];
|
||||
|
||||
export class GlobalStateSynchroniser extends Disposable implements ISynchroniser {
|
||||
|
||||
private static EXTERNAL_USER_DATA_GLOBAL_STATE_KEY: string = 'globalState';
|
||||
|
||||
private _status: SyncStatus = SyncStatus.Idle;
|
||||
get status(): SyncStatus { return this._status; }
|
||||
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
|
||||
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
|
||||
|
||||
private _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
|
||||
|
||||
private readonly lastSyncGlobalStateResource: URI;
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
this.lastSyncGlobalStateResource = joinPath(environmentService.userRoamingDataHome, '.lastSyncGlobalState');
|
||||
this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
|
||||
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
|
||||
}
|
||||
|
||||
private setStatus(status: SyncStatus): void {
|
||||
if (this._status !== status) {
|
||||
this._status = status;
|
||||
this._onDidChangStatus.fire(status);
|
||||
}
|
||||
}
|
||||
|
||||
async sync(): Promise<boolean> {
|
||||
if (!this.configurationService.getValue<boolean>('sync.enableUIState')) {
|
||||
this.logService.trace('UI State: Skipping synchronizing UI state as it is disabled.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.status !== SyncStatus.Idle) {
|
||||
this.logService.trace('UI State: Skipping synchronizing ui state as it is running already.');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logService.trace('UI State: Started synchronizing ui state...');
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
try {
|
||||
await this.doSync();
|
||||
this.logService.trace('UI State: Finised synchronizing ui state.');
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void { }
|
||||
|
||||
private async doSync(): Promise<void> {
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const lastSyncGlobalState = lastSyncData && lastSyncData.content ? JSON.parse(lastSyncData.content) : null;
|
||||
|
||||
let remoteData = await this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData);
|
||||
const remoteGlobalState: IGlobalState = remoteData.content ? JSON.parse(remoteData.content) : null;
|
||||
|
||||
const localGloablState = await this.getLocalGlobalState();
|
||||
|
||||
const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState);
|
||||
|
||||
if (local) {
|
||||
// update local
|
||||
this.logService.info('UI State: Updating local ui state...');
|
||||
await this.writeLocalGlobalState(local);
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
// update remote
|
||||
this.logService.info('UI State: Updating remote ui state...');
|
||||
remoteData = await this.writeToRemote(remote, remoteData.ref);
|
||||
}
|
||||
|
||||
if (remoteData.content
|
||||
&& (!lastSyncData || lastSyncData.ref !== remoteData.ref)
|
||||
) {
|
||||
// update last sync
|
||||
this.logService.info('UI State: Updating last synchronised ui state...');
|
||||
await this.updateLastSyncValue(remoteData);
|
||||
}
|
||||
}
|
||||
|
||||
private async getLocalGlobalState(): Promise<IGlobalState> {
|
||||
const argv: IStringDictionary<any> = {};
|
||||
const storage: IStringDictionary<any> = {};
|
||||
try {
|
||||
const content = await this.fileService.readFile(this.environmentService.argvResource);
|
||||
const argvValue: IStringDictionary<any> = parse(content.value.toString());
|
||||
for (const argvProperty of argvProperties) {
|
||||
if (argvValue[argvProperty] !== undefined) {
|
||||
argv[argvProperty] = argvValue[argvProperty];
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
return { argv, storage };
|
||||
}
|
||||
|
||||
private async writeLocalGlobalState(globalState: IGlobalState): Promise<void> {
|
||||
const content = await this.fileService.readFile(this.environmentService.argvResource);
|
||||
let argvContent = content.value.toString();
|
||||
for (const argvProperty of Object.keys(globalState.argv)) {
|
||||
argvContent = edit(argvContent, [argvProperty], globalState.argv[argvProperty], {});
|
||||
}
|
||||
if (argvContent !== content.value.toString()) {
|
||||
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(argvContent));
|
||||
}
|
||||
}
|
||||
|
||||
private async getLastSyncUserData(): Promise<IUserData | null> {
|
||||
try {
|
||||
const content = await this.fileService.readFile(this.lastSyncGlobalStateResource);
|
||||
return JSON.parse(content.value.toString());
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async updateLastSyncValue(remoteUserData: IUserData): Promise<void> {
|
||||
await this.fileService.writeFile(this.lastSyncGlobalStateResource, VSBuffer.fromString(JSON.stringify(remoteUserData)));
|
||||
}
|
||||
|
||||
private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise<IUserData> {
|
||||
const content = JSON.stringify(globalState);
|
||||
ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref);
|
||||
return { content, ref };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,18 +49,24 @@ export function registerConfiguration(): IDisposable {
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'sync.enableExtensions': {
|
||||
type: 'boolean',
|
||||
description: localize('sync.enableExtensions', "Enable synchronizing extensions."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'sync.enableKeybindings': {
|
||||
type: 'boolean',
|
||||
description: localize('sync.enableKeybindings', "Enable synchronizing keybindings."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'sync.enableUIState': {
|
||||
type: 'boolean',
|
||||
description: localize('sync.enableUIState', "Enable synchronizing UI state."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'sync.enableExtensions': {
|
||||
type: 'boolean',
|
||||
description: localize('sync.enableExtensions', "Enable synchronizing extensions."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'sync.keybindingsPerPlatform': {
|
||||
type: 'boolean',
|
||||
description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."),
|
||||
@@ -145,6 +151,11 @@ export interface ISyncExtension {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface IGlobalState {
|
||||
argv: IStringDictionary<any>;
|
||||
storage: IStringDictionary<any>;
|
||||
}
|
||||
|
||||
export const enum SyncSource {
|
||||
Settings = 1,
|
||||
Keybindings,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensio
|
||||
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
|
||||
import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
|
||||
import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync';
|
||||
|
||||
export class UserDataSyncService extends Disposable implements IUserDataSyncService {
|
||||
|
||||
@@ -32,6 +33,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
private readonly settingsSynchroniser: SettingsSynchroniser;
|
||||
private readonly keybindingsSynchroniser: KeybindingsSynchroniser;
|
||||
private readonly extensionsSynchroniser: ExtensionsSynchroniser;
|
||||
private readonly globalStateSynchroniser: GlobalStateSynchroniser;
|
||||
|
||||
constructor(
|
||||
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@@ -41,8 +43,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
super();
|
||||
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
|
||||
this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser));
|
||||
this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser));
|
||||
this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser));
|
||||
this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.extensionsSynchroniser];
|
||||
this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.globalStateSynchroniser, this.extensionsSynchroniser];
|
||||
this.updateStatus();
|
||||
|
||||
if (this.userDataSyncStoreService.userDataSyncStore) {
|
||||
|
||||
Vendored
+3
-1
@@ -59,6 +59,8 @@ declare module 'vscode' {
|
||||
* detected are read-only from the forwarded ports UI.
|
||||
*/
|
||||
environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[];
|
||||
|
||||
hideCandidatePorts?: boolean;
|
||||
}
|
||||
|
||||
export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation;
|
||||
@@ -1318,7 +1320,7 @@ declare module 'vscode' {
|
||||
|
||||
//#region Language specific settings: https://github.com/microsoft/vscode/issues/26707
|
||||
|
||||
export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { resource: Uri, languageId: string };
|
||||
export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { uri: Uri, languageId: string };
|
||||
|
||||
/**
|
||||
* An event describing the change in Configuration
|
||||
|
||||
@@ -47,12 +47,6 @@ type ConfigurationInspect<T> = {
|
||||
workspaceFolderLanguageValue?: T;
|
||||
};
|
||||
|
||||
function isTextDocument(thing: any): thing is vscode.TextDocument {
|
||||
return thing
|
||||
&& thing.uri instanceof URI
|
||||
&& (!thing.languageId || typeof thing.languageId === 'string');
|
||||
}
|
||||
|
||||
function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder {
|
||||
return thing
|
||||
&& thing.uri instanceof URI
|
||||
@@ -64,9 +58,9 @@ function isUri(thing: any): thing is vscode.Uri {
|
||||
return thing instanceof URI;
|
||||
}
|
||||
|
||||
function isResourceLanguage(thing: any): thing is { resource: URI, languageId: string } {
|
||||
function isResourceLanguage(thing: any): thing is { uri: URI, languageId: string } {
|
||||
return thing
|
||||
&& thing.resource instanceof URI
|
||||
&& thing.uri instanceof URI
|
||||
&& (!thing.languageId || typeof thing.languageId === 'string');
|
||||
}
|
||||
|
||||
@@ -77,11 +71,8 @@ function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null):
|
||||
if (isWorkspaceFolder(scope)) {
|
||||
return { resource: scope.uri };
|
||||
}
|
||||
if (isTextDocument(scope)) {
|
||||
return { resource: scope.uri, overrideIdentifier: scope.languageId };
|
||||
}
|
||||
if (isResourceLanguage(scope)) {
|
||||
return scope;
|
||||
return { resource: scope.uri, overrideIdentifier: scope.languageId };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
value: {
|
||||
authority,
|
||||
options,
|
||||
tunnelInformation: { environmentTunnels: result.environmentTunnels }
|
||||
tunnelInformation: { environmentTunnels: result.environmentTunnels, hideCandidatePorts: result.hideCandidatePorts }
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as vscode from 'vscode';
|
||||
import * as env from 'vs/base/common/platform';
|
||||
import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
|
||||
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
|
||||
@@ -23,7 +24,6 @@ import { SignService } from 'vs/platform/sign/node/signService';
|
||||
import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
|
||||
|
||||
export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
@@ -87,7 +87,22 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
}
|
||||
|
||||
const configProvider = await this._configurationService.getConfigProvider();
|
||||
const shell = this._terminalService.getDefaultShell(true, configProvider);
|
||||
const terminalConfig = configProvider.getConfiguration('terminal');
|
||||
|
||||
let shell;
|
||||
const automationShellConfig = terminalConfig.integrated.automationShell;
|
||||
if (automationShellConfig) {
|
||||
if (env.isWindows) {
|
||||
shell = automationShellConfig.windows;
|
||||
} else if (env.isLinux) {
|
||||
shell = automationShellConfig.linux;
|
||||
} else if (env.isMacintosh) {
|
||||
shell = automationShellConfig.osx;
|
||||
}
|
||||
}
|
||||
if (!shell) {
|
||||
shell = this._terminalService.getDefaultShell(true, configProvider);
|
||||
}
|
||||
|
||||
if (needNewTerminal || !this._integratedTerminalInstance) {
|
||||
|
||||
@@ -108,7 +123,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
terminal.show();
|
||||
|
||||
const shellProcessId = await this._integratedTerminalInstance.processId;
|
||||
const command = prepareCommand(args, shell, configProvider);
|
||||
const command = prepareCommand(args, shell);
|
||||
terminal.sendText(command, true);
|
||||
|
||||
return shellProcessId;
|
||||
@@ -121,7 +136,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
}
|
||||
|
||||
protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService {
|
||||
return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as IProcessEnvironment);
|
||||
return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as env.IProcessEnvironment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
// can review if the files should be changed or not.
|
||||
await Promise.all(this.groupsToClose.map(async groupToClose => {
|
||||
for (const editor of groupToClose.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
|
||||
if (editor.isDirty()) {
|
||||
if (editor.isDirty() && !editor.isSaving() /* ignore editors that are being saved */) {
|
||||
return groupToClose.openEditor(editor);
|
||||
}
|
||||
}
|
||||
@@ -644,8 +644,8 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
const dirtyEditorsToConfirmByResource = new ResourceMap();
|
||||
|
||||
for (const editor of this.editorService.editors) {
|
||||
if (!editor.isDirty()) {
|
||||
continue; // only interested in dirty editors
|
||||
if (!editor.isDirty() || editor.isSaving()) {
|
||||
continue; // only interested in dirty editors (unless in the process of saving)
|
||||
}
|
||||
|
||||
const resource = editor.getResource();
|
||||
|
||||
@@ -226,8 +226,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
const activeEditor = this._group.activeEditor;
|
||||
if (activeEditor) {
|
||||
groupActiveEditorDirtyContextKey.set(activeEditor.isDirty());
|
||||
activeEditorListener.value = activeEditor.onDidChangeDirty(() => groupActiveEditorDirtyContextKey.set(activeEditor.isDirty()));
|
||||
groupActiveEditorDirtyContextKey.set(activeEditor.isDirty() && !activeEditor.isSaving());
|
||||
activeEditorListener.value = activeEditor.onDidChangeDirty(() => groupActiveEditorDirtyContextKey.set(activeEditor.isDirty() && !activeEditor.isSaving()));
|
||||
} else {
|
||||
groupActiveEditorDirtyContextKey.set(false);
|
||||
}
|
||||
@@ -1271,8 +1271,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
|
||||
private async doHandleDirtyClosing(editor: EditorInput): Promise<boolean /* veto */> {
|
||||
if (!editor.isDirty()) {
|
||||
return false; // editor must be dirty
|
||||
if (!editor.isDirty() || editor.isSaving()) {
|
||||
return false; // editor must be dirty and not saving
|
||||
}
|
||||
|
||||
if (editor instanceof SideBySideEditorInput && this.isOpened(editor.master)) {
|
||||
@@ -1309,11 +1309,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const res = await this.fileDialogService.showSaveConfirm(editorResource ? [editorResource] : [editor.getName()]);
|
||||
|
||||
// It could be that the editor saved meanwhile, so we check again
|
||||
// to see if anything needs to happen before closing for good.
|
||||
// It could be that the editor saved meanwhile or is saving, so we check
|
||||
// again to see if anything needs to happen before closing for good.
|
||||
// This can happen for example if autoSave: onFocusChange is configured
|
||||
// so that the save happens when the dialog opens.
|
||||
if (!editor.isDirty()) {
|
||||
if (!editor.isDirty() || editor.isSaving()) {
|
||||
return res === ConfirmResult.CANCEL ? true : false;
|
||||
}
|
||||
|
||||
@@ -1376,9 +1376,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE); // in MRU order only if direction is not specified
|
||||
|
||||
// Filter: saved only
|
||||
// Filter: saved or saving only
|
||||
if (filter.savedOnly) {
|
||||
editorsToClose = editorsToClose.filter(e => !e.isDirty());
|
||||
editorsToClose = editorsToClose.filter(e => !e.isDirty() || e.isSaving());
|
||||
}
|
||||
|
||||
// Filter: direction (left / right)
|
||||
@@ -1471,7 +1471,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
let activeReplacement: EditorReplacement | undefined;
|
||||
const inactiveReplacements: EditorReplacement[] = [];
|
||||
editors.forEach(({ editor, replacement, options }) => {
|
||||
if (editor.isDirty()) {
|
||||
if (editor.isDirty() && !editor.isSaving()) {
|
||||
return; // we do not handle dirty in this method, so ignore all dirty
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return this.editor.isDirty() ? 'codicon codicon-circle-filled' : '';
|
||||
return this.editor.isDirty() && !this.editor.isSaving() ? 'codicon codicon-circle-filled' : '';
|
||||
}
|
||||
|
||||
get group(): IEditorGroup {
|
||||
|
||||
@@ -260,8 +260,8 @@ export class EditorsObserver extends Disposable {
|
||||
|
||||
// Extract least recently used editors that can be closed
|
||||
const leastRecentlyClosableEditors = mostRecentEditors.reverse().filter(({ editor, groupId }) => {
|
||||
if (editor.isDirty()) {
|
||||
return false; // not dirty editors
|
||||
if (editor.isDirty() && !editor.isSaving()) {
|
||||
return false; // not dirty editors (unless in the process of saving)
|
||||
}
|
||||
|
||||
if (exclude && editor === exclude.editor && groupId === exclude.groupId) {
|
||||
|
||||
@@ -166,9 +166,14 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
updateEditorDirty(editor: IEditorInput): void {
|
||||
this.ifEditorIsActive(editor, () => {
|
||||
const titleContainer = assertIsDefined(this.titleContainer);
|
||||
if (editor.isDirty()) {
|
||||
|
||||
// Signal dirty (unless saving)
|
||||
if (editor.isDirty() && !editor.isSaving()) {
|
||||
addClass(titleContainer, 'dirty');
|
||||
} else {
|
||||
}
|
||||
|
||||
// Otherwise, clear dirty
|
||||
else {
|
||||
removeClass(titleContainer, 'dirty');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1001,8 +1001,8 @@ export class TabsTitleControl extends TitleControl {
|
||||
private doRedrawEditorDirty(isGroupActive: boolean, isTabActive: boolean, editor: IEditorInput, tabContainer: HTMLElement): boolean {
|
||||
let hasModifiedBorderColor = false;
|
||||
|
||||
// Tab: dirty
|
||||
if (editor.isDirty()) {
|
||||
// Tab: dirty (unless saving)
|
||||
if (editor.isDirty() && !editor.isSaving()) {
|
||||
addClass(tabContainer, 'dirty');
|
||||
|
||||
// Highlight modified tabs with a border if configured
|
||||
|
||||
@@ -726,7 +726,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
|
||||
private resource: URI | undefined;
|
||||
private label: string;
|
||||
private description?: string;
|
||||
private dirty: boolean;
|
||||
private icon: string;
|
||||
|
||||
constructor(
|
||||
input: IEditorInput | IResourceInput,
|
||||
@@ -747,22 +747,29 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
|
||||
this.resource = resourceForEditorHistory(input, fileService);
|
||||
this.label = input.getName();
|
||||
this.description = input.getDescription();
|
||||
this.dirty = input.isDirty();
|
||||
this.icon = this.getDirtyIndicatorForEditor(input);
|
||||
} else {
|
||||
const resourceInput = input as IResourceInput;
|
||||
this.resource = resourceInput.resource;
|
||||
this.label = resources.basenameOrAuthority(resourceInput.resource);
|
||||
this.description = labelService.getUriLabel(resources.dirname(this.resource), { relative: true });
|
||||
this.dirty = this.resource && this.textFileService.isDirty(this.resource);
|
||||
|
||||
if (this.dirty && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
this.dirty = false; // no dirty decoration if auto save is on with a short timeout
|
||||
}
|
||||
this.icon = this.getDirtyIndicatorForEditor(resourceInput);
|
||||
}
|
||||
}
|
||||
|
||||
private getDirtyIndicatorForEditor(input: EditorInput | IResourceInput): string {
|
||||
let signalDirty = false;
|
||||
if (input instanceof EditorInput) {
|
||||
signalDirty = input.isDirty() && !input.isSaving();
|
||||
} else {
|
||||
signalDirty = this.textFileService.isDirty(input.resource) && this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY;
|
||||
}
|
||||
|
||||
return signalDirty ? 'codicon codicon-circle-filled' : '';
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return this.dirty ? 'codicon codicon-circle-filled' : '';
|
||||
return this.icon;
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
|
||||
@@ -316,7 +316,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
const rootPath = root ? this.labelService.getUriLabel(root) : '';
|
||||
const folderName = folder ? folder.name : '';
|
||||
const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
|
||||
const dirty = editor?.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
|
||||
const dirty = editor?.isDirty() && !editor.isSaving() ? TitlebarPart.TITLE_DIRTY : '';
|
||||
const appName = this.productService.nameLong;
|
||||
const remoteName = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority);
|
||||
const separator = TitlebarPart.TITLE_SEPARATOR;
|
||||
|
||||
@@ -664,8 +664,17 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
}
|
||||
|
||||
getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null {
|
||||
const viewDescriptorCollectionItem = this.viewDescriptorCollections.get(container);
|
||||
return viewDescriptorCollectionItem ? viewDescriptorCollectionItem.viewDescriptorCollection : null;
|
||||
const registeredViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).get(container.id);
|
||||
if (registeredViewContainer) {
|
||||
let viewDescriptorCollectionItem = this.viewDescriptorCollections.get(registeredViewContainer);
|
||||
if (!viewDescriptorCollectionItem) {
|
||||
// Create and register the collection if does not exist
|
||||
this.onDidRegisterViewContainer(registeredViewContainer);
|
||||
viewDescriptorCollectionItem = this.viewDescriptorCollections.get(registeredViewContainer);
|
||||
}
|
||||
return viewDescriptorCollectionItem!.viewDescriptorCollection;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async openView(id: string, focus: boolean): Promise<IView | null> {
|
||||
|
||||
@@ -403,6 +403,14 @@ export interface IEditorInput extends IDisposable {
|
||||
*/
|
||||
isDirty(): boolean;
|
||||
|
||||
/**
|
||||
* Returns if this input is currently being saved or soon to be
|
||||
* saved. Based on this assumption the editor may for example
|
||||
* decide to not signal the dirty state to the user assuming that
|
||||
* the save is scheduled to happen anyway.
|
||||
*/
|
||||
isSaving(): boolean;
|
||||
|
||||
/**
|
||||
* Saves the editor. The provided groupId helps
|
||||
* implementors to e.g. preserve view state of the editor
|
||||
@@ -508,16 +516,20 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
|
||||
return false;
|
||||
}
|
||||
|
||||
save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
isSaving(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
revert(options?: IRevertOptions): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async revert(options?: IRevertOptions): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -701,6 +713,10 @@ export class SideBySideEditorInput extends EditorInput {
|
||||
return this.master.isDirty();
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
return this.master.isSaving();
|
||||
}
|
||||
|
||||
save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
return this.master.save(groupId, options);
|
||||
}
|
||||
@@ -741,8 +757,8 @@ export class SideBySideEditorInput extends EditorInput {
|
||||
this._register(this.master.onDidChangeLabel(() => this._onDidChangeLabel.fire()));
|
||||
}
|
||||
|
||||
resolve(): Promise<EditorModel | null> {
|
||||
return Promise.resolve(null);
|
||||
async resolve(): Promise<EditorModel | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
@@ -791,8 +807,8 @@ export class EditorModel extends Disposable implements IEditorModel {
|
||||
/**
|
||||
* Causes this model to load returning a promise when loading is completed.
|
||||
*/
|
||||
load(): Promise<IEditorModel> {
|
||||
return Promise.resolve(this);
|
||||
async load(): Promise<IEditorModel> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,13 @@ export class DiffEditorInput extends SideBySideEditorInput {
|
||||
super(name, description, original, modified);
|
||||
}
|
||||
|
||||
matches(otherInput: unknown): boolean {
|
||||
if (!super.matches(otherInput)) {
|
||||
return false;
|
||||
}
|
||||
return otherInput instanceof DiffEditorInput && otherInput.forceOpenAsBinary === this.forceOpenAsBinary;
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return DiffEditorInput.ID;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex
|
||||
import { ITextBufferFactory } from 'vs/editor/common/model';
|
||||
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
|
||||
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
||||
export class UntitledTextEditorModel extends BaseTextEditorModel implements IEncodingSupport, IWorkingCopy {
|
||||
@@ -32,12 +32,12 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IEnc
|
||||
private readonly _onDidChangeEncoding: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeEncoding: Event<void> = this._onDidChangeEncoding.event;
|
||||
|
||||
readonly capabilities = 0;
|
||||
readonly capabilities = WorkingCopyCapabilities.Untitled;
|
||||
|
||||
private dirty = false;
|
||||
private versionId = 0;
|
||||
private readonly contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledTextEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY));
|
||||
private configuredEncoding?: string;
|
||||
private configuredEncoding: string | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly preferredMode: string | undefined,
|
||||
|
||||
@@ -4,26 +4,30 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { CodeActionWorkbenchContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/configuration';
|
||||
import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/extensionPoint';
|
||||
import { CodeActionsContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/codeActionsContribution';
|
||||
import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint';
|
||||
import { CodeActionDocumentationContribution } from 'vs/workbench/contrib/codeActions/common/documentationContribution';
|
||||
import { DocumentationExtensionPoint, documentationExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/documentationExtensionPoint';
|
||||
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
|
||||
const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint<CodeActionsExtensionPoint[]>(codeActionsExtensionPointDescriptor);
|
||||
const documentationExtensionPoint = ExtensionsRegistry.registerExtensionPoint<DocumentationExtensionPoint>(documentationExtensionPointDescriptor);
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
|
||||
.registerConfiguration(editorConfiguration);
|
||||
|
||||
class WorkbenchContribution {
|
||||
class WorkbenchConfigurationContribution {
|
||||
constructor(
|
||||
@IKeybindingService keybindingsService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
new CodeActionWorkbenchContribution(codeActionsExtensionPoint, keybindingsService);
|
||||
instantiationService.createInstance(CodeActionsContribution, codeActionsExtensionPoint);
|
||||
instantiationService.createInstance(CodeActionDocumentationContribution, documentationExtensionPoint);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(WorkbenchContribution, LifecyclePhase.Eventually);
|
||||
.registerWorkbenchContribution(WorkbenchConfigurationContribution, LifecyclePhase.Eventually);
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ import { Extensions, IConfigurationNode, IConfigurationRegistry, ConfigurationSc
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { CodeActionsExtensionPoint, ContributedCodeAction } from 'vs/workbench/contrib/codeActions/common/extensionPoint';
|
||||
import { CodeActionsExtensionPoint, ContributedCodeAction } from 'vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint';
|
||||
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig';
|
||||
|
||||
@@ -50,7 +50,7 @@ export const editorConfiguration = Object.freeze<IConfigurationNode>({
|
||||
}
|
||||
});
|
||||
|
||||
export class CodeActionWorkbenchContribution extends Disposable implements IWorkbenchContribution {
|
||||
export class CodeActionsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private _contributedCodeActions: CodeActionsExtensionPoint[] = [];
|
||||
|
||||
@@ -58,7 +58,7 @@ export class CodeActionWorkbenchContribution extends Disposable implements IWork
|
||||
|
||||
constructor(
|
||||
codeActionsExtensionPoint: IExtensionPoint<CodeActionsExtensionPoint[]>,
|
||||
keybindingService: IKeybindingService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { CodeAction, CodeActionContext, CodeActionList, CodeActionProvider, CodeActionProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { DocumentationExtensionPoint } from './documentationExtensionPoint';
|
||||
|
||||
|
||||
export class CodeActionDocumentationContribution extends Disposable implements IWorkbenchContribution, CodeActionProvider {
|
||||
|
||||
private contributions: {
|
||||
title: string;
|
||||
when: ContextKeyExpr;
|
||||
command: string;
|
||||
}[] = [];
|
||||
|
||||
constructor(
|
||||
extensionPoint: IExtensionPoint<DocumentationExtensionPoint>,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super();
|
||||
|
||||
CodeActionProviderRegistry.register('*', this);
|
||||
|
||||
extensionPoint.setHandler(points => {
|
||||
this.contributions = [];
|
||||
for (const documentation of points) {
|
||||
if (!documentation.value.refactoring) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const contribution of documentation.value.refactoring) {
|
||||
const precondition = ContextKeyExpr.deserialize(contribution.when);
|
||||
if (!precondition) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.contributions.push({
|
||||
title: contribution.title,
|
||||
when: precondition,
|
||||
command: contribution.command
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async provideCodeActions(_model: ITextModel, _range: Range | Selection, context: CodeActionContext, _token: CancellationToken): Promise<CodeActionList> {
|
||||
if (!context.only || !CodeActionKind.Refactor.contains(new CodeActionKind(context.only))) {
|
||||
return {
|
||||
actions: [],
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
|
||||
const actions: CodeAction[] = [];
|
||||
|
||||
for (const contribution of this.contributions) {
|
||||
if (!this.contextKeyService.contextMatchesRules(contribution.when)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
actions.push({
|
||||
title: contribution.title,
|
||||
kind: CodeActionKind.RefactorDocumentation.value,
|
||||
command: {
|
||||
id: contribution.command,
|
||||
title: contribution.title
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
actions,
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
|
||||
public readonly providedCodeActionKinds = [CodeActionKind.RefactorDocumentation.value] as const;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService';
|
||||
|
||||
export enum DocumentationExtensionPointFields {
|
||||
when = 'when',
|
||||
title = 'title',
|
||||
command = 'command',
|
||||
}
|
||||
|
||||
export interface RefactoringDocumentationExtensionPoint {
|
||||
readonly [DocumentationExtensionPointFields.title]: string;
|
||||
readonly [DocumentationExtensionPointFields.when]: string;
|
||||
readonly [DocumentationExtensionPointFields.command]: string;
|
||||
}
|
||||
|
||||
export interface DocumentationExtensionPoint {
|
||||
readonly refactoring?: readonly RefactoringDocumentationExtensionPoint[];
|
||||
}
|
||||
|
||||
const documentationExtensionPointSchema = Object.freeze<IConfigurationPropertySchema>({
|
||||
type: 'object',
|
||||
description: nls.localize('contributes.documentation', "Contributed documentation."),
|
||||
properties: {
|
||||
'refactoring': {
|
||||
type: 'array',
|
||||
description: nls.localize('contributes.documentation.refactorings', "Contributed documentation for refactorings."),
|
||||
items: {
|
||||
type: 'object',
|
||||
description: nls.localize('contributes.documentation.refactoring', "Contributed documentation for refactoring."),
|
||||
required: [
|
||||
DocumentationExtensionPointFields.title,
|
||||
DocumentationExtensionPointFields.when,
|
||||
DocumentationExtensionPointFields.command
|
||||
],
|
||||
properties: {
|
||||
[DocumentationExtensionPointFields.title]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.documentation.refactoring.title', "Label for the documentation used in the UI."),
|
||||
},
|
||||
[DocumentationExtensionPointFields.when]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.documentation.refactoring.when', "When clause."),
|
||||
},
|
||||
[DocumentationExtensionPointFields.command]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.documentation.refactoring.command', "Command executed."),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const documentationExtensionPointDescriptor = {
|
||||
extensionPoint: 'documentation',
|
||||
deps: [languagesExtPoint],
|
||||
jsonSchema: documentationExtensionPointSchema
|
||||
};
|
||||
@@ -21,6 +21,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE
|
||||
import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
|
||||
export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput {
|
||||
|
||||
@@ -41,6 +42,7 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput {
|
||||
@ICustomEditorService private readonly customEditorService: ICustomEditorService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService
|
||||
) {
|
||||
super(id, viewType, '', webview, webviewWorkbenchService, lifecycleService);
|
||||
this._editorResource = resource;
|
||||
@@ -109,6 +111,18 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput {
|
||||
return this._model ? this._model.isDirty() : false;
|
||||
}
|
||||
|
||||
public isSaving(): boolean {
|
||||
if (!this.isDirty()) {
|
||||
return false; // the editor needs to be dirty for being saved
|
||||
}
|
||||
|
||||
if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return true; // a short auto save is configured, treat this as being saved
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
return this._model ? this._model.save(options) : Promise.resolve(false);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface IVariableTemplateData {
|
||||
}
|
||||
|
||||
export function renderViewTree(container: HTMLElement): HTMLElement {
|
||||
const treeContainer = document.createElement('div');
|
||||
const treeContainer = $('.');
|
||||
dom.addClass(treeContainer, 'debug-view-content');
|
||||
container.appendChild(treeContainer);
|
||||
return treeContainer;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
@@ -51,7 +51,7 @@ const breakpointHelperDecoration: IModelDecorationOptions = {
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
|
||||
};
|
||||
|
||||
function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, debugService: IDebugService, debugSettings: IDebugConfiguration): { range: Range; options: IModelDecorationOptions; }[] {
|
||||
export function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): { range: Range; options: IModelDecorationOptions; }[] {
|
||||
const result: { range: Range; options: IModelDecorationOptions; }[] = [];
|
||||
breakpoints.forEach((breakpoint) => {
|
||||
if (breakpoint.lineNumber > model.getLineCount()) {
|
||||
@@ -64,7 +64,7 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr
|
||||
);
|
||||
|
||||
result.push({
|
||||
options: getBreakpointDecorationOptions(model, breakpoint, debugService, debugSettings),
|
||||
options: getBreakpointDecorationOptions(model, breakpoint, state, breakpointsActivated, showBreakpointsInOverviewRuler),
|
||||
range
|
||||
});
|
||||
});
|
||||
@@ -72,8 +72,8 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr
|
||||
return result;
|
||||
}
|
||||
|
||||
function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService, debugSettings: IDebugConfiguration): IModelDecorationOptions {
|
||||
const { className, message } = getBreakpointMessageAndClassName(debugService, breakpoint);
|
||||
function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): IModelDecorationOptions {
|
||||
const { className, message } = getBreakpointMessageAndClassName(state, breakpointsActivated, breakpoint);
|
||||
let glyphMarginHoverMessage: MarkdownString | undefined;
|
||||
|
||||
if (message) {
|
||||
@@ -85,14 +85,12 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi
|
||||
}
|
||||
}
|
||||
|
||||
let overviewRulerDecoration: IModelDecorationOverviewRulerOptions | null;
|
||||
if (debugSettings.showBreakpointsInOverviewRuler) {
|
||||
let overviewRulerDecoration: IModelDecorationOverviewRulerOptions | null = null;
|
||||
if (showBreakpointsInOverviewRuler) {
|
||||
overviewRulerDecoration = {
|
||||
color: 'rgb(124, 40, 49)',
|
||||
position: OverviewRulerLane.Left
|
||||
};
|
||||
} else {
|
||||
overviewRulerDecoration = null;
|
||||
}
|
||||
|
||||
const renderInline = breakpoint.column && (breakpoint.column > model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber));
|
||||
@@ -105,11 +103,10 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi
|
||||
};
|
||||
}
|
||||
|
||||
async function createCandidateDecorations(model: ITextModel, breakpointDecorations: IBreakpointDecoration[], debugService: IDebugService): Promise<{ range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[]> {
|
||||
async function createCandidateDecorations(model: ITextModel, breakpointDecorations: IBreakpointDecoration[], session: IDebugSession): Promise<{ range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[]> {
|
||||
const lineNumbers = distinct(breakpointDecorations.map(bpd => bpd.range.startLineNumber));
|
||||
const result: { range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[] = [];
|
||||
const session = debugService.getViewModel().focusedSession;
|
||||
if (session && session.capabilities.supportsBreakpointLocationsRequest) {
|
||||
if (session.capabilities.supportsBreakpointLocationsRequest) {
|
||||
await Promise.all(lineNumbers.map(async lineNumber => {
|
||||
try {
|
||||
const positions = await session.breakpointsLocations(model.uri, lineNumber);
|
||||
@@ -403,7 +400,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
|
||||
const model = activeCodeEditor.getModel();
|
||||
const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri });
|
||||
const debugSettings = this.configurationService.getValue<IDebugConfiguration>('debug');
|
||||
const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService, debugSettings);
|
||||
const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), debugSettings.showBreakpointsInOverviewRuler);
|
||||
|
||||
try {
|
||||
this.ignoreDecorationsChangedEvent = true;
|
||||
@@ -436,7 +433,8 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
|
||||
}
|
||||
|
||||
// Set breakpoint candidate decorations
|
||||
const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService) : [];
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates && session ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, session) : [];
|
||||
const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations);
|
||||
this.candidateDecorations.forEach(candidate => {
|
||||
candidate.inlineWidget.dispose();
|
||||
@@ -446,7 +444,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
|
||||
// Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there
|
||||
// In practice this happens for the first breakpoint that was set on a line
|
||||
// We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information
|
||||
const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService, candidate.breakpoint).className : 'codicon-debug-breakpoint-disabled';
|
||||
const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint).className : 'codicon-debug-breakpoint-disabled';
|
||||
const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn);
|
||||
const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
@@ -46,6 +46,34 @@ export interface IPrivateBreakpointWidgetService {
|
||||
}
|
||||
const DECORATION_KEY = 'breakpointwidgetdecoration';
|
||||
|
||||
function isCurlyBracketOpen(input: IActiveCodeEditor): boolean {
|
||||
const model = input.getModel();
|
||||
const prevBracket = model.findPrevBracket(input.getPosition());
|
||||
if (prevBracket && prevBracket.isOpen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function createDecorations(theme: ITheme, placeHolder: string): IDecorationOptions[] {
|
||||
const transparentForeground = transparent(editorForeground, 0.4)(theme);
|
||||
return [{
|
||||
range: {
|
||||
startLineNumber: 0,
|
||||
endLineNumber: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 1
|
||||
},
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: placeHolder,
|
||||
color: transparentForeground ? transparentForeground.toString() : undefined
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWidgetService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -197,7 +225,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
|
||||
this.toDispose.push(model);
|
||||
const setDecorations = () => {
|
||||
const value = this.input.getModel().getValue();
|
||||
const decorations = !!value ? [] : this.createDecorations();
|
||||
const decorations = !!value ? [] : createDecorations(this.themeService.getTheme(), this.placeholder);
|
||||
this.input.setDecorations(DECORATION_KEY, decorations);
|
||||
};
|
||||
this.input.getModel().onDidChangeContent(() => setDecorations());
|
||||
@@ -207,7 +235,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
|
||||
provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise<CompletionList> => {
|
||||
let suggestionsPromise: Promise<CompletionList>;
|
||||
const underlyingModel = this.editor.getModel();
|
||||
if (underlyingModel && (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen())) {
|
||||
if (underlyingModel && (this.context === Context.CONDITION || (this.context === Context.LOG_MESSAGE && isCurlyBracketOpen(this.input)))) {
|
||||
suggestionsPromise = provideSuggestionItems(underlyingModel, new Position(this.lineNumber, 1), new CompletionOptions(undefined, new Set<CompletionItemKind>().add(CompletionItemKind.Snippet)), _context, token).then(suggestions => {
|
||||
|
||||
let overwriteBefore = 0;
|
||||
@@ -260,42 +288,6 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
|
||||
}
|
||||
}
|
||||
|
||||
private createDecorations(): IDecorationOptions[] {
|
||||
const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme());
|
||||
return [{
|
||||
range: {
|
||||
startLineNumber: 0,
|
||||
endLineNumber: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 1
|
||||
},
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: this.placeholder,
|
||||
color: transparentForeground ? transparentForeground.toString() : undefined
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
private isCurlyBracketOpen(): boolean {
|
||||
const value = this.input.getModel().getValue();
|
||||
const position = this.input.getPosition();
|
||||
if (position) {
|
||||
for (let i = position.column - 2; i >= 0; i--) {
|
||||
if (value[i] === '{') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value[i] === '}') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
close(success: boolean): void {
|
||||
if (success) {
|
||||
// if there is already a breakpoint on this location - remove it.
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugModel, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -45,9 +45,14 @@ function createCheckbox(): HTMLInputElement {
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
const MAX_VISIBLE_BREAKPOINTS = 9;
|
||||
export function getExpandedBodySize(model: IDebugModel): number {
|
||||
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length;
|
||||
return Math.min(MAX_VISIBLE_BREAKPOINTS, length) * 22;
|
||||
}
|
||||
|
||||
export class BreakpointsView extends ViewPane {
|
||||
|
||||
private static readonly MAX_VISIBLE_FILES = 9;
|
||||
private list!: WorkbenchList<IEnablement>;
|
||||
private needsRefresh = false;
|
||||
|
||||
@@ -65,7 +70,7 @@ export class BreakpointsView extends ViewPane {
|
||||
) {
|
||||
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
|
||||
|
||||
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
|
||||
this.minimumBodySize = this.maximumBodySize = getExpandedBodySize(this.debugService.getModel());
|
||||
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
|
||||
}
|
||||
|
||||
@@ -215,7 +220,7 @@ export class BreakpointsView extends ViewPane {
|
||||
|
||||
private onBreakpointsChange(): void {
|
||||
if (this.isBodyVisible()) {
|
||||
this.minimumBodySize = this.getExpandedBodySize();
|
||||
this.minimumBodySize = getExpandedBodySize(this.debugService.getModel());
|
||||
if (this.maximumBodySize < Number.POSITIVE_INFINITY) {
|
||||
this.maximumBodySize = this.minimumBodySize;
|
||||
}
|
||||
@@ -234,12 +239,6 @@ export class BreakpointsView extends ViewPane {
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
private getExpandedBodySize(): number {
|
||||
const model = this.debugService.getModel();
|
||||
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length;
|
||||
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
|
||||
}
|
||||
}
|
||||
|
||||
class BreakpointsDelegate implements IListVirtualDelegate<IEnablement> {
|
||||
@@ -351,7 +350,7 @@ class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTempl
|
||||
data.filePath.textContent = this.labelService.getUriLabel(resources.dirname(breakpoint.uri), { relative: true });
|
||||
data.checkbox.checked = breakpoint.enabled;
|
||||
|
||||
const { message, className } = getBreakpointMessageAndClassName(this.debugService, breakpoint);
|
||||
const { message, className } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint);
|
||||
data.icon.className = `codicon ${className}`;
|
||||
data.breakpoint.title = breakpoint.message || message || '';
|
||||
|
||||
@@ -443,10 +442,10 @@ class FunctionBreakpointsRenderer implements IListRenderer<FunctionBreakpoint, I
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(functionBreakpoint: FunctionBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void {
|
||||
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
|
||||
data.context = functionBreakpoint;
|
||||
data.name.textContent = functionBreakpoint.name;
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService, functionBreakpoint);
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint);
|
||||
data.icon.className = `codicon ${className}`;
|
||||
data.icon.title = message ? message : '';
|
||||
data.checkbox.checked = functionBreakpoint.enabled;
|
||||
@@ -498,10 +497,10 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IBaseBrea
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(dataBreakpoint: DataBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void {
|
||||
renderElement(dataBreakpoint: DataBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
|
||||
data.context = dataBreakpoint;
|
||||
data.name.textContent = dataBreakpoint.description;
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService, dataBreakpoint);
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint);
|
||||
data.icon.className = `codicon ${className}`;
|
||||
data.icon.title = message ? message : '';
|
||||
data.checkbox.checked = dataBreakpoint.enabled;
|
||||
@@ -588,10 +587,10 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
|
||||
return template;
|
||||
}
|
||||
|
||||
renderElement(functionBreakpoint: FunctionBreakpoint, index: number, data: IInputTemplateData): void {
|
||||
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IInputTemplateData): void {
|
||||
data.breakpoint = functionBreakpoint;
|
||||
data.reactedOnEvent = false;
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService, functionBreakpoint);
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint);
|
||||
|
||||
data.icon.className = `codicon ${className}`;
|
||||
data.icon.title = message ? message : '';
|
||||
@@ -638,11 +637,10 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea
|
||||
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
}
|
||||
|
||||
export function getBreakpointMessageAndClassName(debugService: IDebugService, breakpoint: IBreakpoint | FunctionBreakpoint | DataBreakpoint): { message?: string, className: string } {
|
||||
const state = debugService.state;
|
||||
export function getBreakpointMessageAndClassName(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint): { message?: string, className: string } {
|
||||
const debugActive = state === State.Running || state === State.Stopped;
|
||||
|
||||
if (!breakpoint.enabled || !debugService.getModel().areBreakpointsActivated()) {
|
||||
if (!breakpoint.enabled || !breakpointsActivated) {
|
||||
return {
|
||||
className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-disabled' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-disabled' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-disabled' : 'codicon-debug-breakpoint-disabled',
|
||||
message: breakpoint.logMessage ? nls.localize('disabledLogpoint', "Disabled Logpoint") : nls.localize('disabledBreakpoint', "Disabled Breakpoint"),
|
||||
@@ -650,12 +648,12 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br
|
||||
}
|
||||
|
||||
const appendMessage = (text: string): string => {
|
||||
return !(breakpoint instanceof FunctionBreakpoint) && !(breakpoint instanceof DataBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text;
|
||||
return ('message' in breakpoint && breakpoint.message) ? text.concat(', ' + breakpoint.message) : text;
|
||||
};
|
||||
if (debugActive && !breakpoint.verified) {
|
||||
return {
|
||||
className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-unverified' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-unverified' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-unverified' : 'codicon-debug-breakpoint-unverified',
|
||||
message: breakpoint.message || (breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified Logpoint") : nls.localize('unverifiedBreakopint', "Unverified Breakpoint")),
|
||||
message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : (breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified Logpoint") : nls.localize('unverifiedBreakopint', "Unverified Breakpoint")),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -715,6 +713,6 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br
|
||||
|
||||
return {
|
||||
className: 'codicon-debug-breakpoint',
|
||||
message: breakpoint.message || nls.localize('breakpoint', "Breakpoint")
|
||||
message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : nls.localize('breakpoint', "Breakpoint")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
|
||||
import { IDebugService, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
@@ -17,6 +17,71 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
|
||||
const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
|
||||
|
||||
// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.
|
||||
const TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {
|
||||
glyphMarginClassName: 'codicon-debug-stackframe',
|
||||
stickiness
|
||||
};
|
||||
const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
|
||||
glyphMarginClassName: 'codicon-debug-stackframe-focused',
|
||||
stickiness
|
||||
};
|
||||
const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
||||
isWholeLine: true,
|
||||
className: 'debug-top-stack-frame-line',
|
||||
stickiness
|
||||
};
|
||||
const TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = {
|
||||
beforeContentClassName: 'debug-top-stack-frame-column'
|
||||
};
|
||||
const FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
||||
isWholeLine: true,
|
||||
className: 'debug-focused-stack-frame-line',
|
||||
stickiness
|
||||
};
|
||||
|
||||
export function createDecorationsForStackFrame(stackFrame: IStackFrame, topStackFrameRange: IRange | undefined): IModelDeltaDecoration[] {
|
||||
// only show decorations for the currently focused thread.
|
||||
const result: IModelDeltaDecoration[] = [];
|
||||
const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1);
|
||||
|
||||
// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame,
|
||||
// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
|
||||
const callStack = stackFrame.thread.getCallStack();
|
||||
if (callStack && callStack.length && stackFrame === callStack[0]) {
|
||||
result.push({
|
||||
options: TOP_STACK_FRAME_MARGIN,
|
||||
range
|
||||
});
|
||||
|
||||
result.push({
|
||||
options: TOP_STACK_FRAME_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
|
||||
if (topStackFrameRange && topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && topStackFrameRange.startColumn !== stackFrame.range.startColumn) {
|
||||
result.push({
|
||||
options: TOP_STACK_FRAME_INLINE_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
}
|
||||
topStackFrameRange = columnUntilEOLRange;
|
||||
} else {
|
||||
result.push({
|
||||
options: FOCUSED_STACK_FRAME_MARGIN,
|
||||
range
|
||||
});
|
||||
|
||||
result.push({
|
||||
options: FOCUSED_STACK_FRAME_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class CallStackEditorContribution implements IEditorContribution {
|
||||
private toDispose: IDisposable[] = [];
|
||||
private decorationIds: string[] = [];
|
||||
@@ -52,7 +117,7 @@ class CallStackEditorContribution implements IEditorContribution {
|
||||
}
|
||||
|
||||
if (candidateStackFrame && candidateStackFrame.source.uri.toString() === this.editor.getModel()?.uri.toString()) {
|
||||
decorations.push(...this.createDecorationsForStackFrame(candidateStackFrame));
|
||||
decorations.push(...createDecorationsForStackFrame(candidateStackFrame, this.topStackFrameRange));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -61,78 +126,6 @@ class CallStackEditorContribution implements IEditorContribution {
|
||||
return decorations;
|
||||
}
|
||||
|
||||
private createDecorationsForStackFrame(stackFrame: IStackFrame): IModelDeltaDecoration[] {
|
||||
// only show decorations for the currently focused thread.
|
||||
const result: IModelDeltaDecoration[] = [];
|
||||
const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1);
|
||||
|
||||
// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame,
|
||||
// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
|
||||
const callStack = stackFrame.thread.getCallStack();
|
||||
if (callStack && callStack.length && stackFrame === callStack[0]) {
|
||||
result.push({
|
||||
options: CallStackEditorContribution.TOP_STACK_FRAME_MARGIN,
|
||||
range
|
||||
});
|
||||
|
||||
result.push({
|
||||
options: CallStackEditorContribution.TOP_STACK_FRAME_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
|
||||
if (this.topStackFrameRange && this.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && this.topStackFrameRange.startColumn !== stackFrame.range.startColumn) {
|
||||
result.push({
|
||||
options: CallStackEditorContribution.TOP_STACK_FRAME_INLINE_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
}
|
||||
this.topStackFrameRange = columnUntilEOLRange;
|
||||
} else {
|
||||
result.push({
|
||||
options: CallStackEditorContribution.FOCUSED_STACK_FRAME_MARGIN,
|
||||
range
|
||||
});
|
||||
|
||||
result.push({
|
||||
options: CallStackEditorContribution.FOCUSED_STACK_FRAME_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// editor decorations
|
||||
|
||||
static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
|
||||
// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.
|
||||
private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {
|
||||
glyphMarginClassName: 'codicon-debug-stackframe',
|
||||
stickiness
|
||||
};
|
||||
|
||||
private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
|
||||
glyphMarginClassName: 'codicon-debug-stackframe-focused',
|
||||
stickiness
|
||||
};
|
||||
|
||||
private static TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
||||
isWholeLine: true,
|
||||
className: 'debug-top-stack-frame-line',
|
||||
stickiness
|
||||
};
|
||||
|
||||
private static TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = {
|
||||
beforeContentClassName: 'debug-top-stack-frame-column'
|
||||
};
|
||||
|
||||
private static FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
||||
isWholeLine: true,
|
||||
className: 'debug-focused-stack-frame-line',
|
||||
stickiness
|
||||
};
|
||||
|
||||
dispose(): void {
|
||||
this.editor.deltaDecorations(this.decorationIds, []);
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
@@ -40,7 +40,7 @@ const $ = dom.$;
|
||||
|
||||
type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndSessionIds | IStackFrame[];
|
||||
|
||||
function getContext(element: CallStackItem | null): any {
|
||||
export function getContext(element: CallStackItem | null): any {
|
||||
return element instanceof StackFrame ? {
|
||||
sessionId: element.thread.session.getId(),
|
||||
threadId: element.thread.getId(),
|
||||
@@ -53,6 +53,25 @@ function getContext(element: CallStackItem | null): any {
|
||||
} : undefined;
|
||||
}
|
||||
|
||||
// Extensions depend on this context, should not be changed even though it is not fully deterministic
|
||||
export function getContextForContributedActions(element: CallStackItem | null): string | number {
|
||||
if (element instanceof StackFrame) {
|
||||
if (element.source.inMemory) {
|
||||
return element.source.raw.path || element.source.reference || '';
|
||||
}
|
||||
|
||||
return element.source.uri.toString();
|
||||
}
|
||||
if (element instanceof Thread) {
|
||||
return element.threadId;
|
||||
}
|
||||
if (isDebugSession(element)) {
|
||||
return element.getId();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export class CallStackView extends ViewPane {
|
||||
private pauseMessage!: HTMLSpanElement;
|
||||
private pauseMessageLabel!: HTMLSpanElement;
|
||||
@@ -336,7 +355,7 @@ export class CallStackView extends ViewPane {
|
||||
}
|
||||
|
||||
const actions: IAction[] = [];
|
||||
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService);
|
||||
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
@@ -345,24 +364,6 @@ export class CallStackView extends ViewPane {
|
||||
onHide: () => dispose(actionsDisposable)
|
||||
});
|
||||
}
|
||||
|
||||
private getContextForContributedActions(element: CallStackItem | null): string | number {
|
||||
if (element instanceof StackFrame) {
|
||||
if (element.source.inMemory) {
|
||||
return element.source.raw.path || element.source.reference || '';
|
||||
}
|
||||
|
||||
return element.source.uri.toString();
|
||||
}
|
||||
if (element instanceof Thread) {
|
||||
return element.threadId;
|
||||
}
|
||||
if (isDebugSession(element)) {
|
||||
return element.getId();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
interface IThreadTemplateData {
|
||||
|
||||
@@ -441,7 +441,7 @@ export interface IBreakpointsChangeEvent {
|
||||
added?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
|
||||
removed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
|
||||
changed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
|
||||
sessionOnly?: boolean;
|
||||
sessionOnly: boolean;
|
||||
}
|
||||
|
||||
// Debug configuration interfaces
|
||||
|
||||
@@ -1010,7 +1010,7 @@ export class DebugModel implements IDebugModel {
|
||||
this.sortAndDeDup();
|
||||
|
||||
if (fireEvent) {
|
||||
this._onDidChangeBreakpoints.fire({ added: newBreakpoints });
|
||||
this._onDidChangeBreakpoints.fire({ added: newBreakpoints, sessionOnly: false });
|
||||
}
|
||||
|
||||
return newBreakpoints;
|
||||
@@ -1018,7 +1018,7 @@ export class DebugModel implements IDebugModel {
|
||||
|
||||
removeBreakpoints(toRemove: IBreakpoint[]): void {
|
||||
this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
|
||||
this._onDidChangeBreakpoints.fire({ removed: toRemove });
|
||||
this._onDidChangeBreakpoints.fire({ removed: toRemove, sessionOnly: false });
|
||||
}
|
||||
|
||||
updateBreakpoints(data: Map<string, IBreakpointUpdateData>): void {
|
||||
@@ -1031,7 +1031,7 @@ export class DebugModel implements IDebugModel {
|
||||
}
|
||||
});
|
||||
this.sortAndDeDup();
|
||||
this._onDidChangeBreakpoints.fire({ changed: updated });
|
||||
this._onDidChangeBreakpoints.fire({ changed: updated, sessionOnly: false });
|
||||
}
|
||||
|
||||
setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map<string, DebugProtocol.Breakpoint> | undefined): void {
|
||||
@@ -1100,7 +1100,7 @@ export class DebugModel implements IDebugModel {
|
||||
this.breakpointsActivated = true;
|
||||
}
|
||||
|
||||
this._onDidChangeBreakpoints.fire({ changed: changed });
|
||||
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1129,13 +1129,13 @@ export class DebugModel implements IDebugModel {
|
||||
this.breakpointsActivated = true;
|
||||
}
|
||||
|
||||
this._onDidChangeBreakpoints.fire({ changed: changed });
|
||||
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
|
||||
}
|
||||
|
||||
addFunctionBreakpoint(functionName: string, id?: string): IFunctionBreakpoint {
|
||||
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, undefined, undefined, undefined, id);
|
||||
this.functionBreakpoints.push(newFunctionBreakpoint);
|
||||
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint] });
|
||||
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false });
|
||||
|
||||
return newFunctionBreakpoint;
|
||||
}
|
||||
@@ -1144,7 +1144,7 @@ export class DebugModel implements IDebugModel {
|
||||
const functionBreakpoint = this.functionBreakpoints.filter(fbp => fbp.getId() === id).pop();
|
||||
if (functionBreakpoint) {
|
||||
functionBreakpoint.name = name;
|
||||
this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint] });
|
||||
this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1157,13 +1157,13 @@ export class DebugModel implements IDebugModel {
|
||||
removed = this.functionBreakpoints;
|
||||
this.functionBreakpoints = [];
|
||||
}
|
||||
this._onDidChangeBreakpoints.fire({ removed });
|
||||
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
|
||||
}
|
||||
|
||||
addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): void {
|
||||
const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes);
|
||||
this.dataBreakopints.push(newDataBreakpoint);
|
||||
this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint] });
|
||||
this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false });
|
||||
}
|
||||
|
||||
removeDataBreakpoints(id?: string): void {
|
||||
@@ -1175,7 +1175,7 @@ export class DebugModel implements IDebugModel {
|
||||
removed = this.dataBreakopints;
|
||||
this.dataBreakopints = [];
|
||||
}
|
||||
this._onDidChangeBreakpoints.fire({ removed });
|
||||
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
|
||||
}
|
||||
|
||||
getWatchExpressions(): Expression[] {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import * as env from 'vs/base/common/platform';
|
||||
import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/node/externalTerminalService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal';
|
||||
@@ -76,35 +75,22 @@ export function hasChildProcesses(processId: number | undefined): Promise<boolea
|
||||
|
||||
const enum ShellType { cmd, powershell, bash }
|
||||
|
||||
export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, shell: string, configProvider: ExtHostConfigProvider): string {
|
||||
export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, shell: string): string {
|
||||
|
||||
let shellType = env.isWindows ? ShellType.cmd : ShellType.bash; // pick a good default
|
||||
|
||||
if (shell) {
|
||||
|
||||
const config = configProvider.getConfiguration('terminal');
|
||||
|
||||
// get the shell configuration for the current platform
|
||||
const shell_config = config.integrated.shell;
|
||||
if (env.isWindows) {
|
||||
shell = shell_config.windows || getSystemShell(env.Platform.Windows);
|
||||
} else if (env.isLinux) {
|
||||
shell = shell_config.linux || getSystemShell(env.Platform.Linux);
|
||||
} else if (env.isMacintosh) {
|
||||
shell = shell_config.osx || getSystemShell(env.Platform.Mac);
|
||||
} else {
|
||||
throw new Error('Unknown platform');
|
||||
}
|
||||
}
|
||||
shell = shell.trim().toLowerCase();
|
||||
|
||||
// try to determine the shell type
|
||||
shell = shell.trim().toLowerCase();
|
||||
let shellType;
|
||||
if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0) {
|
||||
shellType = ShellType.powershell;
|
||||
} else if (shell.indexOf('cmd.exe') >= 0) {
|
||||
shellType = ShellType.cmd;
|
||||
} else if (shell.indexOf('bash') >= 0) {
|
||||
shellType = ShellType.bash;
|
||||
} else if (env.isWindows) {
|
||||
shellType = ShellType.cmd; // pick a good default for Windows
|
||||
} else {
|
||||
shellType = ShellType.bash; // pick a good default for anything else
|
||||
}
|
||||
|
||||
let quote: (s: string) => string;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { replaceWhitespace, renderExpressionValue, renderVariable } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { replaceWhitespace, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Expression, Variable, Scope, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
|
||||
@@ -32,6 +32,16 @@ suite('Debug - Base Debug View', () => {
|
||||
assert.equal(replaceWhitespace('hey \r\t\n\t\t\n there'), 'hey \\r\\t\\n\\t\\t\\n there');
|
||||
});
|
||||
|
||||
test('render view tree', () => {
|
||||
const container = $('.container');
|
||||
const treeContainer = renderViewTree(container);
|
||||
|
||||
assert.equal(treeContainer.className, 'debug-view-content');
|
||||
assert.equal(container.childElementCount, 1);
|
||||
assert.equal(container.firstChild, treeContainer);
|
||||
assert.equal(treeContainer instanceof HTMLDivElement, true);
|
||||
});
|
||||
|
||||
test('render expression value', () => {
|
||||
let container = $('.container');
|
||||
renderExpressionValue('render \n me', container, { showHover: true, preserveWhitespace: true });
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { DebugModel, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
|
||||
import { NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { getExpandedBodySize, getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IBreakpointData, IDebugSessionOptions, IBreakpointUpdateData, State } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { LanguageIdentifier, LanguageId } from 'vs/editor/common/modes';
|
||||
import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution';
|
||||
import { OverviewRulerLane } from 'vs/editor/common/model';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
|
||||
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
|
||||
}
|
||||
|
||||
function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]): void {
|
||||
let eventCount = 0;
|
||||
const toDispose = model.onDidChangeBreakpoints(e => {
|
||||
assert.equal(e?.sessionOnly, false);
|
||||
assert.equal(e?.changed, undefined);
|
||||
assert.equal(e?.removed, undefined);
|
||||
const added = e?.added;
|
||||
assert.notEqual(added, undefined);
|
||||
assert.equal(added!.length, data.length);
|
||||
eventCount++;
|
||||
dispose(toDispose);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
assert.equal(e!.added![i] instanceof Breakpoint, true);
|
||||
assert.equal((e!.added![i] as Breakpoint).lineNumber, data[i].lineNumber);
|
||||
}
|
||||
});
|
||||
model.addBreakpoints(uri, data);
|
||||
assert.equal(eventCount, 1);
|
||||
}
|
||||
|
||||
suite('Debug - Breakpoints', () => {
|
||||
let model: DebugModel;
|
||||
|
||||
setup(() => {
|
||||
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
|
||||
});
|
||||
|
||||
// Breakpoints
|
||||
|
||||
test('simple', () => {
|
||||
const modelUri = uri.file('/myfolder/myfile.js');
|
||||
|
||||
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
|
||||
assert.equal(model.areBreakpointsActivated(), true);
|
||||
assert.equal(model.getBreakpoints().length, 2);
|
||||
|
||||
let eventCount = 0;
|
||||
const toDispose = model.onDidChangeBreakpoints(e => {
|
||||
eventCount++;
|
||||
assert.equal(e?.added, undefined);
|
||||
assert.equal(e?.sessionOnly, false);
|
||||
assert.equal(e?.removed?.length, 2);
|
||||
assert.equal(e?.changed, undefined);
|
||||
|
||||
dispose(toDispose);
|
||||
});
|
||||
|
||||
model.removeBreakpoints(model.getBreakpoints());
|
||||
assert.equal(eventCount, 1);
|
||||
assert.equal(model.getBreakpoints().length, 0);
|
||||
});
|
||||
|
||||
test('toggling', () => {
|
||||
const modelUri = uri.file('/myfolder/myfile.js');
|
||||
|
||||
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
|
||||
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 12, enabled: true, condition: 'fake condition' }]);
|
||||
assert.equal(model.getBreakpoints().length, 3);
|
||||
const bp = model.getBreakpoints().pop();
|
||||
if (bp) {
|
||||
model.removeBreakpoints([bp]);
|
||||
}
|
||||
assert.equal(model.getBreakpoints().length, 2);
|
||||
|
||||
model.setBreakpointsActivated(false);
|
||||
assert.equal(model.areBreakpointsActivated(), false);
|
||||
model.setBreakpointsActivated(true);
|
||||
assert.equal(model.areBreakpointsActivated(), true);
|
||||
});
|
||||
|
||||
test('two files', () => {
|
||||
const modelUri1 = uri.file('/myfolder/my file first.js');
|
||||
const modelUri2 = uri.file('/secondfolder/second/second file.js');
|
||||
addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
|
||||
assert.equal(getExpandedBodySize(model), 44);
|
||||
|
||||
addBreakpointsAndCheckEvents(model, modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]);
|
||||
assert.equal(getExpandedBodySize(model), 110);
|
||||
|
||||
assert.equal(model.getBreakpoints().length, 5);
|
||||
assert.equal(model.getBreakpoints({ uri: modelUri1 }).length, 2);
|
||||
assert.equal(model.getBreakpoints({ uri: modelUri2 }).length, 3);
|
||||
assert.equal(model.getBreakpoints({ lineNumber: 5 }).length, 1);
|
||||
assert.equal(model.getBreakpoints({ column: 5 }).length, 0);
|
||||
|
||||
const bp = model.getBreakpoints()[0];
|
||||
const update = new Map<string, IBreakpointUpdateData>();
|
||||
update.set(bp.getId(), { lineNumber: 100 });
|
||||
let eventFired = false;
|
||||
const toDispose = model.onDidChangeBreakpoints(e => {
|
||||
eventFired = true;
|
||||
assert.equal(e?.added, undefined);
|
||||
assert.equal(e?.removed, undefined);
|
||||
assert.equal(e?.changed?.length, 1);
|
||||
dispose(toDispose);
|
||||
});
|
||||
model.updateBreakpoints(update);
|
||||
assert.equal(eventFired, true);
|
||||
assert.equal(bp.lineNumber, 100);
|
||||
|
||||
assert.equal(model.getBreakpoints({ enabledOnly: true }).length, 3);
|
||||
model.enableOrDisableAllBreakpoints(false);
|
||||
model.getBreakpoints().forEach(bp => {
|
||||
assert.equal(bp.enabled, false);
|
||||
});
|
||||
assert.equal(model.getBreakpoints({ enabledOnly: true }).length, 0);
|
||||
|
||||
model.setEnablement(bp, true);
|
||||
assert.equal(bp.enabled, true);
|
||||
|
||||
model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 }));
|
||||
assert.equal(getExpandedBodySize(model), 66);
|
||||
|
||||
assert.equal(model.getBreakpoints().length, 3);
|
||||
});
|
||||
|
||||
test('conditions', () => {
|
||||
const modelUri1 = uri.file('/myfolder/my file first.js');
|
||||
addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, condition: 'i < 5', hitCondition: '17' }, { lineNumber: 10, condition: 'j < 3' }]);
|
||||
const breakpoints = model.getBreakpoints();
|
||||
|
||||
assert.equal(breakpoints[0].condition, 'i < 5');
|
||||
assert.equal(breakpoints[0].hitCondition, '17');
|
||||
assert.equal(breakpoints[1].condition, 'j < 3');
|
||||
assert.equal(!!breakpoints[1].hitCondition, false);
|
||||
|
||||
assert.equal(model.getBreakpoints().length, 2);
|
||||
model.removeBreakpoints(model.getBreakpoints());
|
||||
assert.equal(model.getBreakpoints().length, 0);
|
||||
});
|
||||
|
||||
test('function breakpoints', () => {
|
||||
model.addFunctionBreakpoint('foo', '1');
|
||||
model.addFunctionBreakpoint('bar', '2');
|
||||
model.renameFunctionBreakpoint('1', 'fooUpdated');
|
||||
model.renameFunctionBreakpoint('2', 'barUpdated');
|
||||
|
||||
const functionBps = model.getFunctionBreakpoints();
|
||||
assert.equal(functionBps[0].name, 'fooUpdated');
|
||||
assert.equal(functionBps[1].name, 'barUpdated');
|
||||
|
||||
model.removeFunctionBreakpoints();
|
||||
assert.equal(model.getFunctionBreakpoints().length, 0);
|
||||
});
|
||||
|
||||
test('multiple sessions', () => {
|
||||
const modelUri = uri.file('/myfolder/myfile.js');
|
||||
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]);
|
||||
const breakpoints = model.getBreakpoints();
|
||||
const session = createMockSession(model);
|
||||
const data = new Map<string, DebugProtocol.Breakpoint>();
|
||||
|
||||
assert.equal(breakpoints[0].lineNumber, 5);
|
||||
assert.equal(breakpoints[1].lineNumber, 10);
|
||||
|
||||
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
|
||||
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
|
||||
model.setBreakpointSessionData(session.getId(), {}, data);
|
||||
assert.equal(breakpoints[0].lineNumber, 5);
|
||||
assert.equal(breakpoints[1].lineNumber, 50);
|
||||
|
||||
const session2 = createMockSession(model);
|
||||
const data2 = new Map<string, DebugProtocol.Breakpoint>();
|
||||
data2.set(breakpoints[0].getId(), { verified: true, line: 100 });
|
||||
data2.set(breakpoints[1].getId(), { verified: true, line: 500 });
|
||||
model.setBreakpointSessionData(session2.getId(), {}, data2);
|
||||
|
||||
// Breakpoint is verified only once, show that line
|
||||
assert.equal(breakpoints[0].lineNumber, 100);
|
||||
// Breakpoint is verified two times, show the original line
|
||||
assert.equal(breakpoints[1].lineNumber, 10);
|
||||
|
||||
model.setBreakpointSessionData(session.getId(), {}, undefined);
|
||||
// No more double session verification
|
||||
assert.equal(breakpoints[0].lineNumber, 100);
|
||||
assert.equal(breakpoints[1].lineNumber, 500);
|
||||
|
||||
assert.equal(breakpoints[0].supported, false);
|
||||
const data3 = new Map<string, DebugProtocol.Breakpoint>();
|
||||
data3.set(breakpoints[0].getId(), { verified: true, line: 500 });
|
||||
model.setBreakpointSessionData(session2.getId(), { supportsConditionalBreakpoints: true }, data2);
|
||||
assert.equal(breakpoints[0].supported, true);
|
||||
});
|
||||
|
||||
test('exception breakpoints', () => {
|
||||
let eventCount = 0;
|
||||
model.onDidChangeBreakpoints(() => eventCount++);
|
||||
model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]);
|
||||
assert.equal(eventCount, 1);
|
||||
let exceptionBreakpoints = model.getExceptionBreakpoints();
|
||||
assert.equal(exceptionBreakpoints.length, 1);
|
||||
assert.equal(exceptionBreakpoints[0].filter, 'uncaught');
|
||||
assert.equal(exceptionBreakpoints[0].enabled, true);
|
||||
|
||||
model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT' }, { filter: 'caught', label: 'CAUGHT' }]);
|
||||
assert.equal(eventCount, 2);
|
||||
exceptionBreakpoints = model.getExceptionBreakpoints();
|
||||
assert.equal(exceptionBreakpoints.length, 2);
|
||||
assert.equal(exceptionBreakpoints[0].filter, 'uncaught');
|
||||
assert.equal(exceptionBreakpoints[0].enabled, true);
|
||||
assert.equal(exceptionBreakpoints[1].filter, 'caught');
|
||||
assert.equal(exceptionBreakpoints[1].label, 'CAUGHT');
|
||||
assert.equal(exceptionBreakpoints[1].enabled, false);
|
||||
});
|
||||
|
||||
test('data breakpoints', () => {
|
||||
let eventCount = 0;
|
||||
model.onDidChangeBreakpoints(() => eventCount++);
|
||||
|
||||
model.addDataBreakpoint('label', 'id', true, ['read']);
|
||||
model.addDataBreakpoint('second', 'secondId', false, ['readWrite']);
|
||||
const dataBreakpoints = model.getDataBreakpoints();
|
||||
assert.equal(dataBreakpoints[0].canPersist, true);
|
||||
assert.equal(dataBreakpoints[0].dataId, 'id');
|
||||
assert.equal(dataBreakpoints[1].canPersist, false);
|
||||
assert.equal(dataBreakpoints[1].description, 'second');
|
||||
|
||||
assert.equal(eventCount, 2);
|
||||
|
||||
model.removeDataBreakpoints(dataBreakpoints[0].getId());
|
||||
assert.equal(eventCount, 3);
|
||||
assert.equal(model.getDataBreakpoints().length, 1);
|
||||
|
||||
model.removeDataBreakpoints();
|
||||
assert.equal(model.getDataBreakpoints().length, 0);
|
||||
assert.equal(eventCount, 4);
|
||||
});
|
||||
|
||||
test('message and class name', () => {
|
||||
const modelUri = uri.file('/myfolder/my file first.js');
|
||||
addBreakpointsAndCheckEvents(model, modelUri, [
|
||||
{ lineNumber: 5, enabled: true, condition: 'x > 5' },
|
||||
{ lineNumber: 10, enabled: false },
|
||||
{ lineNumber: 12, enabled: true, logMessage: 'hello' },
|
||||
{ lineNumber: 15, enabled: true, hitCondition: '12' },
|
||||
{ lineNumber: 500, enabled: true },
|
||||
]);
|
||||
const breakpoints = model.getBreakpoints();
|
||||
|
||||
let result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]);
|
||||
assert.equal(result.message, 'Expression: x > 5');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-conditional');
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[1]);
|
||||
assert.equal(result.message, 'Disabled Breakpoint');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-disabled');
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]);
|
||||
assert.equal(result.message, 'Log Message: hello');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-log');
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[3]);
|
||||
assert.equal(result.message, 'Hit Count: 12');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-conditional');
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[4]);
|
||||
assert.equal(result.message, 'Breakpoint');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint');
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, false, breakpoints[2]);
|
||||
assert.equal(result.message, 'Disabled Logpoint');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-log-disabled');
|
||||
|
||||
model.addDataBreakpoint('label', 'id', true, ['read']);
|
||||
const dataBreakpoints = model.getDataBreakpoints();
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, dataBreakpoints[0]);
|
||||
assert.equal(result.message, 'Data Breakpoint');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-data');
|
||||
|
||||
const functionBreakpoint = model.addFunctionBreakpoint('foo', '1');
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint);
|
||||
assert.equal(result.message, 'Function Breakpoint');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-function');
|
||||
|
||||
const data = new Map<string, DebugProtocol.Breakpoint>();
|
||||
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
|
||||
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
|
||||
data.set(breakpoints[2].getId(), { verified: true, line: 50, message: 'world' });
|
||||
data.set(functionBreakpoint.getId(), { verified: true });
|
||||
model.setBreakpointSessionData('mocksessionid', { supportsFunctionBreakpoints: false, supportsDataBreakpoints: true, supportsLogPoints: true }, data);
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]);
|
||||
assert.equal(result.message, 'Unverified Breakpoint');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-unverified');
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint);
|
||||
assert.equal(result.message, 'Function breakpoints not supported by this debug type');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-function-unverified');
|
||||
|
||||
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]);
|
||||
assert.equal(result.message, 'Log Message: hello, world');
|
||||
assert.equal(result.className, 'codicon-debug-breakpoint-log');
|
||||
});
|
||||
|
||||
test('decorations', () => {
|
||||
const modelUri = uri.file('/myfolder/my file first.js');
|
||||
const languageIdentifier = new LanguageIdentifier('testMode', LanguageId.PlainText);
|
||||
const textModel = new TextModel(
|
||||
['this is line one', 'this is line two', ' this is line three it has whitespace at start', 'this is line four', 'this is line five'].join('\n'),
|
||||
TextModel.DEFAULT_CREATION_OPTIONS,
|
||||
languageIdentifier
|
||||
);
|
||||
addBreakpointsAndCheckEvents(model, modelUri, [
|
||||
{ lineNumber: 1, enabled: true, condition: 'x > 5' },
|
||||
{ lineNumber: 2, column: 4, enabled: false },
|
||||
{ lineNumber: 3, enabled: true, logMessage: 'hello' },
|
||||
{ lineNumber: 500, enabled: true },
|
||||
]);
|
||||
const breakpoints = model.getBreakpoints();
|
||||
|
||||
let decorations = createBreakpointDecorations(textModel, breakpoints, State.Running, true, true);
|
||||
assert.equal(decorations.length, 3); // last breakpoint filtered out since it has a large line number
|
||||
assert.deepEqual(decorations[0].range, new Range(1, 1, 1, 2));
|
||||
assert.deepEqual(decorations[1].range, new Range(2, 4, 2, 5));
|
||||
assert.deepEqual(decorations[2].range, new Range(3, 5, 3, 6));
|
||||
assert.equal(decorations[0].options.beforeContentClassName, undefined);
|
||||
assert.equal(decorations[1].options.beforeContentClassName, `debug-breakpoint-placeholder`);
|
||||
assert.equal(decorations[0].options.overviewRuler?.position, OverviewRulerLane.Left);
|
||||
const expected = new MarkdownString().appendCodeblock(languageIdentifier.language, 'Expression: x > 5');
|
||||
assert.deepEqual(decorations[0].options.glyphMarginHoverMessage, expected);
|
||||
|
||||
decorations = createBreakpointDecorations(textModel, breakpoints, State.Running, true, false);
|
||||
assert.equal(decorations[0].options.overviewRuler, null);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,352 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import * as sinon from 'sinon';
|
||||
import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView';
|
||||
|
||||
export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
|
||||
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
|
||||
}
|
||||
|
||||
function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } {
|
||||
let firstStackFrame: StackFrame;
|
||||
let secondStackFrame: StackFrame;
|
||||
const thread = new class extends Thread {
|
||||
public getCallStack(): StackFrame[] {
|
||||
return [firstStackFrame, secondStackFrame];
|
||||
}
|
||||
}(session, 'mockthread', 1);
|
||||
|
||||
const firstSource = new Source({
|
||||
name: 'internalModule.js',
|
||||
path: 'a/b/c/d/internalModule.js',
|
||||
sourceReference: 10,
|
||||
}, 'aDebugSessionId');
|
||||
const secondSource = new Source({
|
||||
name: 'internalModule.js',
|
||||
path: 'z/x/c/d/internalModule.js',
|
||||
sourceReference: 11,
|
||||
}, 'aDebugSessionId');
|
||||
|
||||
firstStackFrame = new StackFrame(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
secondStackFrame = new StackFrame(thread, 1, secondSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
|
||||
return { firstStackFrame, secondStackFrame };
|
||||
}
|
||||
|
||||
suite('Debug - CallStack', () => {
|
||||
let model: DebugModel;
|
||||
let rawSession: MockRawSession;
|
||||
|
||||
setup(() => {
|
||||
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
|
||||
rawSession = new MockRawSession();
|
||||
});
|
||||
|
||||
// Threads
|
||||
|
||||
test('threads simple', () => {
|
||||
const threadId = 1;
|
||||
const threadName = 'firstThread';
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
assert.equal(model.getSessions(true).length, 1);
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: threadId,
|
||||
name: threadName
|
||||
}]
|
||||
});
|
||||
|
||||
assert.equal(session.getThread(threadId)!.name, threadName);
|
||||
|
||||
model.clearThreads(session.getId(), true);
|
||||
assert.equal(session.getThread(threadId), undefined);
|
||||
assert.equal(model.getSessions(true).length, 1);
|
||||
});
|
||||
|
||||
test('threads multiple wtih allThreadsStopped', () => {
|
||||
const threadId1 = 1;
|
||||
const threadName1 = 'firstThread';
|
||||
const threadId2 = 2;
|
||||
const threadName2 = 'secondThread';
|
||||
const stoppedReason = 'breakpoint';
|
||||
|
||||
// Add the threads
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
session['raw'] = <any>rawSession;
|
||||
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: threadId1,
|
||||
name: threadName1
|
||||
}]
|
||||
});
|
||||
|
||||
// Stopped event with all threads stopped
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: threadId1,
|
||||
name: threadName1
|
||||
}, {
|
||||
id: threadId2,
|
||||
name: threadName2
|
||||
}],
|
||||
stoppedDetails: {
|
||||
reason: stoppedReason,
|
||||
threadId: 1,
|
||||
allThreadsStopped: true
|
||||
},
|
||||
});
|
||||
|
||||
const thread1 = session.getThread(threadId1)!;
|
||||
const thread2 = session.getThread(threadId2)!;
|
||||
|
||||
// at the beginning, callstacks are obtainable but not available
|
||||
assert.equal(session.getAllThreads().length, 2);
|
||||
assert.equal(thread1.name, threadName1);
|
||||
assert.equal(thread1.stopped, true);
|
||||
assert.equal(thread1.getCallStack().length, 0);
|
||||
assert.equal(thread1.stoppedDetails!.reason, stoppedReason);
|
||||
assert.equal(thread2.name, threadName2);
|
||||
assert.equal(thread2.stopped, true);
|
||||
assert.equal(thread2.getCallStack().length, 0);
|
||||
assert.equal(thread2.stoppedDetails!.reason, undefined);
|
||||
|
||||
// after calling getCallStack, the callstack becomes available
|
||||
// and results in a request for the callstack in the debug adapter
|
||||
thread1.fetchCallStack().then(() => {
|
||||
assert.notEqual(thread1.getCallStack().length, 0);
|
||||
});
|
||||
|
||||
thread2.fetchCallStack().then(() => {
|
||||
assert.notEqual(thread2.getCallStack().length, 0);
|
||||
});
|
||||
|
||||
// calling multiple times getCallStack doesn't result in multiple calls
|
||||
// to the debug adapter
|
||||
thread1.fetchCallStack().then(() => {
|
||||
return thread2.fetchCallStack();
|
||||
});
|
||||
|
||||
// clearing the callstack results in the callstack not being available
|
||||
thread1.clearCallStack();
|
||||
assert.equal(thread1.stopped, true);
|
||||
assert.equal(thread1.getCallStack().length, 0);
|
||||
|
||||
thread2.clearCallStack();
|
||||
assert.equal(thread2.stopped, true);
|
||||
assert.equal(thread2.getCallStack().length, 0);
|
||||
|
||||
model.clearThreads(session.getId(), true);
|
||||
assert.equal(session.getThread(threadId1), undefined);
|
||||
assert.equal(session.getThread(threadId2), undefined);
|
||||
assert.equal(session.getAllThreads().length, 0);
|
||||
});
|
||||
|
||||
test('threads mutltiple without allThreadsStopped', () => {
|
||||
const sessionStub = sinon.spy(rawSession, 'stackTrace');
|
||||
|
||||
const stoppedThreadId = 1;
|
||||
const stoppedThreadName = 'stoppedThread';
|
||||
const runningThreadId = 2;
|
||||
const runningThreadName = 'runningThread';
|
||||
const stoppedReason = 'breakpoint';
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
session['raw'] = <any>rawSession;
|
||||
|
||||
// Add the threads
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: stoppedThreadId,
|
||||
name: stoppedThreadName
|
||||
}]
|
||||
});
|
||||
|
||||
// Stopped event with only one thread stopped
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: 1,
|
||||
name: stoppedThreadName
|
||||
}, {
|
||||
id: runningThreadId,
|
||||
name: runningThreadName
|
||||
}],
|
||||
stoppedDetails: {
|
||||
reason: stoppedReason,
|
||||
threadId: 1,
|
||||
allThreadsStopped: false
|
||||
}
|
||||
});
|
||||
|
||||
const stoppedThread = session.getThread(stoppedThreadId)!;
|
||||
const runningThread = session.getThread(runningThreadId)!;
|
||||
|
||||
// the callstack for the stopped thread is obtainable but not available
|
||||
// the callstack for the running thread is not obtainable nor available
|
||||
assert.equal(stoppedThread.name, stoppedThreadName);
|
||||
assert.equal(stoppedThread.stopped, true);
|
||||
assert.equal(session.getAllThreads().length, 2);
|
||||
assert.equal(stoppedThread.getCallStack().length, 0);
|
||||
assert.equal(stoppedThread.stoppedDetails!.reason, stoppedReason);
|
||||
assert.equal(runningThread.name, runningThreadName);
|
||||
assert.equal(runningThread.stopped, false);
|
||||
assert.equal(runningThread.getCallStack().length, 0);
|
||||
assert.equal(runningThread.stoppedDetails, undefined);
|
||||
|
||||
// after calling getCallStack, the callstack becomes available
|
||||
// and results in a request for the callstack in the debug adapter
|
||||
stoppedThread.fetchCallStack().then(() => {
|
||||
assert.notEqual(stoppedThread.getCallStack().length, 0);
|
||||
assert.equal(runningThread.getCallStack().length, 0);
|
||||
assert.equal(sessionStub.callCount, 1);
|
||||
});
|
||||
|
||||
// calling getCallStack on the running thread returns empty array
|
||||
// and does not return in a request for the callstack in the debug
|
||||
// adapter
|
||||
runningThread.fetchCallStack().then(() => {
|
||||
assert.equal(runningThread.getCallStack().length, 0);
|
||||
assert.equal(sessionStub.callCount, 1);
|
||||
});
|
||||
|
||||
// clearing the callstack results in the callstack not being available
|
||||
stoppedThread.clearCallStack();
|
||||
assert.equal(stoppedThread.stopped, true);
|
||||
assert.equal(stoppedThread.getCallStack().length, 0);
|
||||
|
||||
model.clearThreads(session.getId(), true);
|
||||
assert.equal(session.getThread(stoppedThreadId), undefined);
|
||||
assert.equal(session.getThread(runningThreadId), undefined);
|
||||
assert.equal(session.getAllThreads().length, 0);
|
||||
});
|
||||
|
||||
test('stack frame get specific source name', () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
|
||||
|
||||
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
|
||||
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
|
||||
});
|
||||
|
||||
test('stack frame toString()', () => {
|
||||
const session = createMockSession(model);
|
||||
const thread = new Thread(session, 'mockthread', 1);
|
||||
const firstSource = new Source({
|
||||
name: 'internalModule.js',
|
||||
path: 'a/b/c/d/internalModule.js',
|
||||
sourceReference: 10,
|
||||
}, 'aDebugSessionId');
|
||||
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
assert.equal(stackFrame.toString(), 'app (internalModule.js:1)');
|
||||
|
||||
const secondSource = new Source(undefined, 'aDebugSessionId');
|
||||
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2);
|
||||
assert.equal(stackFrame2.toString(), 'module');
|
||||
});
|
||||
|
||||
test('debug child sessions are added in correct order', () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
const secondSession = createMockSession(model, 'mockSession2');
|
||||
model.addSession(secondSession);
|
||||
const firstChild = createMockSession(model, 'firstChild', { parentSession: session });
|
||||
model.addSession(firstChild);
|
||||
const secondChild = createMockSession(model, 'secondChild', { parentSession: session });
|
||||
model.addSession(secondChild);
|
||||
const thirdSession = createMockSession(model, 'mockSession3');
|
||||
model.addSession(thirdSession);
|
||||
const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession });
|
||||
model.addSession(anotherChild);
|
||||
|
||||
const sessions = model.getSessions();
|
||||
assert.equal(sessions[0].getId(), session.getId());
|
||||
assert.equal(sessions[1].getId(), firstChild.getId());
|
||||
assert.equal(sessions[2].getId(), secondChild.getId());
|
||||
assert.equal(sessions[3].getId(), secondSession.getId());
|
||||
assert.equal(sessions[4].getId(), anotherChild.getId());
|
||||
assert.equal(sessions[5].getId(), thirdSession.getId());
|
||||
});
|
||||
|
||||
test('decorations', () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
|
||||
let decorations = createDecorationsForStackFrame(firstStackFrame, firstStackFrame.range);
|
||||
assert.equal(decorations.length, 2);
|
||||
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
|
||||
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe');
|
||||
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
|
||||
assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line');
|
||||
assert.equal(decorations[1].options.isWholeLine, true);
|
||||
|
||||
decorations = createDecorationsForStackFrame(secondStackFrame, firstStackFrame.range);
|
||||
assert.equal(decorations.length, 2);
|
||||
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
|
||||
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe-focused');
|
||||
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
|
||||
assert.equal(decorations[1].options.className, 'debug-focused-stack-frame-line');
|
||||
assert.equal(decorations[1].options.isWholeLine, true);
|
||||
|
||||
decorations = createDecorationsForStackFrame(firstStackFrame, new Range(1, 5, 1, 6));
|
||||
assert.equal(decorations.length, 3);
|
||||
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
|
||||
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe');
|
||||
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
|
||||
assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line');
|
||||
assert.equal(decorations[1].options.isWholeLine, true);
|
||||
// Inline decoration gets rendered in this case
|
||||
assert.equal(decorations[2].options.beforeContentClassName, 'debug-top-stack-frame-column');
|
||||
assert.deepEqual(decorations[2].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
|
||||
});
|
||||
|
||||
test('contexts', () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
|
||||
let context = getContext(firstStackFrame);
|
||||
assert.equal(context.sessionId, firstStackFrame.thread.session.getId());
|
||||
assert.equal(context.threadId, firstStackFrame.thread.getId());
|
||||
assert.equal(context.frameId, firstStackFrame.getId());
|
||||
|
||||
context = getContext(secondStackFrame.thread);
|
||||
assert.equal(context.sessionId, secondStackFrame.thread.session.getId());
|
||||
assert.equal(context.threadId, secondStackFrame.thread.getId());
|
||||
assert.equal(context.frameId, undefined);
|
||||
|
||||
context = getContext(session);
|
||||
assert.equal(context.sessionId, session.getId());
|
||||
assert.equal(context.threadId, undefined);
|
||||
assert.equal(context.frameId, undefined);
|
||||
|
||||
let contributedContext = getContextForContributedActions(firstStackFrame);
|
||||
assert.equal(contributedContext, firstStackFrame.source.raw.path);
|
||||
contributedContext = getContextForContributedActions(firstStackFrame.thread);
|
||||
assert.equal(contributedContext, firstStackFrame.thread.threadId);
|
||||
contributedContext = getContextForContributedActions(session);
|
||||
assert.equal(contributedContext, session.getId());
|
||||
});
|
||||
});
|
||||
@@ -1,575 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { DebugModel, Expression, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import * as sinon from 'sinon';
|
||||
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
|
||||
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
|
||||
import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
|
||||
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
|
||||
}
|
||||
|
||||
suite('Debug - Model', () => {
|
||||
let model: DebugModel;
|
||||
let rawSession: MockRawSession;
|
||||
|
||||
setup(() => {
|
||||
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
|
||||
rawSession = new MockRawSession();
|
||||
});
|
||||
|
||||
// Breakpoints
|
||||
|
||||
test('breakpoints simple', () => {
|
||||
const modelUri = uri.file('/myfolder/myfile.js');
|
||||
model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
|
||||
assert.equal(model.areBreakpointsActivated(), true);
|
||||
assert.equal(model.getBreakpoints().length, 2);
|
||||
|
||||
model.removeBreakpoints(model.getBreakpoints());
|
||||
assert.equal(model.getBreakpoints().length, 0);
|
||||
});
|
||||
|
||||
test('breakpoints toggling', () => {
|
||||
const modelUri = uri.file('/myfolder/myfile.js');
|
||||
model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
|
||||
model.addBreakpoints(modelUri, [{ lineNumber: 12, enabled: true, condition: 'fake condition' }]);
|
||||
assert.equal(model.getBreakpoints().length, 3);
|
||||
const bp = model.getBreakpoints().pop();
|
||||
if (bp) {
|
||||
model.removeBreakpoints([bp]);
|
||||
}
|
||||
assert.equal(model.getBreakpoints().length, 2);
|
||||
|
||||
model.setBreakpointsActivated(false);
|
||||
assert.equal(model.areBreakpointsActivated(), false);
|
||||
model.setBreakpointsActivated(true);
|
||||
assert.equal(model.areBreakpointsActivated(), true);
|
||||
});
|
||||
|
||||
test('breakpoints two files', () => {
|
||||
const modelUri1 = uri.file('/myfolder/my file first.js');
|
||||
const modelUri2 = uri.file('/secondfolder/second/second file.js');
|
||||
model.addBreakpoints(modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
|
||||
model.addBreakpoints(modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]);
|
||||
|
||||
assert.equal(model.getBreakpoints().length, 5);
|
||||
const bp = model.getBreakpoints()[0];
|
||||
const update = new Map<string, IBreakpointUpdateData>();
|
||||
update.set(bp.getId(), { lineNumber: 100 });
|
||||
model.updateBreakpoints(update);
|
||||
assert.equal(bp.lineNumber, 100);
|
||||
|
||||
model.enableOrDisableAllBreakpoints(false);
|
||||
model.getBreakpoints().forEach(bp => {
|
||||
assert.equal(bp.enabled, false);
|
||||
});
|
||||
model.setEnablement(bp, true);
|
||||
assert.equal(bp.enabled, true);
|
||||
|
||||
model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 }));
|
||||
assert.equal(model.getBreakpoints().length, 3);
|
||||
});
|
||||
|
||||
test('breakpoints conditions', () => {
|
||||
const modelUri1 = uri.file('/myfolder/my file first.js');
|
||||
model.addBreakpoints(modelUri1, [{ lineNumber: 5, condition: 'i < 5', hitCondition: '17' }, { lineNumber: 10, condition: 'j < 3' }]);
|
||||
const breakpoints = model.getBreakpoints();
|
||||
|
||||
assert.equal(breakpoints[0].condition, 'i < 5');
|
||||
assert.equal(breakpoints[0].hitCondition, '17');
|
||||
assert.equal(breakpoints[1].condition, 'j < 3');
|
||||
assert.equal(!!breakpoints[1].hitCondition, false);
|
||||
|
||||
assert.equal(model.getBreakpoints().length, 2);
|
||||
model.removeBreakpoints(model.getBreakpoints());
|
||||
assert.equal(model.getBreakpoints().length, 0);
|
||||
});
|
||||
|
||||
test('function breakpoints', () => {
|
||||
model.addFunctionBreakpoint('foo', '1');
|
||||
model.addFunctionBreakpoint('bar', '2');
|
||||
model.renameFunctionBreakpoint('1', 'fooUpdated');
|
||||
model.renameFunctionBreakpoint('2', 'barUpdated');
|
||||
|
||||
const functionBps = model.getFunctionBreakpoints();
|
||||
assert.equal(functionBps[0].name, 'fooUpdated');
|
||||
assert.equal(functionBps[1].name, 'barUpdated');
|
||||
|
||||
model.removeFunctionBreakpoints();
|
||||
assert.equal(model.getFunctionBreakpoints().length, 0);
|
||||
});
|
||||
|
||||
test('breakpoints multiple sessions', () => {
|
||||
const modelUri = uri.file('/myfolder/myfile.js');
|
||||
const breakpoints = model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]);
|
||||
const session = createMockSession(model);
|
||||
const data = new Map<string, DebugProtocol.Breakpoint>();
|
||||
|
||||
assert.equal(breakpoints[0].lineNumber, 5);
|
||||
assert.equal(breakpoints[1].lineNumber, 10);
|
||||
|
||||
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
|
||||
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
|
||||
model.setBreakpointSessionData(session.getId(), {}, data);
|
||||
assert.equal(breakpoints[0].lineNumber, 5);
|
||||
assert.equal(breakpoints[1].lineNumber, 50);
|
||||
|
||||
const session2 = createMockSession(model);
|
||||
const data2 = new Map<string, DebugProtocol.Breakpoint>();
|
||||
data2.set(breakpoints[0].getId(), { verified: true, line: 100 });
|
||||
data2.set(breakpoints[1].getId(), { verified: true, line: 500 });
|
||||
model.setBreakpointSessionData(session2.getId(), {}, data2);
|
||||
|
||||
// Breakpoint is verified only once, show that line
|
||||
assert.equal(breakpoints[0].lineNumber, 100);
|
||||
// Breakpoint is verified two times, show the original line
|
||||
assert.equal(breakpoints[1].lineNumber, 10);
|
||||
|
||||
model.setBreakpointSessionData(session.getId(), {}, undefined);
|
||||
// No more double session verification
|
||||
assert.equal(breakpoints[0].lineNumber, 100);
|
||||
assert.equal(breakpoints[1].lineNumber, 500);
|
||||
|
||||
assert.equal(breakpoints[0].supported, false);
|
||||
const data3 = new Map<string, DebugProtocol.Breakpoint>();
|
||||
data3.set(breakpoints[0].getId(), { verified: true, line: 500 });
|
||||
model.setBreakpointSessionData(session2.getId(), { supportsConditionalBreakpoints: true }, data2);
|
||||
assert.equal(breakpoints[0].supported, true);
|
||||
});
|
||||
|
||||
// Threads
|
||||
|
||||
test('threads simple', () => {
|
||||
const threadId = 1;
|
||||
const threadName = 'firstThread';
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
assert.equal(model.getSessions(true).length, 1);
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: threadId,
|
||||
name: threadName
|
||||
}]
|
||||
});
|
||||
|
||||
assert.equal(session.getThread(threadId)!.name, threadName);
|
||||
|
||||
model.clearThreads(session.getId(), true);
|
||||
assert.equal(session.getThread(threadId), undefined);
|
||||
assert.equal(model.getSessions(true).length, 1);
|
||||
});
|
||||
|
||||
test('threads multiple wtih allThreadsStopped', () => {
|
||||
const threadId1 = 1;
|
||||
const threadName1 = 'firstThread';
|
||||
const threadId2 = 2;
|
||||
const threadName2 = 'secondThread';
|
||||
const stoppedReason = 'breakpoint';
|
||||
|
||||
// Add the threads
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
session['raw'] = <any>rawSession;
|
||||
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: threadId1,
|
||||
name: threadName1
|
||||
}]
|
||||
});
|
||||
|
||||
// Stopped event with all threads stopped
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: threadId1,
|
||||
name: threadName1
|
||||
}, {
|
||||
id: threadId2,
|
||||
name: threadName2
|
||||
}],
|
||||
stoppedDetails: {
|
||||
reason: stoppedReason,
|
||||
threadId: 1,
|
||||
allThreadsStopped: true
|
||||
},
|
||||
});
|
||||
|
||||
const thread1 = session.getThread(threadId1)!;
|
||||
const thread2 = session.getThread(threadId2)!;
|
||||
|
||||
// at the beginning, callstacks are obtainable but not available
|
||||
assert.equal(session.getAllThreads().length, 2);
|
||||
assert.equal(thread1.name, threadName1);
|
||||
assert.equal(thread1.stopped, true);
|
||||
assert.equal(thread1.getCallStack().length, 0);
|
||||
assert.equal(thread1.stoppedDetails!.reason, stoppedReason);
|
||||
assert.equal(thread2.name, threadName2);
|
||||
assert.equal(thread2.stopped, true);
|
||||
assert.equal(thread2.getCallStack().length, 0);
|
||||
assert.equal(thread2.stoppedDetails!.reason, undefined);
|
||||
|
||||
// after calling getCallStack, the callstack becomes available
|
||||
// and results in a request for the callstack in the debug adapter
|
||||
thread1.fetchCallStack().then(() => {
|
||||
assert.notEqual(thread1.getCallStack().length, 0);
|
||||
});
|
||||
|
||||
thread2.fetchCallStack().then(() => {
|
||||
assert.notEqual(thread2.getCallStack().length, 0);
|
||||
});
|
||||
|
||||
// calling multiple times getCallStack doesn't result in multiple calls
|
||||
// to the debug adapter
|
||||
thread1.fetchCallStack().then(() => {
|
||||
return thread2.fetchCallStack();
|
||||
});
|
||||
|
||||
// clearing the callstack results in the callstack not being available
|
||||
thread1.clearCallStack();
|
||||
assert.equal(thread1.stopped, true);
|
||||
assert.equal(thread1.getCallStack().length, 0);
|
||||
|
||||
thread2.clearCallStack();
|
||||
assert.equal(thread2.stopped, true);
|
||||
assert.equal(thread2.getCallStack().length, 0);
|
||||
|
||||
model.clearThreads(session.getId(), true);
|
||||
assert.equal(session.getThread(threadId1), undefined);
|
||||
assert.equal(session.getThread(threadId2), undefined);
|
||||
assert.equal(session.getAllThreads().length, 0);
|
||||
});
|
||||
|
||||
test('threads mutltiple without allThreadsStopped', () => {
|
||||
const sessionStub = sinon.spy(rawSession, 'stackTrace');
|
||||
|
||||
const stoppedThreadId = 1;
|
||||
const stoppedThreadName = 'stoppedThread';
|
||||
const runningThreadId = 2;
|
||||
const runningThreadName = 'runningThread';
|
||||
const stoppedReason = 'breakpoint';
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
session['raw'] = <any>rawSession;
|
||||
|
||||
// Add the threads
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: stoppedThreadId,
|
||||
name: stoppedThreadName
|
||||
}]
|
||||
});
|
||||
|
||||
// Stopped event with only one thread stopped
|
||||
model.rawUpdate({
|
||||
sessionId: session.getId(),
|
||||
threads: [{
|
||||
id: 1,
|
||||
name: stoppedThreadName
|
||||
}, {
|
||||
id: runningThreadId,
|
||||
name: runningThreadName
|
||||
}],
|
||||
stoppedDetails: {
|
||||
reason: stoppedReason,
|
||||
threadId: 1,
|
||||
allThreadsStopped: false
|
||||
}
|
||||
});
|
||||
|
||||
const stoppedThread = session.getThread(stoppedThreadId)!;
|
||||
const runningThread = session.getThread(runningThreadId)!;
|
||||
|
||||
// the callstack for the stopped thread is obtainable but not available
|
||||
// the callstack for the running thread is not obtainable nor available
|
||||
assert.equal(stoppedThread.name, stoppedThreadName);
|
||||
assert.equal(stoppedThread.stopped, true);
|
||||
assert.equal(session.getAllThreads().length, 2);
|
||||
assert.equal(stoppedThread.getCallStack().length, 0);
|
||||
assert.equal(stoppedThread.stoppedDetails!.reason, stoppedReason);
|
||||
assert.equal(runningThread.name, runningThreadName);
|
||||
assert.equal(runningThread.stopped, false);
|
||||
assert.equal(runningThread.getCallStack().length, 0);
|
||||
assert.equal(runningThread.stoppedDetails, undefined);
|
||||
|
||||
// after calling getCallStack, the callstack becomes available
|
||||
// and results in a request for the callstack in the debug adapter
|
||||
stoppedThread.fetchCallStack().then(() => {
|
||||
assert.notEqual(stoppedThread.getCallStack().length, 0);
|
||||
assert.equal(runningThread.getCallStack().length, 0);
|
||||
assert.equal(sessionStub.callCount, 1);
|
||||
});
|
||||
|
||||
// calling getCallStack on the running thread returns empty array
|
||||
// and does not return in a request for the callstack in the debug
|
||||
// adapter
|
||||
runningThread.fetchCallStack().then(() => {
|
||||
assert.equal(runningThread.getCallStack().length, 0);
|
||||
assert.equal(sessionStub.callCount, 1);
|
||||
});
|
||||
|
||||
// clearing the callstack results in the callstack not being available
|
||||
stoppedThread.clearCallStack();
|
||||
assert.equal(stoppedThread.stopped, true);
|
||||
assert.equal(stoppedThread.getCallStack().length, 0);
|
||||
|
||||
model.clearThreads(session.getId(), true);
|
||||
assert.equal(session.getThread(stoppedThreadId), undefined);
|
||||
assert.equal(session.getThread(runningThreadId), undefined);
|
||||
assert.equal(session.getAllThreads().length, 0);
|
||||
});
|
||||
|
||||
// Expressions
|
||||
|
||||
function assertWatchExpressions(watchExpressions: Expression[], expectedName: string) {
|
||||
assert.equal(watchExpressions.length, 2);
|
||||
watchExpressions.forEach(we => {
|
||||
assert.equal(we.available, false);
|
||||
assert.equal(we.reference, 0);
|
||||
assert.equal(we.name, expectedName);
|
||||
});
|
||||
}
|
||||
|
||||
test('watch expressions', () => {
|
||||
assert.equal(model.getWatchExpressions().length, 0);
|
||||
model.addWatchExpression('console');
|
||||
model.addWatchExpression('console');
|
||||
let watchExpressions = model.getWatchExpressions();
|
||||
assertWatchExpressions(watchExpressions, 'console');
|
||||
|
||||
model.renameWatchExpression(watchExpressions[0].getId(), 'new_name');
|
||||
model.renameWatchExpression(watchExpressions[1].getId(), 'new_name');
|
||||
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
|
||||
|
||||
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
|
||||
|
||||
model.addWatchExpression('mockExpression');
|
||||
model.moveWatchExpression(model.getWatchExpressions()[2].getId(), 1);
|
||||
watchExpressions = model.getWatchExpressions();
|
||||
assert.equal(watchExpressions[0].name, 'new_name');
|
||||
assert.equal(watchExpressions[1].name, 'mockExpression');
|
||||
assert.equal(watchExpressions[2].name, 'new_name');
|
||||
|
||||
model.removeWatchExpressions();
|
||||
assert.equal(model.getWatchExpressions().length, 0);
|
||||
});
|
||||
|
||||
test('repl expressions', () => {
|
||||
const session = createMockSession(model);
|
||||
assert.equal(session.getReplElements().length, 0);
|
||||
model.addSession(session);
|
||||
|
||||
session['raw'] = <any>rawSession;
|
||||
const thread = new Thread(session, 'mockthread', 1);
|
||||
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
const replModel = new ReplModel();
|
||||
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
|
||||
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
|
||||
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
|
||||
|
||||
assert.equal(replModel.getReplElements().length, 3);
|
||||
replModel.getReplElements().forEach(re => {
|
||||
assert.equal((<ReplEvaluationInput>re).value, 'myVariable');
|
||||
});
|
||||
|
||||
replModel.removeReplExpressions();
|
||||
assert.equal(replModel.getReplElements().length, 0);
|
||||
});
|
||||
|
||||
test('stack frame get specific source name', () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
let firstStackFrame: StackFrame;
|
||||
let secondStackFrame: StackFrame;
|
||||
const thread = new class extends Thread {
|
||||
public getCallStack(): StackFrame[] {
|
||||
return [firstStackFrame, secondStackFrame];
|
||||
}
|
||||
}(session, 'mockthread', 1);
|
||||
|
||||
const firstSource = new Source({
|
||||
name: 'internalModule.js',
|
||||
path: 'a/b/c/d/internalModule.js',
|
||||
sourceReference: 10,
|
||||
}, 'aDebugSessionId');
|
||||
const secondSource = new Source({
|
||||
name: 'internalModule.js',
|
||||
path: 'z/x/c/d/internalModule.js',
|
||||
sourceReference: 11,
|
||||
}, 'aDebugSessionId');
|
||||
firstStackFrame = new StackFrame(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
secondStackFrame = new StackFrame(thread, 1, secondSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
|
||||
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
|
||||
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
|
||||
});
|
||||
|
||||
test('stack frame toString()', () => {
|
||||
const session = createMockSession(model);
|
||||
const thread = new Thread(session, 'mockthread', 1);
|
||||
const firstSource = new Source({
|
||||
name: 'internalModule.js',
|
||||
path: 'a/b/c/d/internalModule.js',
|
||||
sourceReference: 10,
|
||||
}, 'aDebugSessionId');
|
||||
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
assert.equal(stackFrame.toString(), 'app (internalModule.js:1)');
|
||||
|
||||
const secondSource = new Source(undefined, 'aDebugSessionId');
|
||||
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2);
|
||||
assert.equal(stackFrame2.toString(), 'module');
|
||||
});
|
||||
|
||||
test('debug child sessions are added in correct order', () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
const secondSession = createMockSession(model, 'mockSession2');
|
||||
model.addSession(secondSession);
|
||||
const firstChild = createMockSession(model, 'firstChild', { parentSession: session });
|
||||
model.addSession(firstChild);
|
||||
const secondChild = createMockSession(model, 'secondChild', { parentSession: session });
|
||||
model.addSession(secondChild);
|
||||
const thirdSession = createMockSession(model, 'mockSession3');
|
||||
model.addSession(thirdSession);
|
||||
const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession });
|
||||
model.addSession(anotherChild);
|
||||
|
||||
const sessions = model.getSessions();
|
||||
assert.equal(sessions[0].getId(), session.getId());
|
||||
assert.equal(sessions[1].getId(), firstChild.getId());
|
||||
assert.equal(sessions[2].getId(), secondChild.getId());
|
||||
assert.equal(sessions[3].getId(), secondSession.getId());
|
||||
assert.equal(sessions[4].getId(), anotherChild.getId());
|
||||
assert.equal(sessions[5].getId(), thirdSession.getId());
|
||||
});
|
||||
|
||||
// Repl output
|
||||
|
||||
test('repl output', () => {
|
||||
const session = createMockSession(model);
|
||||
const repl = new ReplModel();
|
||||
repl.appendToRepl(session, 'first line\n', severity.Error);
|
||||
repl.appendToRepl(session, 'second line ', severity.Error);
|
||||
repl.appendToRepl(session, 'third line ', severity.Error);
|
||||
repl.appendToRepl(session, 'fourth line', severity.Error);
|
||||
|
||||
let elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 2);
|
||||
assert.equal(elements[0].value, 'first line\n');
|
||||
assert.equal(elements[0].severity, severity.Error);
|
||||
assert.equal(elements[1].value, 'second line third line fourth line');
|
||||
assert.equal(elements[1].severity, severity.Error);
|
||||
|
||||
repl.appendToRepl(session, '1', severity.Warning);
|
||||
elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 3);
|
||||
assert.equal(elements[2].value, '1');
|
||||
assert.equal(elements[2].severity, severity.Warning);
|
||||
|
||||
const keyValueObject = { 'key1': 2, 'key2': 'value' };
|
||||
repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
|
||||
const element = <RawObjectReplElement>repl.getReplElements()[3];
|
||||
assert.equal(element.value, 'Object');
|
||||
assert.deepEqual(element.valueObj, keyValueObject);
|
||||
|
||||
repl.removeReplExpressions();
|
||||
assert.equal(repl.getReplElements().length, 0);
|
||||
|
||||
repl.appendToRepl(session, '1\n', severity.Info);
|
||||
repl.appendToRepl(session, '2', severity.Info);
|
||||
repl.appendToRepl(session, '3\n4', severity.Info);
|
||||
repl.appendToRepl(session, '5\n', severity.Info);
|
||||
repl.appendToRepl(session, '6', severity.Info);
|
||||
elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 3);
|
||||
assert.equal(elements[0], '1\n');
|
||||
assert.equal(elements[1], '23\n45\n');
|
||||
assert.equal(elements[2], '6');
|
||||
});
|
||||
|
||||
test('repl merging', () => {
|
||||
// 'mergeWithParent' should be ignored when there is no parent.
|
||||
const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' });
|
||||
const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' });
|
||||
const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' });
|
||||
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
|
||||
const child3 = createMockSession(model, 'child3', { parentSession: parent });
|
||||
|
||||
let parentChanges = 0;
|
||||
parent.onDidChangeReplElements(() => ++parentChanges);
|
||||
|
||||
parent.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 1);
|
||||
assert.equal(parent.getReplElements().length, 1);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 1);
|
||||
assert.equal(grandChild.getReplElements().length, 1);
|
||||
assert.equal(child3.getReplElements().length, 0);
|
||||
|
||||
grandChild.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
assert.equal(grandChild.getReplElements().length, 2);
|
||||
assert.equal(child3.getReplElements().length, 0);
|
||||
|
||||
child3.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
assert.equal(grandChild.getReplElements().length, 2);
|
||||
assert.equal(child3.getReplElements().length, 1);
|
||||
|
||||
child1.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 1);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
assert.equal(grandChild.getReplElements().length, 2);
|
||||
assert.equal(child3.getReplElements().length, 1);
|
||||
});
|
||||
|
||||
test('repl ordering', async () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
const adapter = new MockDebugAdapter();
|
||||
const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!);
|
||||
session.initializeForTest(raw);
|
||||
|
||||
await session.addReplExpression(undefined, 'before.1');
|
||||
assert.equal(session.getReplElements().length, 3);
|
||||
assert.equal((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');
|
||||
assert.equal((<SimpleReplElement>session.getReplElements()[1]).value, 'before.1');
|
||||
assert.equal((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');
|
||||
|
||||
await session.addReplExpression(undefined, 'after.2');
|
||||
await timeout(0);
|
||||
assert.equal(session.getReplElements().length, 6);
|
||||
assert.equal((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');
|
||||
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
|
||||
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as assert from 'assert';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
|
||||
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
|
||||
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
|
||||
|
||||
suite('Debug - REPL', () => {
|
||||
let model: DebugModel;
|
||||
let rawSession: MockRawSession;
|
||||
|
||||
setup(() => {
|
||||
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
|
||||
rawSession = new MockRawSession();
|
||||
});
|
||||
|
||||
test('repl output', () => {
|
||||
const session = createMockSession(model);
|
||||
const repl = new ReplModel();
|
||||
repl.appendToRepl(session, 'first line\n', severity.Error);
|
||||
repl.appendToRepl(session, 'second line ', severity.Error);
|
||||
repl.appendToRepl(session, 'third line ', severity.Error);
|
||||
repl.appendToRepl(session, 'fourth line', severity.Error);
|
||||
|
||||
let elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 2);
|
||||
assert.equal(elements[0].value, 'first line\n');
|
||||
assert.equal(elements[0].severity, severity.Error);
|
||||
assert.equal(elements[1].value, 'second line third line fourth line');
|
||||
assert.equal(elements[1].severity, severity.Error);
|
||||
|
||||
repl.appendToRepl(session, '1', severity.Warning);
|
||||
elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 3);
|
||||
assert.equal(elements[2].value, '1');
|
||||
assert.equal(elements[2].severity, severity.Warning);
|
||||
|
||||
const keyValueObject = { 'key1': 2, 'key2': 'value' };
|
||||
repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
|
||||
const element = <RawObjectReplElement>repl.getReplElements()[3];
|
||||
assert.equal(element.value, 'Object');
|
||||
assert.deepEqual(element.valueObj, keyValueObject);
|
||||
|
||||
repl.removeReplExpressions();
|
||||
assert.equal(repl.getReplElements().length, 0);
|
||||
|
||||
repl.appendToRepl(session, '1\n', severity.Info);
|
||||
repl.appendToRepl(session, '2', severity.Info);
|
||||
repl.appendToRepl(session, '3\n4', severity.Info);
|
||||
repl.appendToRepl(session, '5\n', severity.Info);
|
||||
repl.appendToRepl(session, '6', severity.Info);
|
||||
elements = <SimpleReplElement[]>repl.getReplElements();
|
||||
assert.equal(elements.length, 3);
|
||||
assert.equal(elements[0], '1\n');
|
||||
assert.equal(elements[1], '23\n45\n');
|
||||
assert.equal(elements[2], '6');
|
||||
});
|
||||
|
||||
test('repl merging', () => {
|
||||
// 'mergeWithParent' should be ignored when there is no parent.
|
||||
const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' });
|
||||
const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' });
|
||||
const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' });
|
||||
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
|
||||
const child3 = createMockSession(model, 'child3', { parentSession: parent });
|
||||
|
||||
let parentChanges = 0;
|
||||
parent.onDidChangeReplElements(() => ++parentChanges);
|
||||
|
||||
parent.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 1);
|
||||
assert.equal(parent.getReplElements().length, 1);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 1);
|
||||
assert.equal(grandChild.getReplElements().length, 1);
|
||||
assert.equal(child3.getReplElements().length, 0);
|
||||
|
||||
grandChild.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
assert.equal(grandChild.getReplElements().length, 2);
|
||||
assert.equal(child3.getReplElements().length, 0);
|
||||
|
||||
child3.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 0);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
assert.equal(grandChild.getReplElements().length, 2);
|
||||
assert.equal(child3.getReplElements().length, 1);
|
||||
|
||||
child1.appendToRepl('1\n', severity.Info);
|
||||
assert.equal(parentChanges, 2);
|
||||
assert.equal(parent.getReplElements().length, 2);
|
||||
assert.equal(child1.getReplElements().length, 1);
|
||||
assert.equal(child2.getReplElements().length, 2);
|
||||
assert.equal(grandChild.getReplElements().length, 2);
|
||||
assert.equal(child3.getReplElements().length, 1);
|
||||
});
|
||||
|
||||
test('repl expressions', () => {
|
||||
const session = createMockSession(model);
|
||||
assert.equal(session.getReplElements().length, 0);
|
||||
model.addSession(session);
|
||||
|
||||
session['raw'] = <any>rawSession;
|
||||
const thread = new Thread(session, 'mockthread', 1);
|
||||
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
|
||||
const replModel = new ReplModel();
|
||||
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
|
||||
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
|
||||
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
|
||||
|
||||
assert.equal(replModel.getReplElements().length, 3);
|
||||
replModel.getReplElements().forEach(re => {
|
||||
assert.equal((<ReplEvaluationInput>re).value, 'myVariable');
|
||||
});
|
||||
|
||||
replModel.removeReplExpressions();
|
||||
assert.equal(replModel.getReplElements().length, 0);
|
||||
});
|
||||
|
||||
test('repl ordering', async () => {
|
||||
const session = createMockSession(model);
|
||||
model.addSession(session);
|
||||
|
||||
const adapter = new MockDebugAdapter();
|
||||
const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!);
|
||||
session.initializeForTest(raw);
|
||||
|
||||
await session.addReplExpression(undefined, 'before.1');
|
||||
assert.equal(session.getReplElements().length, 3);
|
||||
assert.equal((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');
|
||||
assert.equal((<SimpleReplElement>session.getReplElements()[1]).value, 'before.1');
|
||||
assert.equal((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');
|
||||
|
||||
await session.addReplExpression(undefined, 'after.2');
|
||||
await timeout(0);
|
||||
assert.equal(session.getReplElements().length, 6);
|
||||
assert.equal((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');
|
||||
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
|
||||
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Expression, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
|
||||
// Expressions
|
||||
|
||||
function assertWatchExpressions(watchExpressions: Expression[], expectedName: string) {
|
||||
assert.equal(watchExpressions.length, 2);
|
||||
watchExpressions.forEach(we => {
|
||||
assert.equal(we.available, false);
|
||||
assert.equal(we.reference, 0);
|
||||
assert.equal(we.name, expectedName);
|
||||
});
|
||||
}
|
||||
|
||||
suite('Debug - Watch', () => {
|
||||
|
||||
let model: DebugModel;
|
||||
|
||||
setup(() => {
|
||||
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
|
||||
});
|
||||
|
||||
test('watch expressions', () => {
|
||||
assert.equal(model.getWatchExpressions().length, 0);
|
||||
model.addWatchExpression('console');
|
||||
model.addWatchExpression('console');
|
||||
let watchExpressions = model.getWatchExpressions();
|
||||
assertWatchExpressions(watchExpressions, 'console');
|
||||
|
||||
model.renameWatchExpression(watchExpressions[0].getId(), 'new_name');
|
||||
model.renameWatchExpression(watchExpressions[1].getId(), 'new_name');
|
||||
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
|
||||
|
||||
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
|
||||
|
||||
model.addWatchExpression('mockExpression');
|
||||
model.moveWatchExpression(model.getWatchExpressions()[2].getId(), 1);
|
||||
watchExpressions = model.getWatchExpressions();
|
||||
assert.equal(watchExpressions[0].name, 'new_name');
|
||||
assert.equal(watchExpressions[1].name, 'mockExpression');
|
||||
assert.equal(watchExpressions[2].name, 'new_name');
|
||||
|
||||
model.removeWatchExpressions();
|
||||
assert.equal(model.getWatchExpressions().length, 0);
|
||||
});
|
||||
});
|
||||
@@ -3,19 +3,29 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
|
||||
import { AdapterEndEvent, IBreakpoint, IBreakpointData, IBreakpointsChangeEvent, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugger, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEvaluate, IExceptionBreakpoint, IExceptionInfo, IExpression, IFunctionBreakpoint, ILaunch, IRawModelUpdate, IReplElement, IReplElementSource, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
|
||||
const noopEvent = new Emitter<any>().event;
|
||||
|
||||
export class MockDebugService implements IDebugService {
|
||||
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
private readonly _model: IDebugModel;
|
||||
private readonly _viewModel: IViewModel;
|
||||
|
||||
constructor() {
|
||||
this._model = new MockDebugModel();
|
||||
this._viewModel = new MockDebugViewModel();
|
||||
}
|
||||
|
||||
public get state(): State {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
@@ -32,9 +42,7 @@ export class MockDebugService implements IDebugService {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
public get onDidChangeState(): Event<State> {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
public onDidChangeState: Event<State> = noopEvent;
|
||||
|
||||
public getConfigurationManager(): IConfigurationManager {
|
||||
throw new Error('not implemented');
|
||||
@@ -116,11 +124,11 @@ export class MockDebugService implements IDebugService {
|
||||
}
|
||||
|
||||
public getModel(): IDebugModel {
|
||||
throw new Error('not implemented');
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public getViewModel(): IViewModel {
|
||||
throw new Error('not implemented');
|
||||
return this._viewModel;
|
||||
}
|
||||
|
||||
public logToRepl(session: IDebugSession, value: string): void { }
|
||||
@@ -528,3 +536,97 @@ export class MockDebugAdapter extends AbstractDebugAdapter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockDebugModel implements IDebugModel {
|
||||
onDidChangeBreakpoints: Event<IBreakpointsChangeEvent | undefined> = noopEvent;
|
||||
|
||||
get onDidChangeCallStack(): Event<void> {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
get onDidChangeWatchExpressions(): Event<IExpression | undefined> {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
getSession(sessionId: string | undefined, includeInactive?: boolean | undefined): IDebugSession | undefined {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
getSessions(includeInactive?: boolean | undefined): IDebugSession[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getBreakpoints(filter?: { uri?: uri | undefined; lineNumber?: number | undefined; column?: number | undefined; enabledOnly?: boolean | undefined; } | undefined): readonly IBreakpoint[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
areBreakpointsActivated(): boolean {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
getFunctionBreakpoints(): readonly IFunctionBreakpoint[] {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
getDataBreakpoints(): readonly IDataBreakpoint[] {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
getExceptionBreakpoints(): readonly IExceptionBreakpoint[] {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
getWatchExpressions(): readonly (IExpression & IEvaluate)[] {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
class MockDebugViewModel implements IViewModel {
|
||||
get focusedSession(): IDebugSession | undefined {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
get focusedThread(): IThread | undefined {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
focusedStackFrame: IStackFrame | undefined = undefined;
|
||||
|
||||
get onDidFocusSession(): Event<IDebugSession | undefined> {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; }> = noopEvent;
|
||||
|
||||
get onDidSelectExpression(): Event<IExpression | undefined> {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
getSelectedExpression(): IExpression | undefined {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
setSelectedExpression(expression: IExpression | undefined): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
isMultiSessionView(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ export class OpenEditorsView extends ViewPane {
|
||||
const element = e.elements.length ? e.elements[0] : undefined;
|
||||
if (element instanceof OpenEditor) {
|
||||
const resource = element.getResource();
|
||||
this.dirtyEditorFocusedContext.set(element.editor.isDirty());
|
||||
this.dirtyEditorFocusedContext.set(element.editor.isDirty() && !element.editor.isSaving());
|
||||
this.readonlyEditorFocusedContext.set(element.editor.isReadonly());
|
||||
this.resourceContext.set(withUndefinedAsNull(resource));
|
||||
} else if (!!element) {
|
||||
@@ -419,7 +419,7 @@ export class OpenEditorsView extends ViewPane {
|
||||
private updateDirtyIndicator(workingCopy?: IWorkingCopy): void {
|
||||
if (workingCopy) {
|
||||
const gotDirty = workingCopy.isDirty();
|
||||
if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return; // do not indicate dirty of working copies that are auto saved after short delay
|
||||
}
|
||||
}
|
||||
@@ -586,7 +586,7 @@ class OpenEditorRenderer implements IListRenderer<OpenEditor, IOpenEditorTemplat
|
||||
|
||||
renderElement(editor: OpenEditor, _index: number, templateData: IOpenEditorTemplateData): void {
|
||||
templateData.actionRunner.editor = editor;
|
||||
editor.isDirty() ? dom.addClass(templateData.container, 'dirty') : dom.removeClass(templateData.container, 'dirty');
|
||||
editor.isDirty() && !editor.isSaving() ? dom.addClass(templateData.container, 'dirty') : dom.removeClass(templateData.container, 'dirty');
|
||||
templateData.root.setEditor(editor.editor, {
|
||||
italic: editor.isPreview(),
|
||||
extraClasses: ['open-editor'],
|
||||
|
||||
@@ -43,7 +43,7 @@ export class DirtyFilesIndicator extends Disposable implements IWorkbenchContrib
|
||||
|
||||
private onWorkingCopyDidChangeDirty(workingCopy: IWorkingCopy): void {
|
||||
const gotDirty = workingCopy.isDirty();
|
||||
if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return; // do not indicate dirty of working copies that are auto saved after short delay
|
||||
}
|
||||
|
||||
|
||||
@@ -235,15 +235,29 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) {
|
||||
return true; // always indicate dirty state if we are in conflict or error state
|
||||
return model.isDirty();
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
const model = this.textFileService.models.get(this.resource);
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.hasState(ModelState.SAVED) || model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) {
|
||||
return false; // require the model to be dirty and not in conflict or error state
|
||||
}
|
||||
|
||||
// Note: currently not checking for ModelState.PENDING_SAVE for a reason
|
||||
// because we currently miss an event for this state change on editors
|
||||
// and it could result in bad UX where an editor can be closed even though
|
||||
// it shows up as dirty and has not finished saving yet.
|
||||
|
||||
if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return false; // fast auto save enabled so we do not declare dirty
|
||||
return true; // a short auto save is configured, treat this as being saved
|
||||
}
|
||||
|
||||
return model.isDirty();
|
||||
return false;
|
||||
}
|
||||
|
||||
revert(options?: IRevertOptions): Promise<boolean> {
|
||||
|
||||
@@ -246,6 +246,10 @@ export class OpenEditor implements IEditorIdentifier {
|
||||
return this.editor.isDirty();
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
return this.editor.isSaving();
|
||||
}
|
||||
|
||||
getResource(): URI | undefined {
|
||||
return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
|
||||
export const forwardedPortsViewEnabled = new RawContextKey<boolean>('forwardedPortsViewEnabled', false);
|
||||
|
||||
@@ -608,17 +609,24 @@ namespace ForwardPortAction {
|
||||
return null;
|
||||
}
|
||||
|
||||
function error(notificationService: INotificationService, tunnel: RemoteTunnel | void, host: string, port: number) {
|
||||
if (!tunnel) {
|
||||
notificationService.error(nls.localize('remote.tunnel.forwardError', "Unable to forward {0}:{1}. The host may not be available.", host, port));
|
||||
}
|
||||
}
|
||||
|
||||
export function inlineHandler(): ICommandHandler {
|
||||
return async (accessor, arg) => {
|
||||
const remoteExplorerService = accessor.get(IRemoteExplorerService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
if (arg instanceof TunnelItem) {
|
||||
remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort });
|
||||
remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }).then(tunnel => error(notificationService, tunnel, arg.remoteHost, arg.remotePort));
|
||||
} else {
|
||||
remoteExplorerService.setEditable(undefined, {
|
||||
onFinish: (value, success) => {
|
||||
let parsed: { host: string, port: number } | undefined;
|
||||
if (success && (parsed = parseInput(value))) {
|
||||
remoteExplorerService.forward({ host: parsed.host, port: parsed.port });
|
||||
remoteExplorerService.forward({ host: parsed.host, port: parsed.port }).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port));
|
||||
}
|
||||
remoteExplorerService.setEditable(undefined, null);
|
||||
},
|
||||
@@ -632,6 +640,7 @@ namespace ForwardPortAction {
|
||||
export function commandPaletteHandler(): ICommandHandler {
|
||||
return async (accessor, arg) => {
|
||||
const remoteExplorerService = accessor.get(IRemoteExplorerService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
await viewsService.openView(TunnelPanel.ID, true);
|
||||
@@ -641,7 +650,7 @@ namespace ForwardPortAction {
|
||||
});
|
||||
let parsed: { host: string, port: number } | undefined;
|
||||
if (value && (parsed = parseInput(value))) {
|
||||
remoteExplorerService.forward({ host: parsed.host, port: parsed.port });
|
||||
remoteExplorerService.forward({ host: parsed.host, port: parsed.port }).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,6 +212,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}, {
|
||||
id: 'sync.enableKeybindings',
|
||||
label: localize('user keybindings', "User Keybindings")
|
||||
}, {
|
||||
id: 'sync.enableUIState',
|
||||
label: localize('ui state', "UI State")
|
||||
}, {
|
||||
id: 'sync.enableExtensions',
|
||||
label: localize('extensions', "Extensions")
|
||||
|
||||
@@ -270,7 +270,7 @@ export class ElectronWindow extends Disposable {
|
||||
if (isMacintosh) {
|
||||
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => {
|
||||
const gotDirty = workingCopy.isDirty();
|
||||
if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return; // do not indicate dirty of working copies that are auto saved after short delay
|
||||
}
|
||||
|
||||
|
||||
@@ -73,19 +73,19 @@ class TestEditorInput extends EditorInput implements IFileEditorInput {
|
||||
setFailToOpen(): void {
|
||||
this.fails = true;
|
||||
}
|
||||
save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
this.gotSaved = true;
|
||||
return Promise.resolve(true);
|
||||
return true;
|
||||
}
|
||||
saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
|
||||
this.gotSavedAs = true;
|
||||
return Promise.resolve(true);
|
||||
return true;
|
||||
}
|
||||
revert(options?: IRevertOptions): Promise<boolean> {
|
||||
async revert(options?: IRevertOptions): Promise<boolean> {
|
||||
this.gotReverted = true;
|
||||
this.gotSaved = false;
|
||||
this.gotSavedAs = false;
|
||||
return Promise.resolve(true);
|
||||
return true;
|
||||
}
|
||||
isDirty(): boolean {
|
||||
return this.dirty;
|
||||
@@ -383,12 +383,12 @@ suite('EditorService', () => {
|
||||
const ed = instantiationService.createInstance(MyEditor, 'my.editor');
|
||||
|
||||
const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'), undefined);
|
||||
const delegate = instantiationService.createInstance(DelegatingEditorService, (delegate, group, input) => {
|
||||
const delegate = instantiationService.createInstance(DelegatingEditorService, async (delegate, group, input) => {
|
||||
assert.strictEqual(input, inp);
|
||||
|
||||
done();
|
||||
|
||||
return Promise.resolve(ed);
|
||||
return ed;
|
||||
});
|
||||
|
||||
delegate.openEditor(inp);
|
||||
|
||||
@@ -473,7 +473,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
|
||||
// set the resolved authority
|
||||
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options);
|
||||
this._remoteExplorerService.addEnvironmentTunnels(resolvedAuthority.tunnelInformation?.environmentTunnels);
|
||||
this._remoteExplorerService.setTunnelInformation(resolvedAuthority.tunnelInformation);
|
||||
|
||||
// monitor for breakage
|
||||
const connection = this._remoteAgentService.getConnection();
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IEditableData } from 'vs/workbench/common/views';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TunnelInformation } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
|
||||
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
|
||||
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
|
||||
@@ -54,6 +55,7 @@ export function MakeAddress(host: string, port: number): string {
|
||||
export class TunnelModel extends Disposable {
|
||||
readonly forwarded: Map<string, Tunnel>;
|
||||
readonly detected: Map<string, Tunnel>;
|
||||
private _candidatesEnabled: boolean = true;
|
||||
private _onForwardPort: Emitter<Tunnel> = new Emitter();
|
||||
public onForwardPort: Event<Tunnel> = this._onForwardPort.event;
|
||||
private _onClosePort: Emitter<{ host: string, port: number }> = new Emitter();
|
||||
@@ -181,6 +183,10 @@ export class TunnelModel extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
set candidateEnabled(enabled: boolean) {
|
||||
this._candidatesEnabled = enabled;
|
||||
}
|
||||
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
|
||||
this._candidateFinder = finder;
|
||||
}
|
||||
@@ -190,6 +196,10 @@ export class TunnelModel extends Disposable {
|
||||
}
|
||||
|
||||
private async updateCandidates(): Promise<void> {
|
||||
if (!this._candidatesEnabled) {
|
||||
this._candidates = [];
|
||||
return;
|
||||
}
|
||||
if (this._candidateFinder) {
|
||||
this._candidates = await this._candidateFinder();
|
||||
}
|
||||
@@ -211,7 +221,7 @@ export interface IRemoteExplorerService {
|
||||
getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined;
|
||||
forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise<RemoteTunnel | void>;
|
||||
close(remote: { host: string, port: number }): Promise<void>;
|
||||
addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void;
|
||||
setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void;
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void;
|
||||
refresh(): Promise<void>;
|
||||
}
|
||||
@@ -258,10 +268,12 @@ class RemoteExplorerService implements IRemoteExplorerService {
|
||||
return this.tunnelModel.close(remote.host, remote.port);
|
||||
}
|
||||
|
||||
addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void {
|
||||
if (tunnels) {
|
||||
this.tunnelModel.addEnvironmentTunnels(tunnels);
|
||||
setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void {
|
||||
if (tunnelInformation && tunnelInformation.environmentTunnels) {
|
||||
this.tunnelModel.addEnvironmentTunnels(tunnelInformation.environmentTunnels);
|
||||
}
|
||||
|
||||
this.tunnelModel.candidateEnabled = tunnelInformation ? (tunnelInformation.hideCandidatePorts !== true) : true;
|
||||
}
|
||||
|
||||
setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Event } from 'vs/base/common/event';
|
||||
import { relative } from 'vs/base/common/path';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.search';
|
||||
export const PANEL_ID = 'workbench.view.search';
|
||||
export const PANEL_ID = 'workbench.panel.search';
|
||||
export const VIEW_ID = 'workbench.view.search';
|
||||
|
||||
export const ISearchService = createDecorator<ISearchService>('searchService');
|
||||
|
||||
@@ -29,7 +29,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
|
||||
export interface IBackupMetaData {
|
||||
@@ -82,7 +82,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
private readonly _onDidChangeDirty = this._register(new Emitter<void>());
|
||||
readonly onDidChangeDirty = this._onDidChangeDirty.event;
|
||||
|
||||
readonly capabilities = WorkingCopyCapabilities.AutoSave;
|
||||
readonly capabilities = 0;
|
||||
|
||||
private contentEncoding: string | undefined; // encoding as reported from disk
|
||||
|
||||
|
||||
@@ -13,10 +13,11 @@ import { TernarySearchTree } from 'vs/base/common/map';
|
||||
export const enum WorkingCopyCapabilities {
|
||||
|
||||
/**
|
||||
* Signals that the working copy participates
|
||||
* in auto saving as configured by the user.
|
||||
* Signals that the working copy requires
|
||||
* additional input when saving, e.g. an
|
||||
* associated path to save to.
|
||||
*/
|
||||
AutoSave = 1 << 1
|
||||
Untitled = 1 << 1
|
||||
}
|
||||
|
||||
export interface IWorkingCopy {
|
||||
|
||||
@@ -95,6 +95,8 @@ import { find } from 'vs/base/common/arrays';
|
||||
import { WorkingCopyService, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { MockDebugService } from 'vs/workbench/contrib/debug/test/common/mockDebug';
|
||||
|
||||
export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined);
|
||||
@@ -324,6 +326,7 @@ export function workbenchInstantiationService(): ITestInstantiationService {
|
||||
instantiationService.stub(ICodeEditorService, new TestCodeEditorService());
|
||||
instantiationService.stub(IViewletService, new TestViewletService());
|
||||
instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());
|
||||
instantiationService.stub(IDebugService, new MockDebugService());
|
||||
|
||||
return instantiationService;
|
||||
}
|
||||
|
||||
@@ -9978,10 +9978,10 @@ xterm-addon-webgl@0.5.0-beta.7:
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0-beta.7.tgz#b7b95a362e942ad6f86fa286d7b7bd8ee3e7cf67"
|
||||
integrity sha512-v6aCvhm1C6mvaurGwUYQfyhb2cAUyuVnzf3Ob/hy5ebtyzUj4wW0N9NbqDEJk67UeMi1lV2xZqrO5gNeTpVqFA==
|
||||
|
||||
xterm@4.4.0-beta.13:
|
||||
version "4.4.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.13.tgz#f7c5fa0d2b098ce0dd8b7c96d3d5fcaee22b86ed"
|
||||
integrity sha512-ZoDOVO3w84CXekBveGw1H2lcvM4HkJG5suXesE/3S+N4DnBhBcK/vw4kdooALGoorJV2GtgA1XEA6+m4N5Sgnw==
|
||||
xterm@4.4.0-beta.15:
|
||||
version "4.4.0-beta.15"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.15.tgz#5897bf79d29d1a2496ccd54665aded28c341b1cc"
|
||||
integrity sha512-Dvz1CMCYKeoxPF7uIDznbRgUA2Mct49Bq93K2nnrDU0pDMM3Sf1t9fkEyz59wxSx5XEHVdLS80jywsz4sjXBjQ==
|
||||
|
||||
y18n@^3.2.1:
|
||||
version "3.2.1"
|
||||
|
||||
Reference in New Issue
Block a user