Merge branch 'master' into notebook/outputs

This commit is contained in:
Johannes Rieken 2021-02-10 18:20:40 +01:00
commit b881f37fcc
136 changed files with 1668 additions and 5333 deletions

View file

@ -268,7 +268,6 @@
"**/vs/base/{common,node,electron-main}/**",
"**/vs/base/parts/*/{common,node,electron-main}/**",
"**/vs/platform/*/{common,node,electron-main}/**",
"**/vs/code/**",
"*" // node modules
]
},
@ -807,7 +806,6 @@
"**/vs/platform/**/{common,node}/**",
"**/vs/workbench/**/{common,node}/**",
"**/vs/server/**",
"**/vs/code/**/{common,node}/**",
"*" // node modules
]
},
@ -1009,6 +1007,7 @@
"end",
"expand",
"hide",
"invalidate",
"open",
"override",
"receive",

View file

@ -93,7 +93,7 @@
"addLabel": "z-author-verified",
"removeLabel": "author-verification-requested",
"requireLabel": "author-verification-requested",
"disallowLabel": "awaiting-insiders-release"
"disallowLabel": "unreleased"
},
{
"type": "comment",

View file

@ -1,4 +1,5 @@
Eric Amodio <eamodio@microsoft.com> Eric Amodio <eamodio@gmail.com>
Eric Amodio <eamodio@microsoft.com> Eric Amodio <eamodio@ioninteractive.com>
Daniel Imms <daimms@microsoft.com> Daniel Imms <tyriar@tyriar.com>
Tanha Kabir <tanha.kabir@microsoft.com> Tanha Kabir <tanhakabir.ca@gmail.com>
Raymond Zhao <raymondzhao@microsoft.com>

View file

@ -319,7 +319,7 @@
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features",
"vscode:prepublish": "npm run build-ext && npm run build-preview",
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json",
"build-preview": "webpack --mode production",
"build-preview": "npx webpack-cli --mode production",
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},

View file

@ -4,7 +4,7 @@
"license": "MIT",
"description": "Dependencies shared by all extensions",
"dependencies": {
"typescript": "^4.2.0-dev.20210201"
"typescript": "insiders"
},
"scripts": {
"postinstall": "node ./postinstall"

View file

@ -1,220 +1,2 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./preview-src/index.ts");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./preview-src/events.ts":
/*!*******************************!*\
!*** ./preview-src/events.ts ***!
\*******************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"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 });
exports.onceDocumentLoaded = void 0;
function onceDocumentLoaded(f) {
if (document.readyState === 'loading' || document.readyState === 'uninitialized') {
document.addEventListener('DOMContentLoaded', f);
}
else {
f();
}
}
exports.onceDocumentLoaded = onceDocumentLoaded;
/***/ }),
/***/ "./preview-src/index.ts":
/*!******************************!*\
!*** ./preview-src/index.ts ***!
\******************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"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 events_1 = __webpack_require__(/*! ./events */ "./preview-src/events.ts");
const vscode = acquireVsCodeApi();
function getSettings() {
const element = document.getElementById('simple-browser-settings');
if (element) {
const data = element.getAttribute('data-settings');
if (data) {
return JSON.parse(data);
}
}
throw new Error(`Could not load settings`);
}
const settings = getSettings();
const iframe = document.querySelector('iframe');
const header = document.querySelector('.header');
const input = header.querySelector('.url-input');
const forwardButton = header.querySelector('.forward-button');
const backButton = header.querySelector('.back-button');
const reloadButton = header.querySelector('.reload-button');
const openExternalButton = header.querySelector('.open-external-button');
window.addEventListener('message', e => {
switch (e.data.type) {
case 'focus':
{
iframe.focus();
break;
}
case 'didChangeFocusLockIndicatorEnabled':
{
toggleFocusLockIndicatorEnabled(e.data.enabled);
break;
}
}
});
events_1.onceDocumentLoaded(() => {
setInterval(() => {
var _a;
const iframeFocused = ((_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.tagName) === 'IFRAME';
document.body.classList.toggle('iframe-focused', iframeFocused);
}, 50);
iframe.addEventListener('load', () => {
// Noop
});
input.addEventListener('change', e => {
const url = e.target.value;
navigateTo(url);
});
forwardButton.addEventListener('click', () => {
history.forward();
});
backButton.addEventListener('click', () => {
history.back();
});
openExternalButton.addEventListener('click', () => {
vscode.postMessage({
type: 'openExternal',
url: input.value
});
});
reloadButton.addEventListener('click', () => {
// This does not seem to trigger what we want
// history.go(0);
// This incorrectly adds entries to the history but does reload
iframe.src = input.value;
});
navigateTo(settings.url);
input.value = settings.url;
toggleFocusLockIndicatorEnabled(settings.focusLockIndicatorEnabled);
function navigateTo(rawUrl) {
try {
const url = new URL(rawUrl);
// Try to bust the cache for the iframe
// There does not appear to be any way to reliably do this except modifying the url
url.searchParams.append('vscodeBrowserReqId', Date.now().toString());
iframe.src = url.toString();
}
catch (_a) {
iframe.src = rawUrl;
}
}
});
function toggleFocusLockIndicatorEnabled(enabled) {
document.body.classList.toggle('enable-focus-lock-indicator', enabled);
}
/***/ })
/******/ });
!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1),r=acquireVsCodeApi();const c=function(){const e=document.getElementById("simple-browser-settings");if(e){const t=e.getAttribute("data-settings");if(t)return JSON.parse(t)}throw new Error("Could not load settings")}(),a=document.querySelector("iframe"),u=document.querySelector(".header"),d=u.querySelector(".url-input"),i=u.querySelector(".forward-button"),s=u.querySelector(".back-button"),l=u.querySelector(".reload-button"),f=u.querySelector(".open-external-button");function y(e){document.body.classList.toggle("enable-focus-lock-indicator",e)}window.addEventListener("message",e=>{switch(e.data.type){case"focus":a.focus();break;case"didChangeFocusLockIndicatorEnabled":y(e.data.enabled)}}),o.onceDocumentLoaded(()=>{function e(e){try{const t=new URL(e);t.searchParams.append("vscodeBrowserReqId",Date.now().toString()),a.src=t.toString()}catch(t){a.src=e}}setInterval(()=>{var e;const t="IFRAME"===(null===(e=document.activeElement)||void 0===e?void 0:e.tagName);document.body.classList.toggle("iframe-focused",t)},50),a.addEventListener("load",()=>{}),d.addEventListener("change",t=>{e(t.target.value)}),i.addEventListener("click",()=>{history.forward()}),s.addEventListener("click",()=>{history.back()}),f.addEventListener("click",()=>{r.postMessage({type:"openExternal",url:d.value})}),l.addEventListener("click",()=>{a.src=d.value}),e(c.url),d.value=c.url,y(c.focusLockIndicatorEnabled)})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=void 0,t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}}]);
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View file

@ -54,8 +54,8 @@
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features",
"vscode:prepublish": "npm run build-ext && npm run build-preview",
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json",
"build-preview": "webpack --mode development",
"build-preview-production": "webpack --mode production",
"build-preview": "npx webpack-cli --mode development",
"build-preview-production": "npx webpack-cli --mode production",
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose",
"postinstall": "node ./build/copy"
@ -66,11 +66,7 @@
},
"devDependencies": {
"vscode-codicons": "^0.0.14",
"@types/node": "^12.11.7",
"ts-loader": "^6.2.1",
"typescript": "^3.7.3",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.0"
"@types/node": "^12.11.7"
},
"repository": {
"type": "git",

File diff suppressed because it is too large Load diff

View file

@ -3,410 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
interface IDisposable {
dispose(): void;
export function activate() {
// no-op. This extension may be removed in the future
}
const enum Constants {
ConfigSection = 'testing',
EnableCodeLensConfig = 'enableCodeLens',
EnableDiagnosticsConfig = 'enableProblemDiagnostics',
}
export function activate(context: vscode.ExtensionContext) {
const diagnostics = vscode.languages.createDiagnosticCollection();
const services = new TestingEditorServices(diagnostics);
context.subscriptions.push(
services,
diagnostics,
vscode.languages.registerCodeLensProvider({ scheme: 'file' }, services),
);
}
class TestingConfig implements IDisposable {
private section = vscode.workspace.getConfiguration(Constants.ConfigSection);
private readonly changeEmitter = new vscode.EventEmitter<void>();
private readonly listener = vscode.workspace.onDidChangeConfiguration(evt => {
if (evt.affectsConfiguration(Constants.ConfigSection)) {
this.section = vscode.workspace.getConfiguration(Constants.ConfigSection);
this.changeEmitter.fire();
}
});
public readonly onChange = this.changeEmitter.event;
public get codeLens() {
return this.section.get(Constants.EnableCodeLensConfig, true);
}
public get diagnostics() {
return this.section.get(Constants.EnableDiagnosticsConfig, false);
}
public get isEnabled() {
return this.codeLens || this.diagnostics;
}
public dispose() {
this.listener.dispose();
}
}
export class TestingEditorServices implements IDisposable, vscode.CodeLensProvider {
private readonly codeLensChangeEmitter = new vscode.EventEmitter<void>();
private readonly documents = new Map<string, DocumentTestObserver>();
private readonly config = new TestingConfig();
private disposables: IDisposable[];
private wasEnabled = this.config.isEnabled;
/**
* @inheritdoc
*/
public readonly onDidChangeCodeLenses = this.codeLensChangeEmitter.event;
constructor(private readonly diagnostics: vscode.DiagnosticCollection) {
this.disposables = [
new vscode.Disposable(() => this.expireAll()),
this.config,
vscode.window.onDidChangeVisibleTextEditors((editors) => {
if (!this.config.isEnabled) {
return;
}
const expiredEditors = new Set(this.documents.keys());
for (const editor of editors) {
const key = editor.document.uri.toString();
this.ensure(key, editor.document);
expiredEditors.delete(key);
}
for (const expired of expiredEditors) {
this.expire(expired);
}
}),
vscode.workspace.onDidCloseTextDocument((document) => {
this.expire(document.uri.toString());
}),
this.config.onChange(() => {
if (!this.wasEnabled || this.config.isEnabled) {
this.attachToAllVisible();
} else if (this.wasEnabled || !this.config.isEnabled) {
this.expireAll();
}
this.wasEnabled = this.config.isEnabled;
this.codeLensChangeEmitter.fire();
}),
];
if (this.config.isEnabled) {
this.attachToAllVisible();
}
}
/**
* @inheritdoc
*/
public provideCodeLenses(document: vscode.TextDocument) {
if (!this.config.codeLens) {
return [];
}
return this.documents.get(document.uri.toString())?.provideCodeLenses() ?? [];
}
/**
* Attach to all currently visible editors.
*/
private attachToAllVisible() {
for (const editor of vscode.window.visibleTextEditors) {
this.ensure(editor.document.uri.toString(), editor.document);
}
}
/**
* Unattaches to all tests.
*/
private expireAll() {
for (const observer of this.documents.values()) {
observer.dispose();
}
this.documents.clear();
}
/**
* Subscribes to tests for the document URI.
*/
private ensure(key: string, document: vscode.TextDocument) {
const state = this.documents.get(key);
if (!state) {
const observer = new DocumentTestObserver(document, this.diagnostics, this.config);
this.documents.set(key, observer);
observer.onDidChangeCodeLenses(() => this.config.codeLens && this.codeLensChangeEmitter.fire());
}
}
/**
* Expires and removes the watcher for the document.
*/
private expire(key: string) {
const observer = this.documents.get(key);
if (!observer) {
return;
}
observer.dispose();
this.documents.delete(key);
}
/**
* @override
*/
public dispose() {
this.disposables.forEach((d) => d.dispose());
}
}
class DocumentTestObserver implements IDisposable {
private readonly codeLensChangeEmitter = new vscode.EventEmitter<void>();
private readonly observer = vscode.test.createDocumentTestObserver(this.document);
private readonly disposables: IDisposable[];
public readonly onDidChangeCodeLenses = this.codeLensChangeEmitter.event;
private didHaveDiagnostics = this.config.diagnostics;
constructor(
private readonly document: vscode.TextDocument,
private readonly diagnostics: vscode.DiagnosticCollection,
private readonly config: TestingConfig,
) {
this.disposables = [
this.observer,
this.codeLensChangeEmitter,
config.onChange(() => {
if (this.didHaveDiagnostics && !config.diagnostics) {
this.diagnostics.set(document.uri, []);
} else if (!this.didHaveDiagnostics && config.diagnostics) {
this.updateDiagnostics();
}
this.didHaveDiagnostics = config.diagnostics;
}),
this.observer.onDidChangeTest(() => {
this.updateDiagnostics();
this.codeLensChangeEmitter.fire();
}),
];
}
private updateDiagnostics() {
if (!this.config.diagnostics) {
return;
}
const uriString = this.document.uri.toString();
const diagnostics: vscode.Diagnostic[] = [];
for (const test of iterateOverTests(this.observer.tests)) {
for (const message of test.state.messages) {
if (message.location?.uri.toString() === uriString) {
diagnostics.push({
range: message.location.range,
message: message.message.toString(),
severity: testToDiagnosticSeverity(message.severity),
});
}
}
}
this.diagnostics.set(this.document.uri, diagnostics);
}
public provideCodeLenses(): vscode.CodeLens[] {
const lenses: vscode.CodeLens[] = [];
for (const test of iterateOverTests(this.observer.tests)) {
const { debuggable = false, runnable = true } = test;
if (!test.location || !(debuggable || runnable)) {
continue;
}
const summary = summarize(test);
lenses.push({
isResolved: true,
range: test.location.range,
command: {
title: `$(${testStateToIcon[summary.computedState]}) ${getLabelFor(test, summary)}`,
command: 'vscode.runTests',
arguments: [[test]],
tooltip: localize('tooltip.debug', 'Debug {0}', test.label),
},
});
if (debuggable) {
lenses.push({
isResolved: true,
range: test.location.range,
command: {
title: localize('action.debug', 'Debug'),
command: 'vscode.debugTests',
arguments: [[test]],
tooltip: localize('tooltip.debug', 'Debug {0}', test.label),
},
});
}
}
return lenses;
}
/**
* @override
*/
public dispose() {
this.diagnostics.set(this.document.uri, []);
this.disposables.forEach(d => d.dispose());
}
}
function getLabelFor(test: vscode.TestItem, summary: ITestSummary) {
if (summary.duration !== undefined) {
return localize(
'tooltip.runStateWithDuration',
'{0}/{1} Tests Passed in {2}',
summary.passed,
summary.passed + summary.failed,
formatDuration(summary.duration),
);
}
if (summary.passed > 0 || summary.failed > 0) {
return localize('tooltip.runState', '{0}/{1} Tests Passed', summary.passed, summary.failed);
}
if (test.state.runState === vscode.TestRunState.Passed) {
return test.state.duration !== undefined
? localize('state.passedWithDuration', 'Passed in {0}', formatDuration(test.state.duration))
: localize('state.passed', 'Passed');
}
if (isFailedState(test.state.runState)) {
return localize('state.failed', 'Failed');
}
return localize('action.run', 'Run Tests');
}
function formatDuration(duration: number) {
if (duration < 1_000) {
return `${Math.round(duration)}ms`;
}
if (duration < 100_000) {
return `${(duration / 1000).toPrecision(3)}s`;
}
return `${(duration / 1000 / 60).toPrecision(3)}m`;
}
const statePriority: { [K in vscode.TestRunState]: number } = {
[vscode.TestRunState.Running]: 6,
[vscode.TestRunState.Queued]: 5,
[vscode.TestRunState.Errored]: 4,
[vscode.TestRunState.Failed]: 3,
[vscode.TestRunState.Passed]: 2,
[vscode.TestRunState.Skipped]: 1,
[vscode.TestRunState.Unset]: 0,
};
const maxPriority = (a: vscode.TestRunState, b: vscode.TestRunState) =>
statePriority[a] > statePriority[b] ? a : b;
const isFailedState = (s: vscode.TestRunState) =>
s === vscode.TestRunState.Failed || s === vscode.TestRunState.Errored;
interface ITestSummary {
passed: number;
failed: number;
duration: number | undefined;
computedState: vscode.TestRunState;
}
function summarize(test: vscode.TestItem) {
let passed = 0;
let failed = 0;
let duration: number | undefined;
let computedState = test.state.runState;
const queue = test.children ? [test.children] : [];
while (queue.length) {
for (const test of queue.pop()!) {
computedState = maxPriority(computedState, test.state.runState);
if (test.state.runState === vscode.TestRunState.Passed) {
passed++;
if (test.state.duration !== undefined) {
duration = test.state.duration + (duration ?? 0);
}
} else if (isFailedState(test.state.runState)) {
failed++;
if (test.state.duration !== undefined) {
duration = test.state.duration + (duration ?? 0);
}
}
if (test.children) {
queue.push(test.children);
}
}
}
return { passed, failed, duration, computedState };
}
function* iterateOverTests(tests: ReadonlyArray<vscode.TestItem>) {
const queue = [tests];
while (queue.length) {
for (const test of queue.pop()!) {
yield test;
if (test.children) {
queue.push(test.children);
}
}
}
}
const testStateToIcon: { [K in vscode.TestRunState]: string } = {
[vscode.TestRunState.Errored]: 'testing-error-icon',
[vscode.TestRunState.Failed]: 'testing-failed-icon',
[vscode.TestRunState.Passed]: 'testing-passed-icon',
[vscode.TestRunState.Queued]: 'testing-queued-icon',
[vscode.TestRunState.Skipped]: 'testing-skipped-icon',
[vscode.TestRunState.Unset]: 'beaker',
[vscode.TestRunState.Running]: 'loading~spin',
};
const testToDiagnosticSeverity = (severity: vscode.TestMessageSeverity | undefined) => {
switch (severity) {
case vscode.TestMessageSeverity.Hint:
return vscode.DiagnosticSeverity.Hint;
case vscode.TestMessageSeverity.Information:
return vscode.DiagnosticSeverity.Information;
case vscode.TestMessageSeverity.Warning:
return vscode.DiagnosticSeverity.Warning;
case vscode.TestMessageSeverity.Error:
default:
return vscode.DiagnosticSeverity.Error;
}
};

View file

@ -15,7 +15,9 @@ import * as languageModeIds from '../utils/languageModeIds';
const jsTsLanguageConfiguration: vscode.LanguageConfiguration = {
indentationRules: {
decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]].*$/,
increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/
increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/,
// e.g. * ...| or */| or *-----*/|
unIndentedLinePattern: /^(\t|[ ])*[ ]\*[^/]*\*\/\s*$|^(\t|[ ])*[ ]\*\/\s*$|^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/
},
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
onEnterRules: [

View file

@ -67,4 +67,47 @@ suite.skip('OnEnter', () => {
` x`));
});
});
test('should not indent after a multi-line comment block 1', () => {
return withRandomFileEditor(`/*-----\n * line 1\n * line 2\n *-----*/\n${CURSOR}`, 'js', async (_editor, document) => {
await type(document, '\nx');
assert.strictEqual(
document.getText(),
joinLines(
`/*-----`,
` * line 1`,
` * line 2`,
` *-----*/`,
``,
`x`));
});
});
test('should not indent after a multi-line comment block 2', () => {
return withRandomFileEditor(`/*-----\n * line 1\n * line 2\n */\n${CURSOR}`, 'js', async (_editor, document) => {
await type(document, '\nx');
assert.strictEqual(
document.getText(),
joinLines(
`/*-----`,
` * line 1`,
` * line 2`,
` */`,
``,
`x`));
});
});
test('should indent within a multi-line comment block', () => {
return withRandomFileEditor(`/*-----\n * line 1\n * line 2${CURSOR}`, 'js', async (_editor, document) => {
await type(document, '\nx');
assert.strictEqual(
document.getText(),
joinLines(
`/*-----`,
` * line 1`,
` * line 2`,
` * x`));
});
});
});

View file

@ -19,10 +19,10 @@ fast-plist@0.1.2:
resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8"
integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg=
typescript@^4.2.0-dev.20210201:
version "4.2.0-dev.20210201"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.0-dev.20210201.tgz#3d8ae7214cd4b23d3ae400f84d1afe5679f3e2f0"
integrity sha512-By8G30ZYs+b/8084cSnjP7ILd8ExUBC4Qi9FY2iRlBApHu/A08ExLlCRnYkHN1PgxcNs4rTaozJsgXHJ6cg92g==
typescript@insiders:
version "4.2.0-insiders.20210210"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.0-insiders.20210210.tgz#f109217dec41af7ef0fece60a91e5c3cce572d25"
integrity sha512-PBBQ/WNJ/HG+cH2nu+sPFbUPqv8FMraVihOfwlBzoa+0iGHa1I8gueJTUyBIOPU24tClLwKoU8dfscpn0rnuWw==
vscode-grammar-updater@^1.0.3:
version "1.0.3"

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.54.0",
"distro": "1bc28dd86603be17b5bf6ffa9370864f58e0ef77",
"distro": "f542aeaaf397d5c71e480442fe772dbe362726db",
"author": {
"name": "Microsoft Corporation"
},
@ -195,7 +195,7 @@
"underscore": "^1.8.2",
"vinyl": "^2.0.0",
"vinyl-fs": "^3.0.0",
"vscode-debugprotocol": "1.44.0",
"vscode-debugprotocol": "1.45.0-pre.0",
"vscode-nls-dev": "^3.3.1",
"vscode-telemetry-extractor": "^1.6.0",
"webpack": "^4.43.0",
@ -222,4 +222,4 @@
"elliptic": "^6.5.3",
"nwmatcher": "^1.4.4"
}
}
}

View file

@ -12,6 +12,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Event, Emitter } from 'vs/base/common/event';
import { getElementsByTagName, EventHelper, createStyleSheet, addDisposableListener, append, $ } from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
import { Delayer } from 'vs/base/common/async';
let DEBUG = false;
// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this
@ -87,6 +88,7 @@ export class Sash extends Disposable {
private hidden: boolean;
private orientation!: Orientation;
private size: number;
private hoverDelayer = this._register(new Delayer(300));
private _state: SashState = SashState.Enabled;
get state(): SashState { return this._state; }
@ -136,10 +138,10 @@ export class Sash extends Disposable {
if (state !== SashState.Disabled) {
this._orthogonalStartDragHandle = append(this.el, $('.orthogonal-drag-handle.start'));
this.orthogonalStartDragHandleDisposables.add(toDisposable(() => this._orthogonalStartDragHandle!.remove()));
domEvent(this._orthogonalStartDragHandle, 'mouseenter', false)
(() => sash.el.classList.add('hover'), undefined, this.orthogonalStartDragHandleDisposables);
domEvent(this._orthogonalStartDragHandle, 'mouseleave', false)
(() => sash.el.classList.remove('hover'), undefined, this.orthogonalStartDragHandleDisposables);
domEvent(this._orthogonalStartDragHandle, 'mouseenter')
(() => Sash.onMouseEnter(sash), undefined, this.orthogonalStartDragHandleDisposables);
domEvent(this._orthogonalStartDragHandle, 'mouseleave')
(() => Sash.onMouseLeave(sash), undefined, this.orthogonalStartDragHandleDisposables);
}
};
@ -166,10 +168,10 @@ export class Sash extends Disposable {
if (state !== SashState.Disabled) {
this._orthogonalEndDragHandle = append(this.el, $('.orthogonal-drag-handle.end'));
this.orthogonalEndDragHandleDisposables.add(toDisposable(() => this._orthogonalEndDragHandle!.remove()));
domEvent(this._orthogonalEndDragHandle, 'mouseenter', false)
(() => sash.el.classList.add('hover'), undefined, this.orthogonalEndDragHandleDisposables);
domEvent(this._orthogonalEndDragHandle, 'mouseleave', false)
(() => sash.el.classList.remove('hover'), undefined, this.orthogonalEndDragHandleDisposables);
domEvent(this._orthogonalEndDragHandle, 'mouseenter')
(() => Sash.onMouseEnter(sash), undefined, this.orthogonalEndDragHandleDisposables);
domEvent(this._orthogonalEndDragHandle, 'mouseleave')
(() => Sash.onMouseLeave(sash), undefined, this.orthogonalEndDragHandleDisposables);
}
};
@ -197,6 +199,8 @@ export class Sash extends Disposable {
this._register(domEvent(this.el, 'mousedown')(this.onMouseDown, this));
this._register(domEvent(this.el, 'dblclick')(this.onMouseDoubleClick, this));
this._register(domEvent(this.el, 'mouseenter')(() => Sash.onMouseEnter(this)));
this._register(domEvent(this.el, 'mouseleave')(() => Sash.onMouseLeave(this)));
this._register(Gesture.addTarget(this.el));
this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this));
@ -394,6 +398,15 @@ export class Sash extends Disposable {
}));
}
private static onMouseEnter(sash: Sash): void {
sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'));
}
private static onMouseLeave(sash: Sash): void {
sash.hoverDelayer.cancel();
sash.el.classList.remove('hover');
}
layout(): void {
if (this.orientation === Orientation.VERTICAL) {
const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);

View file

@ -69,7 +69,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
this.model.updateElementHeight(element, height);
}
resort(element: T, recursive = true): void {
resort(element: T | null, recursive = true): void {
this.model.resort(element, recursive);
}

View file

@ -16,6 +16,13 @@ export interface ReadableStreamEvents<T> {
/**
* The 'data' event is emitted whenever the stream is
* relinquishing ownership of a chunk of data to a consumer.
*
* NOTE: PLEASE UNDERSTAND THAT ADDING A DATA LISTENER CAN
* TURN THE STREAM INTO FLOWING MODE. IT IS THEREFOR THE
* LAST LISTENER THAT SHOULD BE ADDED AND NOT THE FIRST
*
* Use `listenStream` as a helper method to listen to
* stream events in the right order.
*/
on(event: 'data', callback: (data: T) => void): void;
@ -268,7 +275,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
// end with data or error if provided
if (result instanceof Error) {
this.error(result);
} else if (result) {
} else if (typeof result !== 'undefined') {
this.write(result);
}
@ -489,18 +496,74 @@ export function peekReadable<T>(readable: Readable<T>, reducer: IReducer<T>, max
}
/**
* Helper to fully read a T stream into a T.
* Helper to fully read a T stream into a T or consuming
* a stream fully, awaiting all the events without caring
* about the data.
*/
export function consumeStream<T>(stream: ReadableStreamEvents<T>, reducer: IReducer<T>): Promise<T> {
export function consumeStream<T>(stream: ReadableStreamEvents<T>, reducer: IReducer<T>): Promise<T>;
export function consumeStream(stream: ReadableStreamEvents<unknown>): Promise<undefined>;
export function consumeStream<T>(stream: ReadableStreamEvents<T>, reducer?: IReducer<T>): Promise<T | undefined> {
return new Promise((resolve, reject) => {
const chunks: T[] = [];
stream.on('data', data => chunks.push(data));
stream.on('error', error => reject(error));
stream.on('end', () => resolve(reducer(chunks)));
listenStream(stream, {
onData: chunk => {
if (reducer) {
chunks.push(chunk);
}
},
onError: error => {
if (reducer) {
reject(error);
} else {
resolve(undefined);
}
},
onEnd: () => {
if (reducer) {
resolve(reducer(chunks));
} else {
resolve(undefined);
}
}
});
});
}
export interface IStreamListener<T> {
/**
* The 'data' event is emitted whenever the stream is
* relinquishing ownership of a chunk of data to a consumer.
*/
onData(data: T): void;
/**
* Emitted when any error occurs.
*/
onError(err: Error): void;
/**
* The 'end' event is emitted when there is no more data
* to be consumed from the stream. The 'end' event will
* not be emitted unless the data is completely consumed.
*/
onEnd(): void;
}
/**
* Helper to listen to all events of a T stream in proper order.
*/
export function listenStream<T>(stream: ReadableStreamEvents<T>, listener: IStreamListener<T>): void {
stream.on('error', error => listener.onError(error));
stream.on('end', () => listener.onEnd());
// Adding the `data` listener will turn the stream
// into flowing mode. As such it is important to
// add this listener last (DO NOT CHANGE!)
stream.on('data', data => listener.onData(data));
}
/**
* Helper to peek up to `maxChunks` into a stream. The return type signals if
* the stream has ended or not. If not, caller needs to add a `data` listener
@ -509,9 +572,9 @@ export function consumeStream<T>(stream: ReadableStreamEvents<T>, reducer: IRedu
export function peekStream<T>(stream: ReadableStream<T>, maxChunks: number): Promise<ReadableBufferedStream<T>> {
return new Promise((resolve, reject) => {
const streamListeners = new DisposableStore();
const buffer: T[] = [];
// Data Listener
const buffer: T[] = [];
const dataListener = (chunk: T) => {
// Add to buffer
@ -529,23 +592,27 @@ export function peekStream<T>(stream: ReadableStream<T>, maxChunks: number): Pro
}
};
streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener)));
stream.on('data', dataListener);
// Error Listener
const errorListener = (error: Error) => {
return reject(error);
};
streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener)));
stream.on('error', errorListener);
// End Listener
const endListener = () => {
return resolve({ stream, buffer, ended: true });
};
streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener)));
stream.on('error', errorListener);
streamListeners.add(toDisposable(() => stream.removeListener('end', endListener)));
stream.on('end', endListener);
// Important: leave the `data` listener last because
// this can turn the stream into flowing mode and we
// want `error` events to be received as well.
streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener)));
stream.on('data', dataListener);
});
}
@ -585,46 +652,11 @@ export function toReadable<T>(t: T): Readable<T> {
export function transform<Original, Transformed>(stream: ReadableStreamEvents<Original>, transformer: ITransformer<Original, Transformed>, reducer: IReducer<Transformed>): ReadableStream<Transformed> {
const target = newWriteableStream<Transformed>(reducer);
stream.on('data', data => target.write(transformer.data(data)));
stream.on('end', () => target.end());
stream.on('error', error => target.error(transformer.error ? transformer.error(error) : error));
listenStream(stream, {
onData: data => target.write(transformer.data(data)),
onError: error => target.error(transformer.error ? transformer.error(error) : error),
onEnd: () => target.end()
});
return target;
}
export interface IReadableStreamObservable {
/**
* A promise to await the `end` or `error` event
* of a stream.
*/
errorOrEnd: () => Promise<void>;
}
/**
* Helper to observe a stream for certain events through
* a promise based API.
*/
export function observe(stream: ReadableStream<unknown>): IReadableStreamObservable {
// A stream is closed when it ended or errord
// We install this listener right from the
// beginning to catch the events early.
const errorOrEnd = Promise.race([
new Promise<void>(resolve => stream.on('end', () => resolve())),
new Promise<void>(resolve => stream.on('error', () => resolve()))
]);
return {
errorOrEnd(): Promise<void> {
// We need to ensure the stream is flowing so that our
// listeners are getting triggered. It is possible that
// the stream is not flowing because no `data` listener
// was attached yet.
stream.resume();
return errorOrEnd;
}
};
}

View file

@ -1023,146 +1023,141 @@ export class StaticRouter<TContext = string> implements IClientRouter<TContext>
}
}
//#region createChannelReceiver / createChannelSender
/**
* Use both `createChannelReceiver` and `createChannelSender`
* for automated process <=> process communication over methods
* and events. You do not need to spell out each method on both
* sides, a proxy will take care of this.
* Use ProxyChannels to automatically wrapping and unwrapping
* services to/from IPC channels, instead of manually wrapping
* each service method and event.
*
* Rules:
* - if marshalling is enabled, only `URI` and `RegExp` is converted
* Restrictions:
* - If marshalling is enabled, only `URI` and `RegExp` is converted
* automatically for you
* - events must follow the naming convention `onUppercase`
* - Events must follow the naming convention `onUpperCase`
* - `CancellationToken` is currently not supported
* - if a context is provided, you can use `AddFirstParameterToFunctions`
* - If a context is provided, you can use `AddFirstParameterToFunctions`
* utility to signal this in the receiving side type
*/
export namespace ProxyChannel {
export interface IBaseChannelOptions {
export interface IProxyOptions {
/**
* Disables automatic marshalling of `URI`.
* If marshalling is disabled, `UriComponents`
* must be used instead.
*/
disableMarshalling?: boolean;
}
export interface IChannelReceiverOptions extends IBaseChannelOptions { }
export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel {
const handler = service as { [key: string]: unknown };
const disableMarshalling = options && options.disableMarshalling;
// Buffer any event that should be supported by
// iterating over all property keys and finding them
const mapEventNameToEvent = new Map<string, Event<unknown>>();
for (const key in handler) {
if (propertyIsEvent(key)) {
mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event<unknown>, true));
}
/**
* Disables automatic marshalling of `URI`.
* If marshalling is disabled, `UriComponents`
* must be used instead.
*/
disableMarshalling?: boolean;
}
return new class implements IServerChannel {
export interface ICreateServiceChannelOptions extends IProxyOptions { }
listen<T>(_: unknown, event: string): Event<T> {
const eventImpl = mapEventNameToEvent.get(event);
if (eventImpl) {
return eventImpl as Event<T>;
export function fromService(service: unknown, options?: ICreateServiceChannelOptions): IServerChannel {
const handler = service as { [key: string]: unknown };
const disableMarshalling = options && options.disableMarshalling;
// Buffer any event that should be supported by
// iterating over all property keys and finding them
const mapEventNameToEvent = new Map<string, Event<unknown>>();
for (const key in handler) {
if (propertyIsEvent(key)) {
mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event<unknown>, true));
}
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, args?: any[]): Promise<any> {
const target = handler[command];
if (typeof target === 'function') {
return new class implements IServerChannel {
// Revive unless marshalling disabled
if (!disableMarshalling && Array.isArray(args)) {
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i]);
}
listen<T>(_: unknown, event: string): Event<T> {
const eventImpl = mapEventNameToEvent.get(event);
if (eventImpl) {
return eventImpl as Event<T>;
}
return target.apply(handler, args);
throw new Error(`Event not found: ${event}`);
}
throw new Error(`Method not found: ${command}`);
}
};
}
export interface IChannelSenderOptions extends IBaseChannelOptions {
/**
* If provided, will add the value of `context`
* to each method call to the target.
*/
context?: unknown;
/**
* If provided, will not proxy any of the properties
* that are part of the Map but rather return that value.
*/
properties?: Map<string, unknown>;
}
export function createChannelSender<T>(channel: IChannel, options?: IChannelSenderOptions): T {
const disableMarshalling = options && options.disableMarshalling;
return new Proxy({}, {
get(_target: T, propKey: PropertyKey) {
if (typeof propKey === 'string') {
// Check for predefined values
if (options?.properties?.has(propKey)) {
return options.properties.get(propKey);
}
// Event
if (propertyIsEvent(propKey)) {
return channel.listen(propKey);
}
// Function
return async function (...args: any[]) {
// Add context if any
let methodArgs: any[];
if (options && !isUndefinedOrNull(options.context)) {
methodArgs = [options.context, ...args];
} else {
methodArgs = args;
}
const result = await channel.call(propKey, methodArgs);
call(_: unknown, command: string, args?: any[]): Promise<any> {
const target = handler[command];
if (typeof target === 'function') {
// Revive unless marshalling disabled
if (!disableMarshalling) {
return revive(result);
if (!disableMarshalling && Array.isArray(args)) {
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i]);
}
}
return result;
};
return target.apply(handler, args);
}
throw new Error(`Method not found: ${command}`);
}
};
}
throw new Error(`Property not found: ${String(propKey)}`);
}
}) as T;
export interface ICreateProxyServiceOptions extends IProxyOptions {
/**
* If provided, will add the value of `context`
* to each method call to the target.
*/
context?: unknown;
/**
* If provided, will not proxy any of the properties
* that are part of the Map but rather return that value.
*/
properties?: Map<string, unknown>;
}
export function toService<T>(channel: IChannel, options?: ICreateProxyServiceOptions): T {
const disableMarshalling = options && options.disableMarshalling;
return new Proxy({}, {
get(_target: T, propKey: PropertyKey) {
if (typeof propKey === 'string') {
// Check for predefined values
if (options?.properties?.has(propKey)) {
return options.properties.get(propKey);
}
// Event
if (propertyIsEvent(propKey)) {
return channel.listen(propKey);
}
// Function
return async function (...args: any[]) {
// Add context if any
let methodArgs: any[];
if (options && !isUndefinedOrNull(options.context)) {
methodArgs = [options.context, ...args];
} else {
methodArgs = args;
}
const result = await channel.call(propKey, methodArgs);
// Revive unless marshalling disabled
if (!disableMarshalling) {
return revive(result);
}
return result;
};
}
throw new Error(`Property not found: ${String(propKey)}`);
}
}) as T;
}
function propertyIsEvent(name: string): boolean {
// Assume a property is an event if it has a form of "onSomething"
return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2));
}
}
function propertyIsEvent(name: string): boolean {
// Assume a property is an event if it has a form of "onSomething"
return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2));
}
//#endregion
const colorTables = [
['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'],
['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD']

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { ipcRenderer } from 'electron';
import { Event } from 'vs/base/common/event';
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { Protocol as MessagePortProtocol } from 'vs/base/parts/ipc/common/ipc.mp';
@ -39,6 +39,9 @@ export class Server extends IPCServer {
};
// Send one port back to the requestor
// Note: we intentionally use `electron` APIs here because
// transferables like the `MessagePort` cannot be transfered
// over preload scripts when `contextIsolation: true`
ipcRenderer.postMessage('vscode:createMessageChannelResult', nonce, [outgoingPort]);
return result;

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Emitter, Event } from 'vs/base/common/event';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
@ -332,10 +332,10 @@ suite('Base IPC', function () {
const testServer = new TestIPCServer();
server = testServer;
server.registerChannel(TestChannelId, createChannelReceiver(service));
server.registerChannel(TestChannelId, ProxyChannel.fromService(service));
client = testServer.createConnection('client1');
ipcService = createChannelSender(client.getChannel(TestChannelId));
ipcService = ProxyChannel.toService(client.getChannel(TestChannelId));
});
teardown(function () {
@ -398,10 +398,10 @@ suite('Base IPC', function () {
const testServer = new TestIPCServer();
server = testServer;
server.registerChannel(TestChannelId, createChannelReceiver(service));
server.registerChannel(TestChannelId, ProxyChannel.fromService(service));
client = testServer.createConnection('client1');
ipcService = createChannelSender(client.getChannel(TestChannelId), { context: 'Super Context' });
ipcService = ProxyChannel.toService(client.getChannel(TestChannelId), { context: 'Super Context' });
});
teardown(function () {

View file

@ -35,17 +35,6 @@
}
},
/**
* @param {string} channel
* @param {any} message
* @param {MessagePort[]} transfer
*/
postMessage(channel, message, transfer) {
if (validateIPC(channel)) {
ipcRenderer.postMessage(channel, message, transfer);
}
},
/**
* @param {string} channel
* @param {any[]} args
@ -88,6 +77,33 @@
}
},
ipcMessagePort: {
/**
* @param {string} channelRequest
* @param {string} channelResponse
* @param {string} requestNonce
*/
connect(channelRequest, channelResponse, requestNonce) {
if (validateIPC(channelRequest) && validateIPC(channelResponse)) {
const responseListener = (/** @type {import('electron').IpcRendererEvent} */ e, /** @type {string} */ responseNonce) => {
// validate that the nonce from the response is the same
// as when requested. and if so, use `postMessage` to
// send the `MessagePort` safely over, even when context
// isolation is enabled
if (requestNonce === responseNonce) {
ipcRenderer.off(channelResponse, responseListener);
window.postMessage(requestNonce, '*', e.ports);
}
};
// request message port from main and await result
ipcRenderer.on(channelResponse, responseListener);
ipcRenderer.send(channelRequest, requestNonce);
}
}
},
/**
* Support for subset of methods of Electron's `webFrame` type.
*/

View file

@ -15,10 +15,12 @@ export interface IpcRendererEvent extends Event {
// Docs: https://electronjs.org/docs/api/structures/ipc-renderer-event
/**
* A list of MessagePorts that were transferred with this message
*/
ports: MessagePort[];
// Note: API with `Transferable` intentionally commented out because you
// cannot transfer these when `contextIsolation: true`.
// /**
// * A list of MessagePorts that were transferred with this message
// */
// ports: MessagePort[];
/**
* The `IpcRenderer` instance that emitted the event originally
*/
@ -93,20 +95,23 @@ export interface IpcRenderer {
* If you do not need a response to the message, consider using `ipcRenderer.send`.
*/
invoke(channel: string, ...args: any[]): Promise<any>;
/**
* Send a message to the main process, optionally transferring ownership of zero or
* more `MessagePort` objects.
*
* The transferred `MessagePort` objects will be available in the main process as
* `MessagePortMain` objects by accessing the `ports` property of the emitted
* event.
*
* For example:
*
* For more information on using `MessagePort` and `MessageChannel`, see the MDN
* documentation.
*/
postMessage(channel: string, message: any, transfer?: MessagePort[]): void;
// Note: API with `Transferable` intentionally commented out because you
// cannot transfer these when `contextIsolation: true`.
// /**
// * Send a message to the main process, optionally transferring ownership of zero or
// * more `MessagePort` objects.
// *
// * The transferred `MessagePort` objects will be available in the main process as
// * `MessagePortMain` objects by accessing the `ports` property of the emitted
// * event.
// *
// * For example:
// *
// * For more information on using `MessagePort` and `MessageChannel`, see the MDN
// * documentation.
// */
// postMessage(channel: string, message: any): void;
}
export interface WebFrame {

View file

@ -94,7 +94,23 @@ export interface ISandboxContext {
sandbox: boolean;
}
export interface IpcMessagePort {
/**
* Establish a connection via `MessagePort` to a target. The main process
* will need to transfer the port over to the `channelResponse` after listening
* to `channelRequest` with a payload of `requestNonce` so that the
* source can correlate the response.
*
* The source should install a `window.on('message')` listener, ensuring `e.data`
* matches `requestNonce`, `e.source` matches `window` and then receiving the
* `MessagePort` via `e.ports[0]`.
*/
connect(channelRequest: string, channelResponse: string, requestNonce: string): void;
}
export const ipcRenderer: IpcRenderer = globals.vscode.ipcRenderer;
export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort;
export const webFrame: WebFrame = globals.vscode.webFrame;
export const crashReporter: CrashReporter = globals.vscode.crashReporter;
export const process: ISandboxNodeProcess = globals.vscode.process;

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream, observe } from 'vs/base/common/stream';
import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream, listenStream } from 'vs/base/common/stream';
import { timeout } from 'vs/base/common/async';
suite('Stream', () => {
@ -69,6 +69,7 @@ suite('Stream', () => {
stream.end('Final Bit');
assert.strictEqual(chunks.length, 4);
assert.strictEqual(chunks[3], 'Final Bit');
assert.strictEqual(end, true);
stream.destroy();
@ -76,6 +77,15 @@ suite('Stream', () => {
assert.strictEqual(chunks.length, 4);
});
test('WriteableStream - end with empty string works', async () => {
const reducer = (strings: string[]) => strings.length > 0 ? strings.join() : 'error';
const stream = newWriteableStream<string>(reducer);
stream.end('');
const result = await consumeStream(stream, reducer);
assert.strictEqual(result, '');
});
test('WriteableStream - removeListener', () => {
const stream = newWriteableStream<string>(strings => strings.join());
@ -270,6 +280,56 @@ suite('Stream', () => {
assert.strictEqual(consumed, '1,2,3,4,5');
});
test('consumeStream - without reducer', async () => {
const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
const consumed = await consumeStream(stream);
assert.strictEqual(consumed, undefined);
});
test('consumeStream - without reducer and error', async () => {
const stream = newWriteableStream<string>(strings => strings.join());
stream.error(new Error());
const consumed = await consumeStream(stream);
assert.strictEqual(consumed, undefined);
});
test('listenStream', () => {
const stream = newWriteableStream<string>(strings => strings.join());
let error = false;
let end = false;
let data = '';
listenStream(stream, {
onData: d => {
data = d;
},
onError: e => {
error = true;
},
onEnd: () => {
end = true;
}
});
stream.write('Hello');
assert.strictEqual(data, 'Hello');
stream.write('World');
assert.strictEqual(data, 'World');
assert.strictEqual(error, false);
assert.strictEqual(end, false);
stream.error(new Error());
assert.strictEqual(error, true);
stream.end('Final Bit');
assert.strictEqual(end, true);
});
test('peekStream', async () => {
for (let i = 0; i < 5; i++) {
const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
@ -335,30 +395,6 @@ suite('Stream', () => {
assert.strictEqual(consumed, '11,22,33,44,55');
});
test('observer', async () => {
const source1 = newWriteableStream<string>(strings => strings.join());
setTimeout(() => source1.error(new Error()));
await observe(source1).errorOrEnd();
const source2 = newWriteableStream<string>(strings => strings.join());
setTimeout(() => source2.end('Hello Test'));
await observe(source2).errorOrEnd();
const source3 = newWriteableStream<string>(strings => strings.join());
setTimeout(() => {
source3.write('Hello Test');
source3.error(new Error());
});
await observe(source3).errorOrEnd();
const source4 = newWriteableStream<string>(strings => strings.join());
setTimeout(() => {
source4.write('Hello Test');
source4.end();
});
await observe(source4).errorOrEnd();
});
test('events are delivered even if a listener is removed during delivery', () => {
const stream = newWriteableStream<string>(strings => strings.join());

View file

@ -7,8 +7,9 @@ import product from 'vs/platform/product/common/product';
import * as fs from 'fs';
import { release } from 'os';
import { gracefulify } from 'graceful-fs';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
import { StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
import { ipcRenderer } from 'electron';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp';
import { StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
@ -28,7 +29,6 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogger } from 'vs/platform/log/common/log';
import { LogLevelChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
@ -40,7 +40,8 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/co
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
import { IMainProcessService, MessagePortMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog';
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
@ -184,7 +185,7 @@ class SharedProcessMain extends Disposable {
services.set(IRequestService, new SyncDescriptor(RequestService));
// Native Host
const nativeHostService = createChannelSender<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
const nativeHostService = ProxyChannel.toService<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
services.set(INativeHostService, nativeHostService);
// Download
@ -270,12 +271,12 @@ class SharedProcessMain extends Disposable {
// Localizations
const localizationsService = accessor.get(ILocalizationsService);
const localizationsChannel = createChannelReceiver(localizationsService);
const localizationsChannel = ProxyChannel.fromService(localizationsService);
this.server.registerChannel('localizations', localizationsChannel);
// Diagnostics
const diagnosticsService = accessor.get(IDiagnosticsService);
const diagnosticsChannel = createChannelReceiver(diagnosticsService);
const diagnosticsChannel = ProxyChannel.fromService(diagnosticsService);
this.server.registerChannel('diagnostics', diagnosticsChannel);
// Extension Tips

View file

@ -10,14 +10,14 @@ import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } fro
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { resolveShellEnv } from 'vs/code/node/shellEnv';
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { getDelayedChannel, StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron';
import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcess';
import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
@ -525,7 +525,7 @@ export class CodeApplication extends Disposable {
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId]));
@ -606,7 +606,7 @@ export class CodeApplication extends Disposable {
// Register more Main IPC services
const launchMainService = accessor.get(ILaunchMainService);
const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
const launchChannel = ProxyChannel.fromService(launchMainService, { disableMarshalling: true });
this.mainIpcServer.registerChannel('launch', launchChannel);
// Register more Electron IPC services
@ -615,44 +615,44 @@ export class CodeApplication extends Disposable {
electronIpcServer.registerChannel('update', updateChannel);
const issueMainService = accessor.get(IIssueMainService);
const issueChannel = createChannelReceiver(issueMainService);
const issueChannel = ProxyChannel.fromService(issueMainService);
electronIpcServer.registerChannel('issue', issueChannel);
const encryptionMainService = accessor.get(IEncryptionMainService);
const encryptionChannel = createChannelReceiver(encryptionMainService);
const encryptionChannel = ProxyChannel.fromService(encryptionMainService);
electronIpcServer.registerChannel('encryption', encryptionChannel);
const keyboardLayoutMainService = accessor.get(IKeyboardLayoutMainService);
const keyboardLayoutChannel = createChannelReceiver(keyboardLayoutMainService);
const keyboardLayoutChannel = ProxyChannel.fromService(keyboardLayoutMainService);
electronIpcServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
const displayMainService = accessor.get(IDisplayMainService);
const displayChannel = createChannelReceiver(displayMainService);
const displayChannel = ProxyChannel.fromService(displayMainService);
electronIpcServer.registerChannel('display', displayChannel);
const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService);
const nativeHostChannel = createChannelReceiver(this.nativeHostMainService);
const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService);
electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel));
const workspacesService = accessor.get(IWorkspacesService);
const workspacesChannel = createChannelReceiver(workspacesService);
const workspacesChannel = ProxyChannel.fromService(workspacesService);
electronIpcServer.registerChannel('workspaces', workspacesChannel);
const menubarMainService = accessor.get(IMenubarMainService);
const menubarChannel = createChannelReceiver(menubarMainService);
const menubarChannel = ProxyChannel.fromService(menubarMainService);
electronIpcServer.registerChannel('menubar', menubarChannel);
const urlService = accessor.get(IURLService);
const urlChannel = createChannelReceiver(urlService);
const urlChannel = ProxyChannel.fromService(urlService);
electronIpcServer.registerChannel('url', urlChannel);
const extensionUrlTrustService = accessor.get(IExtensionUrlTrustService);
const extensionUrlTrustChannel = createChannelReceiver(extensionUrlTrustService);
const extensionUrlTrustChannel = ProxyChannel.fromService(extensionUrlTrustService);
electronIpcServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel);
const webviewManagerService = accessor.get(IWebviewManagerService);
const webviewChannel = createChannelReceiver(webviewManagerService);
const webviewChannel = ProxyChannel.fromService(webviewManagerService);
electronIpcServer.registerChannel('webview', webviewChannel);
const storageMainService = accessor.get(IStorageMainService);

View file

@ -12,7 +12,7 @@ import product from 'vs/platform/product/common/product';
import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Server as NodeIPCServer, serve as nodeIPCServe, connect as nodeIPCConnect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net';
import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net';
import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
@ -292,7 +292,7 @@ class CodeMain {
}, 10000);
}
const launchService = createChannelSender<ILaunchMainService>(client.getChannel('launch'), { disableMarshalling: true });
const launchService = ProxyChannel.toService<ILaunchMainService>(client.getChannel('launch'), { disableMarshalling: true });
// Process Info
if (args.status) {

View file

@ -38,6 +38,7 @@ import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRed
import { TextChange } from 'vs/editor/common/model/textChange';
import { Constants } from 'vs/base/common/uint';
import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
import { listenStream } from 'vs/base/common/stream';
function createTextBufferBuilder() {
return new PieceTreeTextBufferBuilder();
@ -64,33 +65,33 @@ export function createTextBufferFactoryFromStream(stream: ITextStream | VSBuffer
let done = false;
stream.on('data', (chunk: string | VSBuffer) => {
if (validator) {
const error = validator(chunk);
if (error) {
listenStream<string | VSBuffer>(stream, {
onData: chunk => {
if (validator) {
const error = validator(chunk);
if (error) {
done = true;
reject(error);
}
}
if (filter) {
chunk = filter(chunk);
}
builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString());
},
onError: error => {
if (!done) {
done = true;
reject(error);
}
}
if (filter) {
chunk = filter(chunk);
}
builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString());
});
stream.on('error', (error) => {
if (!done) {
done = true;
reject(error);
}
});
stream.on('end', () => {
if (!done) {
done = true;
resolve(builder.finish());
},
onEnd: () => {
if (!done) {
done = true;
resolve(builder.finish());
}
}
});
});

View file

@ -5,7 +5,6 @@
.monaco-action-bar .action-item.menu-entry .action-label {
background-image: var(--menu-entry-icon-light);
display: inline-flex;
}
.vs-dark .monaco-action-bar .action-item.menu-entry .action-label,

View file

@ -95,7 +95,7 @@ function fillInActions(
const target = isPrimaryGroup(group) ? primaryBucket : secondaryBucket;
const submenuActions = action.actions;
if (target.length + submenuActions.length - 1 <= primaryMaxCount && shouldInlineSubmenu(action, group, target.length)) {
if (target.length + submenuActions.length - 2 <= primaryMaxCount && shouldInlineSubmenu(action, group, target.length)) {
target.splice(index, 1, ...submenuActions);
}
}

View file

@ -14,7 +14,7 @@ import { TernarySearchTree } from 'vs/base/common/map';
import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, VSBufferReadableStream, VSBufferReadableBufferedStream, bufferedStreamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer';
import { isReadableStream, transform, peekReadable, peekStream, isReadableBufferedStream, newWriteableStream, IReadableStreamObservable, observe } from 'vs/base/common/stream';
import { isReadableStream, transform, peekReadable, peekStream, isReadableBufferedStream, newWriteableStream, listenStream, consumeStream } from 'vs/base/common/stream';
import { Promises, Queue } from 'vs/base/common/async';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
@ -454,8 +454,7 @@ export class FileService extends Disposable implements IFileService {
throw error;
});
let fileStreamObserver: IReadableStreamObservable | undefined = undefined;
let fileStream: VSBufferReadableStream | undefined = undefined;
try {
// if the etag is provided, we await the result of the validation
@ -466,8 +465,6 @@ export class FileService extends Disposable implements IFileService {
await statPromise;
}
let fileStream: VSBufferReadableStream | undefined = undefined;
// read unbuffered (only if either preferred, or the provider has no buffered read capability)
if (!(hasOpenReadWriteCloseCapability(provider) || hasFileReadStreamCapability(provider)) || (hasReadWriteCapability(provider) && options?.preferUnbuffered)) {
fileStream = this.readFileUnbuffered(provider, resource, options);
@ -483,9 +480,6 @@ export class FileService extends Disposable implements IFileService {
fileStream = this.readFileBuffered(provider, resource, cancellableSource.token, options);
}
// observe the stream for the error case below
fileStreamObserver = observe(fileStream);
const fileStat = await statPromise;
return {
@ -497,8 +491,8 @@ export class FileService extends Disposable implements IFileService {
// Await the stream to finish so that we exit this method
// in a consistent state with file handles closed
// (https://github.com/microsoft/vscode/issues/114024)
if (fileStreamObserver) {
await fileStreamObserver.errorOrEnd();
if (fileStream) {
await consumeStream(fileStream);
}
throw new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
@ -1065,28 +1059,29 @@ export class FileService extends Disposable implements IFileService {
return new Promise(async (resolve, reject) => {
stream.on('data', async chunk => {
listenStream(stream, {
onData: async chunk => {
// pause stream to perform async write operation
stream.pause();
// pause stream to perform async write operation
stream.pause();
try {
await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0);
} catch (error) {
return reject(error);
}
try {
await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0);
} catch (error) {
return reject(error);
}
posInFile += chunk.byteLength;
posInFile += chunk.byteLength;
// resume stream now that we have successfully written
// run this on the next tick to prevent increasing the
// execution stack because resume() may call the event
// handler again before finishing.
setTimeout(() => stream.resume());
// resume stream now that we have successfully written
// run this on the next tick to prevent increasing the
// execution stack because resume() may call the event
// handler again before finishing.
setTimeout(() => stream.resume());
},
onError: error => reject(error),
onEnd: () => resolve()
});
stream.on('error', error => reject(error));
stream.on('end', () => resolve());
});
}

View file

@ -30,7 +30,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
export interface IWatcherOptions {
pollingInterval?: number;
usePolling: boolean;
usePolling: boolean | string[];
}
export interface IDiskFileSystemProviderOptions {

View file

@ -5,8 +5,8 @@
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatcherService';
import { createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
const server = new Server('watcher');
const service = new NsfwWatcherService();
server.registerChannel('watcher', createChannelReceiver(service));
server.registerChannel('watcher', ProxyChannel.fromService(service));

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createChannelSender, getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel, getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { Disposable } from 'vs/base/common/lifecycle';
@ -61,7 +61,7 @@ export class FileWatcher extends Disposable {
}));
// Initialize watcher
this.service = createChannelSender<IWatcherService>(getNextTickChannel(client.getChannel('watcher')));
this.service = ProxyChannel.toService<IWatcherService>(getNextTickChannel(client.getChannel('watcher')));
this.service.setVerboseLogging(this.verboseLogging);

View file

@ -49,7 +49,7 @@ export class ChokidarWatcherService extends Disposable implements IWatcherServic
get wacherCount() { return this._watcherCount; }
private pollingInterval?: number;
private usePolling?: boolean;
private usePolling?: boolean | string[];
private verboseLogging: boolean | undefined;
private spamCheckStartTime: number | undefined;
@ -101,7 +101,11 @@ export class ChokidarWatcherService extends Disposable implements IWatcherServic
private watch(basePath: string, requests: IWatcherRequest[]): IWatcher {
const pollingInterval = this.pollingInterval || 5000;
const usePolling = this.usePolling;
let usePolling = this.usePolling; // boolean or a list of path patterns
if (Array.isArray(usePolling)) {
// switch to polling if one of the paths matches with a watched path
usePolling = usePolling.some(pattern => requests.some(r => glob.match(pattern, r.path)));
}
const watcherOpts: chokidar.WatchOptions = {
ignoreInitial: true,

View file

@ -13,7 +13,7 @@ export interface IWatcherRequest {
export interface IWatcherOptions {
pollingInterval?: number;
usePolling?: boolean;
usePolling?: boolean | string[]; // boolean or a set of glob patterns matching folders that need polling
verboseLogging?: boolean;
}

View file

@ -5,8 +5,8 @@
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { ChokidarWatcherService } from 'vs/platform/files/node/watcher/unix/chokidarWatcherService';
import { createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
const server = new Server('watcher');
const service = new ChokidarWatcherService();
server.registerChannel('watcher', createChannelReceiver(service));
server.registerChannel('watcher', ProxyChannel.fromService(service));

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createChannelSender, getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel, getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { Disposable } from 'vs/base/common/lifecycle';
@ -62,7 +62,7 @@ export class FileWatcher extends Disposable {
}));
// Initialize watcher
this.service = createChannelSender<IWatcherService>(getNextTickChannel(client.getChannel('watcher')));
this.service = ProxyChannel.toService<IWatcherService>(getNextTickChannel(client.getChannel('watcher')));
this.service.init({ ...this.watcherOptions, verboseLogging: this.verboseLogging });
this._register(this.service.onDidChangeFile(e => !this.isDisposed && this.onDidFilesChange(e)));

View file

@ -1584,6 +1584,20 @@ flakySuite('Disk File Service', function () {
assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE);
}
(isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('readFile - dangling symbolic link - https://github.com/microsoft/vscode/issues/116049', async () => {
const link = URI.file(join(testDir, 'small.js-link'));
await promises.symlink(join(testDir, 'small.js'), link.fsPath);
let error: FileOperationError | undefined = undefined;
try {
await service.readFile(link);
} catch (err) {
error = err;
}
assert.ok(error);
});
test('createFile', async () => {
return assertCreateFile(contents => VSBuffer.fromString(contents));
});

View file

@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
/**
* An implementation of `IMainProcessService` that leverages MessagePorts.
*/
export class MessagePortMainProcessService implements IMainProcessService {
declare readonly _serviceBrand: undefined;
constructor(
private server: MessagePortServer,
private router: StaticRouter
) { }
getChannel(channelName: string): IChannel {
return this.server.getChannel(channelName, this.router);
}
registerChannel(channelName: string, channel: IServerChannel<string>): void {
this.server.registerChannel(channelName, channel);
}
}

View file

@ -3,11 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client as IPCElectronClient } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron';
import { Disposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
export const IMainProcessService = createDecorator<IMainProcessService>('mainProcessService');
@ -45,24 +44,3 @@ export class ElectronIPCMainProcessService extends Disposable implements IMainPr
this.mainProcessConnection.registerChannel(channelName, channel);
}
}
/**
* An implementation of `IMainProcessService` that leverages MessagePorts.
*/
export class MessagePortMainProcessService implements IMainProcessService {
declare readonly _serviceBrand: undefined;
constructor(
private server: MessagePortServer,
private router: StaticRouter
) { }
getChannel(channelName: string): IChannel {
return this.server.getChannel(channelName, this.router);
}
registerChannel(channelName: string, channel: IServerChannel<string>): void {
this.server.registerChannel(channelName, channel);
}
}

View file

@ -5,8 +5,7 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { IpcRendererEvent } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { ipcMessagePort } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
@ -45,13 +44,13 @@ export class SharedProcessService extends Disposable implements ISharedProcessSe
// Ask to create message channel inside the window
// and send over a UUID to correlate the response
const nonce = generateUuid();
ipcRenderer.send('vscode:createSharedProcessMessageChannel', nonce);
ipcMessagePort.connect('vscode:createSharedProcessMessageChannel', 'vscode:createSharedProcessMessageChannelResult', nonce);
// Wait until the main side has returned the `MessagePort`
// We need to filter by the `nonce` to ensure we listen
// to the right response.
const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePort }>(ipcRenderer, 'vscode:createSharedProcessMessageChannelResult', (e: IpcRendererEvent, nonce: string) => ({ nonce, port: e.ports[0] }));
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce)));
const onMessageChannelResult = Event.fromDOMEventEmitter<{ nonce: string, port: MessagePort, source: unknown }>(window, 'message', (e: MessageEvent) => ({ nonce: e.data, port: e.ports[0], source: e.source }));
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce && e.source === window)));
this.logService.trace('Renderer->SharedProcess#connect: connection established');

View file

@ -5,7 +5,7 @@
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
// @ts-ignore: interface is implemented via proxy
export class NativeHostService implements INativeHostService {
@ -16,7 +16,7 @@ export class NativeHostService implements INativeHostService {
readonly windowId: number,
@IMainProcessService mainProcessService: IMainProcessService
) {
return createChannelSender<INativeHostService>(mainProcessService.getChannel('nativeHost'), {
return ProxyChannel.toService<INativeHostService>(mainProcessService.getChannel('nativeHost'), {
context: windowId,
properties: (() => {
const properties = new Map<string, unknown>();

View file

@ -17,7 +17,7 @@ export class UserDataSyncLogService extends AbstractLogger implements IUserDataS
@IEnvironmentService environmentService: IEnvironmentService
) {
super();
this.logger = this._register(loggerService.createLogger(environmentService.userDataSyncLogResource));
this.logger = this._register(loggerService.createLogger(environmentService.userDataSyncLogResource, { name: 'settingssync' }));
}
trace(message: string, ...args: any[]): void {

View file

@ -9,6 +9,7 @@ import { bufferToStream, VSBufferReadableStream } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { listenStream } from 'vs/base/common/stream';
import { URI } from 'vs/base/common/uri';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
@ -75,28 +76,27 @@ export class WebviewProtocolProvider extends Disposable {
if (!this.listening) {
this.listening = true;
// Data
stream.on('data', data => {
try {
if (!this.push(data.buffer)) {
stream.pause(); // pause the stream if we should not push anymore
listenStream(stream, {
onData: data => {
try {
if (!this.push(data.buffer)) {
stream.pause(); // pause the stream if we should not push anymore
}
} catch (error) {
this.emit(error);
}
},
onError: error => {
this.emit('error', error);
},
onEnd: () => {
try {
this.push(null); // signal EOS
} catch (error) {
this.emit(error);
}
} catch (error) {
this.emit(error);
}
});
// End
stream.on('end', () => {
try {
this.push(null); // signal EOS
} catch (error) {
this.emit(error);
}
});
// Error
stream.on('error', error => this.emit('error', error));
}
// ensure the stream is flowing
@ -168,7 +168,11 @@ export class WebviewProtocolProvider extends Disposable {
rewriteUri = (uri) => {
if (metadata.remoteConnectionData) {
if (uri.scheme === Schemas.vscodeRemote || (metadata.extensionLocation?.scheme === Schemas.vscodeRemote)) {
return URI.parse(`http://${metadata.remoteConnectionData.host}:${metadata.remoteConnectionData.port}`).with({
let host = metadata.remoteConnectionData.host;
if (host && host.indexOf(':') !== -1) { // IPv6 address
host = `[${host}]`;
}
return URI.parse(`http://${host}:${metadata.remoteConnectionData.port}`).with({
path: '/vscode-remote-resource',
query: `tkn=${metadata.remoteConnectionData.connectionToken}&path=${encodeURIComponent(uri.path)}`,
});

View file

@ -19,7 +19,7 @@ import product from 'vs/platform/product/common/product';
import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { defaultWindowState, ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
@ -44,14 +44,6 @@ export interface IWindowCreationOptions {
isExtensionTestHost?: boolean;
}
export const defaultWindowState = function (mode = WindowMode.Normal): IWindowState {
return {
width: 1024,
height: 768,
mode
};
};
interface ITouchBarSegment extends SegmentedControlSegment {
id: string;
}
@ -702,6 +694,15 @@ export class CodeWindow extends Disposable implements ICodeWindow {
config.userEnv = { ...currentUserEnv, ...config.userEnv }; // still allow to override certain environment as passed in
}
// If named pipe was instantiated for the crashpad_handler process, reuse the same
// pipe for new app instances connecting to the original app instance.
// Ref: https://github.com/microsoft/vscode/issues/115874
if (process.env['CHROME_CRASHPAD_PIPE_NAME']) {
Object.assign(config.userEnv, {
CHROME_CRASHPAD_PIPE_NAME: process.env['CHROME_CRASHPAD_PIPE_NAME']
});
}
// If this is the first time the window is loaded, we associate the paths
// directly with the window because we assume the loading will just work
if (this._readyState === ReadyState.NONE) {

View file

@ -45,6 +45,14 @@ export interface IWindowState {
display?: number;
}
export const defaultWindowState = function (mode = WindowMode.Normal): IWindowState {
return {
width: 1024,
height: 768,
mode
};
};
export const enum WindowMode {
Maximized,
Normal,

View file

@ -12,7 +12,7 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IStateService } from 'vs/platform/state/node/state';
import { CodeWindow } from 'vs/code/electron-main/window';
import { CodeWindow } from 'vs/platform/windows/electron-main/window';
import { BrowserWindow, MessageBoxOptions, WebContents } from 'electron';
import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';

View file

@ -8,13 +8,12 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { defaultWindowState } from 'vs/code/electron-main/window';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/windows/common/windows';
import { ICodeWindow, IWindowsMainService, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { defaultWindowState, ICodeWindow, IWindowsMainService, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export interface IWindowState {

View file

@ -2077,10 +2077,12 @@ declare module 'vscode' {
export function registerTestProvider<T extends TestItem>(testProvider: TestProvider<T>): Disposable;
/**
* Runs tests with the given options. If no options are given, then
* all tests are run. Returns the resulting test run.
* Runs tests. The "run" contains the list of tests to run as well as a
* method that can be used to update their state. At the point in time
* that "run" is called, all tests given in the run have their state
* automatically set to {@link TestRunState.Queued}.
*/
export function runTests<T extends TestItem>(options: TestRunOptions<T>, cancellationToken?: CancellationToken): Thenable<void>;
export function runTests<T extends TestItem>(run: TestRunOptions<T>, cancellationToken?: CancellationToken): Thenable<void>;
/**
* Returns an observer that retrieves tests in the given workspace folder.
@ -2194,6 +2196,14 @@ declare module 'vscode' {
*/
readonly discoveredInitialTests?: Thenable<unknown>;
/**
* An event that fires when a test becomes outdated, as a result of
* file changes, for example. In "watch" mode, tests that are outdated
* will be automatically re-run after a short delay. Firing a test
* with children will mark the entire subtree as outdated.
*/
readonly onDidInvalidateTest?: Event<T>;
/**
* Dispose will be called when there are no longer observers interested
* in the hierarchy.
@ -2238,11 +2248,11 @@ declare module 'vscode' {
* @todo this will eventually need to be able to return a summary report, coverage for example.
*/
// eslint-disable-next-line vscode-dts-provider-naming
runTests?(options: TestRunOptions<T>, cancellationToken: CancellationToken): ProviderResult<void>;
runTests?(options: TestRun<T>, cancellationToken: CancellationToken): ProviderResult<void>;
}
/**
* Options given to `TestProvider.runTests`
* Options given to {@link test.runTests}
*/
export interface TestRunOptions<T extends TestItem = TestItem> {
/**
@ -2257,6 +2267,17 @@ declare module 'vscode' {
debug: boolean;
}
/**
* Options given to `TestProvider.runTests`
*/
export interface TestRun<T extends TestItem = TestItem> extends TestRunOptions<T> {
/**
* Updates the state of the test in the run. By default, all tests involved
* in the run will have a "queued" state until they are updated by this method.
*/
setState(test: T, state: TestState): void;
}
/**
* A test item is an item shown in the "test explorer" view. It encompasses
* both a suite and a test, since they have almost or identical capabilities.
@ -2305,12 +2326,6 @@ declare module 'vscode' {
* Optional list of nested tests for this item.
*/
children?: TestItem[];
/**
* Test run state. Will generally be {@link TestRunState.Unset} by
* default.
*/
state: TestState;
}
/**
@ -2345,11 +2360,11 @@ declare module 'vscode' {
* in order to update it. This allows consumers to quickly and easily check
* for changes via object identity.
*/
export class TestState {
export interface TestState {
/**
* Current state of the test.
*/
readonly runState: TestRunState;
readonly state: TestRunState;
/**
* Optional duration of the test run, in milliseconds.
@ -2360,14 +2375,7 @@ declare module 'vscode' {
* Associated test run message. Can, for example, contain assertion
* failure information if the test fails.
*/
readonly messages: ReadonlyArray<Readonly<TestMessage>>;
/**
* @param state Run state to hold in the test state
* @param messages List of associated messages for the test
* @param duration Length of time the test run took, if appropriate.
*/
constructor(runState: TestRunState, messages?: TestMessage[], duration?: number);
readonly messages?: ReadonlyArray<Readonly<TestMessage>>;
}
/**

View file

@ -214,13 +214,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
$logout(providerId: string, sessionId: string): Promise<void> {
return this.authenticationService.logout(providerId, sessionId);
}
private isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
const extensionData = allowList.find(extension => extension.id === extensionId);
return !!extensionData;
}
private async loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
const { choice } = await this.dialogService.show(
Severity.Info,
@ -257,10 +250,15 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
if (existingSessionPreference) {
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
if (matchingSession) {
const allowed = await this.authenticationService.showGetSessionPrompt(providerId, matchingSession.account.label, extensionId, extensionName);
if (allowed) {
return matchingSession;
const allowed = this.authenticationService.isAccessAllowed(providerId, matchingSession.account.label, extensionId);
if (!allowed) {
const didAcceptPrompt = await this.authenticationService.showGetSessionPrompt(providerId, matchingSession.account.label, extensionId, extensionName);
if (!didAcceptPrompt) {
throw new Error('User did not consent to login.');
}
}
return matchingSession;
}
}
}
@ -270,14 +268,14 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<modes.AuthenticationSession | undefined> {
const orderedScopes = scopes.sort().join(' ');
const sessions = (await this.authenticationService.getSessions(providerId)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
const sessions = (await this.authenticationService.getSessions(providerId, true)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
const silent = !options.createIfNone;
let session: modes.AuthenticationSession | undefined;
if (sessions.length) {
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
session = sessions[0];
const allowed = this.isAccessAllowed(providerId, session.account.label, extensionId);
const allowed = this.authenticationService.isAccessAllowed(providerId, session.account.label, extensionId);
if (!allowed) {
if (!silent) {
const didAcceptPrompt = await this.authenticationService.showGetSessionPrompt(providerId, session.account.label, extensionId, extensionName);
@ -302,7 +300,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
throw new Error('User did not consent to login.');
}
session = await this.authenticationService.login(providerId, scopes);
session = await this.authenticationService.login(providerId, scopes, true);
await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
} else {
await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);

View file

@ -129,13 +129,13 @@ export class MainThreadFileSystemEventService {
}
} else {
if (operation === FileOperation.CREATE) {
message = localize('ask.N.create', "{0} extensions want to make refactoring changes with this file creation", data.extensionNames.length);
message = localize({ key: 'ask.N.create', comment: ['{0} is a number, e.g "3 extensions want..."'] }, "{0} extensions want to make refactoring changes with this file creation", data.extensionNames.length);
} else if (operation === FileOperation.COPY) {
message = localize('ask.N.copy', "{0} extensions want to make refactoring changes with this file copy", data.extensionNames.length);
message = localize({ key: 'ask.N.copy', comment: ['{0} is a number, e.g "3 extensions want..."'] }, "{0} extensions want to make refactoring changes with this file copy", data.extensionNames.length);
} else if (operation === FileOperation.MOVE) {
message = localize('ask.N.move', "{0} extensions want to make refactoring changes with this file move", data.extensionNames.length);
message = localize({ key: 'ask.N.move', comment: ['{0} is a number, e.g "3 extensions want..."'] }, "{0} extensions want to make refactoring changes with this file move", data.extensionNames.length);
} else /* if (operation === FileOperation.DELETE) */ {
message = localize('ask.N.delete', "{0} extensions want to make refactoring changes with this file deletion", data.extensionNames.length);
message = localize({ key: 'ask.N.delete', comment: ['{0} is a number, e.g "3 extensions want..."'] }, "{0} extensions want to make refactoring changes with this file deletion", data.extensionNames.length);
}
}

View file

@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { getTestSubscriptionKey, ITestState, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestResultService, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
@ -19,12 +19,6 @@ const reviveDiff = (diff: TestsDiff) => {
if (item.item.location) {
item.item.location.uri = URI.revive(item.item.location.uri);
}
for (const message of item.item.state.messages) {
if (message.location) {
message.location.uri = URI.revive(message.location.uri);
}
}
}
}
};
@ -37,30 +31,42 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
constructor(
extHostContext: IExtHostContext,
@ITestService private readonly testService: ITestService,
@ITestResultService resultService: ITestResultService,
@ITestResultService private readonly resultService: ITestResultService,
) {
super();
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostTesting);
this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri)));
this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri)));
const testCompleteListener = this._register(new MutableDisposable());
this._register(resultService.onNewTestResult(results => {
testCompleteListener.value = results.onComplete(() => this.proxy.$publishTestResults({ tests: results.tests }));
}));
// const testCompleteListener = this._register(new MutableDisposable());
// todo(@connor4312): reimplement, maybe
// this._register(resultService.onResultsChanged(results => {
// testCompleteListener.value = results.onComplete(() => this.proxy.$publishTestResults({ tests: [] }));
// }));
testService.updateRootProviderCount(1);
const lastCompleted = resultService.results.find(r => !r.isComplete);
if (lastCompleted) {
this.proxy.$publishTestResults({ tests: lastCompleted.tests });
}
for (const { resource, uri } of this.testService.subscriptions) {
this.proxy.$subscribeToTests(resource, uri);
}
}
/**
* @inheritdoc
*/
$updateTestStateInRun(runId: string, testId: string, state: ITestState): void {
const r = this.resultService.getResult(runId);
if (r && r instanceof LiveTestResult) {
for (const message of state.messages) {
if (message.location) {
message.location.uri = URI.revive(message.location.uri);
}
}
r.updateState(testId, state);
}
}
/**
* @inheritdoc
*/
@ -105,8 +111,9 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
this.testService.publishDiff(resource, URI.revive(uri), diff);
}
public $runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult> {
return this.testService.runTests(req, token);
public async $runTests(req: RunTestsRequest, token: CancellationToken): Promise<string> {
const result = await this.testService.runTests(req, token);
return result.id;
}
public dispose() {

View file

@ -1297,10 +1297,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
// checkProposedApiEnabled(extension);
return extHostTypes.TestMessageSeverity;
},
get TestState() {
// checkProposedApiEnabled(extension);
return extHostTypes.TestState;
},
get WorkspaceTrustState() {
// checkProposedApiEnabled(extension);
return extHostTypes.WorkspaceTrustState;

View file

@ -58,7 +58,7 @@ import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib
import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
import { InternalTestItem, InternalTestResults, RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { InternalTestItem, InternalTestResults, ITestState, RunTestForProviderRequest, RunTestsRequest, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust';
@ -1826,7 +1826,7 @@ export const enum ExtHostTestingResource {
}
export interface ExtHostTestingShape {
$runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise<RunTestsResult>;
$runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise<void>;
$subscribeToTests(resource: ExtHostTestingResource, uri: UriComponents): void;
$unsubscribeFromTests(resource: ExtHostTestingResource, uri: UriComponents): void;
$lookupTest(test: TestIdWithProvider): Promise<InternalTestItem | undefined>;
@ -1840,7 +1840,8 @@ export interface MainThreadTestingShape {
$subscribeToDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
$unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
$publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult>;
$updateTestStateInRun(runId: string, testId: string, state: ITestState): void;
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<string>;
}
// --- proxy identifiers

View file

@ -16,11 +16,11 @@ import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTes
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
import { TestItem, TestState } from 'vs/workbench/api/common/extHostTypeConverters';
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { OwnedTestCollection, SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, InternalTestItemWithChildren, InternalTestResults, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, InternalTestItemWithChildren, InternalTestResults, RunTestForProviderRequest, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import type * as vscode from 'vscode';
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
@ -93,9 +93,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
// Find workspace items first, then owned tests, then document tests.
// If a test instance exists in both the workspace and document, prefer
// the workspace because it's less ephemeral.
.map(test => this.workspaceObservers.getMirroredTestDataByReference(test)
?? mapFind(this.testSubscriptions.values(), c => c.collection.getTestByReference(test))
?? this.textDocumentObservers.getMirroredTestDataByReference(test))
.map(this.getInternalTestForReference, this)
.filter(isDefined)
.map(item => ({ providerId: item.providerId, testId: item.id })),
debug: req.debug
@ -219,10 +217,10 @@ export class ExtHostTesting implements ExtHostTestingShape {
* providers to be run.
* @override
*/
public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise<RunTestsResult> {
public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise<void> {
const provider = this.providers.get(req.providerId);
if (!provider || !provider.runTests) {
return EMPTY_TEST_RESULT;
return;
}
const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual)
@ -230,16 +228,25 @@ export class ExtHostTesting implements ExtHostTestingShape {
// Only send the actual TestItem's to the user to run.
.map(t => t instanceof TestItemFilteredWrapper ? t.actual : t);
if (!tests.length) {
return EMPTY_TEST_RESULT;
return;
}
try {
await provider.runTests({ tests, debug: req.debug }, cancellation);
await provider.runTests({
setState: (test, state) => {
const internal = this.getInternalTestForReference(test);
if (internal) {
this.flushCollectionDiffs();
this.proxy.$updateTestStateInRun(req.runId, internal.id, TestState.from(state));
}
}, tests, debug: req.debug
}, cancellation);
for (const { collection } of this.testSubscriptions.values()) {
collection.flushDiff(); // ensure all states are updated
}
return EMPTY_TEST_RESULT;
return;
} catch (e) {
console.error(e); // so it appears to attached debuggers
throw e;
@ -256,6 +263,28 @@ export class ExtHostTesting implements ExtHostTestingShape {
return Promise.resolve(item);
}
/**
* Flushes diff information for all collections to ensure state in the
* main thread is updated.
*/
private flushCollectionDiffs() {
for (const { collection } of this.testSubscriptions.values()) {
collection.flushDiff();
}
}
/**
* Gets the internal test item associated with the reference from the extension.
*/
private getInternalTestForReference(test: vscode.TestItem) {
// Find workspace items first, then owned tests, then document tests.
// If a test instance exists in both the workspace and document, prefer
// the workspace because it's less ephemeral.
return this.workspaceObservers.getMirroredTestDataByReference(test)
?? mapFind(this.testSubscriptions.values(), c => c.collection.getTestByReference(test))
?? this.textDocumentObservers.getMirroredTestDataByReference(test);
}
private createDefaultDocumentTestHierarchy(provider: vscode.TestProvider, document: vscode.TextDocument, folder: vscode.WorkspaceFolder | undefined): vscode.TestHierarchy<vscode.TestItem> | undefined {
if (!folder) {
return;
@ -361,10 +390,6 @@ export class TestItemFilteredWrapper implements vscode.TestItem {
return this.actual.runnable;
}
public get state() {
return this.actual.state;
}
public get children() {
// We only want children that match the filter.
return this.getWrappedChildren().filter(child => child.hasNodeMatchingFilter);
@ -645,7 +670,6 @@ class TestItemFromMirror implements vscode.RequiredTestItem {
public get id() { return this.#internal.revived.id!; }
public get label() { return this.#internal.revived.label; }
public get description() { return this.#internal.revived.description; }
public get state() { return this.#internal.revived.state; }
public get location() { return this.#internal.revived.location; }
public get runnable() { return this.#internal.revived.runnable ?? true; }
public get debuggable() { return this.#internal.revived.debuggable ?? false; }
@ -665,7 +689,6 @@ class TestItemFromMirror implements vscode.RequiredTestItem {
id: this.id,
label: this.label,
description: this.description,
state: this.state,
location: this.location,
runnable: this.runnable,
debuggable: this.debuggable,

View file

@ -1478,22 +1478,22 @@ export namespace NotebookDecorationRenderOptions {
export namespace TestState {
export function from(item: vscode.TestState): ITestState {
return {
runState: item.runState,
state: item.state,
duration: item.duration,
messages: item.messages.map(message => ({
messages: item.messages?.map(message => ({
message: MarkdownString.fromStrict(message.message) || '',
severity: message.severity,
expectedOutput: message.expectedOutput,
actualOutput: message.actualOutput,
location: message.location ? location.from(message.location) : undefined,
})),
})) ?? [],
};
}
export function to(item: ITestState): vscode.TestState {
return new types.TestState(
item.runState,
item.messages.map(message => ({
return {
state: item.state,
messages: item.messages.map(message => ({
message: typeof message.message === 'string' ? message.message : MarkdownString.to(message.message),
severity: message.severity,
expectedOutput: message.expectedOutput,
@ -1503,8 +1503,8 @@ export namespace TestState {
uri: URI.revive(message.location.uri)
}),
})),
item.duration,
);
duration: item.duration,
};
}
}
@ -1518,7 +1518,6 @@ export namespace TestItem {
debuggable: item.debuggable ?? false,
description: item.description,
runnable: item.runnable ?? true,
state: TestState.from(item.state),
};
}
@ -1533,7 +1532,6 @@ export namespace TestItem {
debuggable: item.debuggable,
description: item.description,
runnable: item.runnable,
state: TestState.to(item.state),
};
}
}

View file

@ -2849,6 +2849,33 @@ export enum ColorThemeKind {
//#region Notebook
export class NotebookCellMetadata {
readonly readonly: boolean;
readonly inputCollapsed: boolean;
readonly outputCollapsed: boolean;
[key: string]: unknown;
constructor(readonly: boolean, inputCollapsed: boolean, outputCollapsed: boolean) {
this.readonly = readonly;
this.inputCollapsed = inputCollapsed;
this.outputCollapsed = outputCollapsed;
}
with(change: Partial<{ readonly: boolean, inputCollapsed: boolean, outputCollapsed: boolean, [key: string]: unknown }>): NotebookCellMetadata {
const thisAndChange = { ...this, ...change };
const res = new NotebookCellMetadata(thisAndChange.readonly, thisAndChange.inputCollapsed, thisAndChange.outputCollapsed);
for (const key in change) {
if (Object.prototype.hasOwnProperty.call(change, key)) {
res[key] = change[key];
}
}
return res;
}
}
export class NotebookCellOutputItem {
static isNotebookCellOutputItem(obj: unknown): obj is vscode.NotebookCellOutputItem {
@ -3018,31 +3045,6 @@ export enum TestMessageSeverity {
Hint = 3
}
@es5ClassCompat
export class TestState {
#runState: TestRunState;
#duration?: number;
#messages: ReadonlyArray<Readonly<vscode.TestMessage>>;
public get runState() {
return this.#runState;
}
public get duration() {
return this.#duration;
}
public get messages() {
return this.#messages;
}
constructor(runState: TestRunState, messages: vscode.TestMessage[] = [], duration?: number) {
this.#runState = runState;
this.#messages = Object.freeze(messages.map(m => Object.freeze(m)));
this.#duration = duration;
}
}
export type RequiredTestItem = vscode.RequiredTestItem;
export type TestItem = vscode.TestItem;

View file

@ -182,6 +182,11 @@ const apiMenus: IAPIMenu[] = [
id: MenuId.TimelineItemContext,
description: localize('view.timelineContext', "The Timeline view item context menu")
},
{
key: 'ports/item/context',
id: MenuId.TunnelContext,
description: localize('view.tunnelContext', "The Ports view item context menu")
}
];
namespace schema {

View file

@ -104,7 +104,7 @@ export class BreadcrumbsModel {
}
const breadcrumbsElements = this._currentOutline.value.config.breadcrumbsDataSource.getBreadcrumbElements();
for (let i = this._cfgSymbolPath.getValue() === 'last' ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) {
for (let i = this._cfgSymbolPath.getValue() === 'last' && breadcrumbsElements.length > 0 ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) {
result.push(new OutlineElement2(breadcrumbsElements[i], this._currentOutline.value));
}

View file

@ -469,7 +469,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_KEEP_EDITORS_COMMAND_ID, title: nls.localize('toggleKeepEditors', "Keep Editors Open"), toggled: ContextKeyExpr.not('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, title: { value: nls.localize('run', "Run"), original: 'Run', }, icon: Codicon.run, group: 'navigation' });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, title: { value: nls.localize('run', "Run"), original: 'Run', }, icon: Codicon.run, group: 'navigation', order: -1 });
interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; }

View file

@ -64,13 +64,7 @@
}
.monaco-workbench .pane > .pane-body > .welcome-view .monaco-button {
margin-left: auto;
margin-right: auto;
}
.monaco-workbench .pane > .pane-body.wide > .welcome-view .monaco-button {
margin-left: inherit;
max-width: 260px;
max-width: 300px;
}
.monaco-workbench .pane > .pane-body .welcome-view-content {

View file

@ -9,7 +9,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchCo
import { Disposable } from 'vs/base/common/lifecycle';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IDisplayMainService } from 'vs/platform/display/common/displayMainService';
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
class DisplayChangeRemeasureFonts extends Disposable implements IWorkbenchContribution {
@ -18,7 +18,7 @@ class DisplayChangeRemeasureFonts extends Disposable implements IWorkbenchContri
@IMainProcessService mainProcessService: IMainProcessService
) {
super();
const displayMainService = createChannelSender<IDisplayMainService>(mainProcessService.getChannel('display'));
const displayMainService = ProxyChannel.toService<IDisplayMainService>(mainProcessService.getChannel('display'));
displayMainService.onDidDisplayChanged(() => {
clearAllFontInfos();
});

View file

@ -76,7 +76,6 @@ function revealLastElement(tree: WorkbenchAsyncDataTree<any, any, any>) {
const sessionsToIgnore = new Set<IDebugSession>();
const identityProvider = { getId: (element: IReplElement) => element.getId() };
const diffIdentityProvider = { getId: (element: IReplElement) => element.getId() + element.toString() };
export class Repl extends ViewPane implements IHistoryNavigationWidget {
declare readonly _serviceBrand: undefined;
@ -513,7 +512,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
}
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
await this.tree.updateChildren(undefined, true, false, { diffIdentityProvider });
await this.tree.updateChildren(undefined, true, false, { diffIdentityProvider: identityProvider });
const session = this.tree.getInput();
if (session) {

View file

@ -704,6 +704,7 @@ declare module DebugProtocol {
The request configures the debuggers response to thrown exceptions.
If an exception is configured to break, a 'stopped' event is fired (with reason 'exception').
Clients should only call this request if the capability 'exceptionBreakpointFilters' returns one or more filters.
If a filter or filter option is invalid (e.g. due to an invalid 'condition'), the request should fail with an 'ErrorResponse' explaining the problem(s).
*/
export interface SetExceptionBreakpointsRequest extends Request {
// command: 'setExceptionBreakpoints';
@ -1629,10 +1630,14 @@ declare module DebugProtocol {
filter: string;
/** The name of the filter option. This will be shown in the UI. */
label: string;
/** An optional help text providing additional information about the exception filter. This string is typically shown as a hover and must be translated. */
description?: string;
/** Initial value of the filter option. If not specified a value 'false' is assumed. */
default?: boolean;
/** Controls whether a condition can be specified for this filter option. If false or missing, a condition can not be set. */
supportsCondition?: boolean;
/** An optional help text providing information about the condition. This string is shown as the placeholder text for a text box and must be translated. */
conditionDescription?: string;
}
/** A structured message object. Used to return errors from requests. */
@ -1774,6 +1779,8 @@ declare module DebugProtocol {
endLine?: number;
/** An optional end column of the range covered by the stack frame. */
endColumn?: number;
/** Indicates whether this frame can be restarted with the 'restart' request. Clients should only use this if the debug adapter supports the 'restart' request (capability 'supportsRestartRequest' is true). */
canRestart?: boolean;
/** Optional memory reference for the current instruction pointer in this frame. */
instructionPointerReference?: string;
/** The module associated with this frame, if any. */

View file

@ -16,6 +16,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
const MAX_REPL_LENGTH = 10000;
let topReplElementCounter = 0;
const getUniqueId = () => `topReplElement:${topReplElementCounter++}`;
export class SimpleReplElement implements IReplElement {
@ -234,13 +235,14 @@ export class ReplModel {
return;
}
if (!previousElement.value.endsWith('\n') && !previousElement.value.endsWith('\r\n') && previousElement.count === 1) {
previousElement.value += data;
this.replElements[this.replElements.length - 1] = new SimpleReplElement(
session, getUniqueId(), previousElement.value + data, sev, source);
this._onDidChangeElements.fire();
return;
}
}
const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source);
const element = new SimpleReplElement(session, getUniqueId(), data, sev, source);
this.addReplElement(element);
} else {
// TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression
@ -315,7 +317,7 @@ export class ReplModel {
}
// show object
this.appendToRepl(session, new RawObjectReplElement(`topReplElement:${topReplElementCounter++}`, (<any>a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source);
this.appendToRepl(session, new RawObjectReplElement(getUniqueId(), (<any>a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source);
}
// string: watch out for % replacement directive

View file

@ -2193,7 +2193,7 @@ export class SystemDisabledWarningAction extends ExtensionAction {
}
if (this.workspaceTrustService.isWorkspaceTrustEnabled() && this.extension.enablementState === EnablementState.DisabledByTrustRequirement) {
this.class = `${SystemDisabledWarningAction.TRUST_CLASS}`;
this.tooltip = localize('extension disabled because of trust requirement', "This extension has been disabled as it requires a trusted workspace");
this.tooltip = localize('extension disabled because of trust requirement', "This extension has been disabled because the current workspace is not trusted");
return;
}
}

View file

@ -31,4 +31,5 @@ export const starEmptyIcon = registerIcon('extensions-star-empty', Codicon.starE
export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, localize('warningIcon', 'Icon shown with a warning message in the extensions editor.'));
export const infoIcon = registerIcon('extensions-info-message', Codicon.info, localize('infoIcon', 'Icon shown with an info message in the extensions editor.'));
export const trustIcon = registerIcon('extension-trust-message', Codicon.shield, localize('trustIcon', 'Icon shown with a message in the extension editor.'));
export const trustIcon = registerIcon('extension-workspace-trust', Codicon.shield, localize('trustIcon', 'Icon shown with a workspace trust message in the extension editor.'));

View file

@ -34,6 +34,9 @@
}
.extension-install-count .codicon,
.extension-action.codicon-extensions-info-message,
.extension-action.codicon-extensions-warning-message,
.extension-action.codicon-extension-workspace-trust,
.extension-action.codicon-extensions-manage {
color: inherit;
}

View file

@ -53,6 +53,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
import { IExplorerService } from 'vs/workbench/contrib/files/browser/files';
import { listenStream } from 'vs/base/common/stream';
export const NEW_FILE_COMMAND_ID = 'explorer.newFile';
export const NEW_FILE_LABEL = nls.localize('newFile', "New File");
@ -1034,22 +1035,22 @@ const downloadFileHandler = async (accessor: ServicesAccessor) => {
reject();
}));
sourceStream.on('data', data => {
if (!disposed) {
target.write(data.buffer);
reportProgress(contents.name, contents.size, data.byteLength, operation);
listenStream(sourceStream, {
onData: data => {
if (!disposed) {
target.write(data.buffer);
reportProgress(contents.name, contents.size, data.byteLength, operation);
}
},
onError: error => {
disposables.dispose();
reject(error);
},
onEnd: () => {
disposables.dispose();
resolve();
}
});
sourceStream.on('error', error => {
disposables.dispose();
reject(error);
});
sourceStream.on('end', () => {
disposables.dispose();
resolve();
});
});
}

View file

@ -1624,7 +1624,10 @@ export class ChangeCellLanguageAction extends NotebookCellAction {
const modelService = accessor.get(IModelService);
const quickInputService = accessor.get(IQuickInputService);
const providerLanguages = [...context.notebookEditor.viewModel.notebookDocument.resolvedLanguages, 'markdown'];
const providerLanguages = [
...(context.notebookEditor.activeKernel?.supportedLanguages ?? context.notebookEditor.viewModel.notebookDocument.resolvedLanguages),
'markdown'
];
providerLanguages.forEach(languageId => {
let description: string;
if (context.cell.cellKind === CellKind.Markdown ? (languageId === 'markdown') : (languageId === context.cell.language)) {

View file

@ -91,8 +91,8 @@ registerAction2(class extends Action2 {
) : undefined;
if (selectedKernel) {
editor.activeKernel = selectedKernel!;
return selectedKernel!.resolve(editor.uri!, editor.getId(), tokenSource.token);
editor.activeKernel = selectedKernel;
return selectedKernel.resolve(editor.uri!, editor.getId(), tokenSource.token);
} else {
picker.show();
}
@ -224,4 +224,3 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution {
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(KernelStatus, LifecyclePhase.Ready);

View file

@ -1470,6 +1470,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const nextIndex = ui ? this.viewModel.getNextVisibleCellIndex(index) : index + 1;
let language;
if (type === CellKind.Code) {
const supportedLanguages = this._activeKernel?.supportedLanguages ?? this.viewModel.notebookDocument.resolvedLanguages;
const defaultLanguage = supportedLanguages[0] || 'plaintext';
if (cell?.cellKind === CellKind.Code) {
language = cell.language;
} else if (cell?.cellKind === CellKind.Markdown) {
@ -1477,20 +1479,20 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
if (nearestCodeCellIndex > -1) {
language = this.viewModel.viewCells[nearestCodeCellIndex].language;
} else {
language = this.viewModel.resolvedLanguages[0] || 'plaintext';
language = defaultLanguage;
}
} else {
if (cell === undefined && direction === 'above') {
// insert cell at the very top
language = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Code)?.language || this.viewModel.resolvedLanguages[0] || 'plaintext';
language = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Code)?.language || defaultLanguage;
} else {
language = this.viewModel.resolvedLanguages[0] || 'plaintext';
language = defaultLanguage;
}
}
if (this.viewModel.resolvedLanguages.indexOf(language) < 0) {
if (!supportedLanguages.includes(language)) {
// the language no longer exists
language = this.viewModel.resolvedLanguages[0] || 'plaintext';
language = defaultLanguage;
}
} else {
language = 'markdown';
@ -1500,8 +1502,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
(direction === 'above' ? index : nextIndex) :
index;
const focused = this._list.getFocusedElements();
const newCell = this.viewModel.createCell(insertIndex, initialText, language, type, undefined, [], true, undefined, focused);
return newCell as CellViewModel;
return this.viewModel.createCell(insertIndex, initialText, language, type, undefined, [], true, undefined, focused);
}
async splitNotebookCell(cell: ICellViewModel): Promise<CellViewModel[] | null> {

View file

@ -40,13 +40,9 @@ import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService } from
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
export class NotebookKernelProviderInfoStore extends Disposable {
export class NotebookKernelProviderInfoStore {
private readonly _notebookKernelProviders: INotebookKernelProvider[] = [];
constructor() {
super();
}
add(provider: INotebookKernelProvider) {
this._notebookKernelProviders.push(provider);
this._updateProviderExtensionsInfo();
@ -66,7 +62,7 @@ export class NotebookKernelProviderInfoStore extends Disposable {
}
getContributedKernelProviders() {
return [...this._notebookKernelProviders.values()];
return [...this._notebookKernelProviders];
}
private _updateProviderExtensionsInfo() {

View file

@ -168,10 +168,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return this._notebook;
}
get resolvedLanguages() {
return this._notebook.resolvedLanguages;
}
get uri() {
return this._notebook.uri;
}

View file

@ -189,6 +189,8 @@ export interface INotebookTextModel {
metadata: NotebookDocumentMetadata
readonly uri: URI;
readonly versionId: number;
/** @deprecated */
languages: string[];
readonly cells: readonly ICell[];
onWillDispose(listener: () => void): IDisposable;
@ -722,6 +724,7 @@ export interface INotebookKernelInfoDto2 {
detail?: string;
isPreferred?: boolean;
preloads?: UriComponents[];
supportedLanguages?: string[]
}
export interface INotebookKernelInfo2 extends INotebookKernelInfoDto2 {

View file

@ -462,6 +462,7 @@ class OutputAutomaticPortForwarding extends Disposable {
class ProcAutomaticPortForwarding extends Disposable {
private candidateListener: IDisposable | undefined;
private autoForwarded: Set<string> = new Set();
private notifiedOnly: Set<string> = new Set();
private notifier: OnAutoForwardedAction;
private initialCandidates: Set<string> = new Set();
private portsFeatures: IDisposable | undefined;
@ -543,18 +544,18 @@ class ProcAutomaticPortForwarding extends Disposable {
if (this.initialCandidates.has(address)) {
return undefined;
}
const alreadyForwarded = mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.forwarded, value.host, value.port);
if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.detected, value.host, value.port)) {
return undefined;
}
if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.forwarded, value.host, value.port)) {
return undefined;
}
if (this.portsAttributes.getAttributes(value.port)?.onAutoForward === OnPortForward.Ignore) {
return undefined;
}
const forwarded = await this.remoteExplorerService.forward(value, undefined, undefined, undefined, undefined, undefined, false);
if (forwarded) {
if (!alreadyForwarded && forwarded) {
this.autoForwarded.add(address);
} else if (forwarded) {
this.notifiedOnly.add(address);
}
return forwarded;
}))).filter(tunnel => !!tunnel);
@ -571,6 +572,9 @@ class ProcAutomaticPortForwarding extends Disposable {
this.remoteExplorerService.close(value);
this.autoForwarded.delete(key);
removedPorts.push(value.port);
} else if (this.notifiedOnly.has(key)) {
this.notifiedOnly.delete(key);
removedPorts.push(value.port);
} else if (this.initialCandidates.has(key)) {
this.initialCandidates.delete(key);
}

View file

@ -812,23 +812,26 @@ namespace LabelTunnelAction {
export const LABEL = nls.localize('remote.tunnel.label', "Set Label");
export function handler(): ICommandHandler {
return async (accessor, arg) => {
return async (accessor, arg): Promise<{ port: number, label: string } | undefined> => {
const context = (arg !== undefined || arg instanceof TunnelItem) ? arg : accessor.get(IContextKeyService).getContextKeyValue(TunnelViewSelectionKeyName);
if (context instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
remoteExplorerService.setEditable(context, {
onFinish: async (value, success) => {
if (success) {
remoteExplorerService.tunnelModel.name(context.remoteHost, context.remotePort, value);
}
remoteExplorerService.setEditable(context, null);
},
validationMessage: () => null,
placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"),
startingValue: context.name
return new Promise(resolve => {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
remoteExplorerService.setEditable(context, {
onFinish: async (value, success) => {
if (success) {
remoteExplorerService.tunnelModel.name(context.remoteHost, context.remotePort, value);
}
remoteExplorerService.setEditable(context, null);
resolve(success ? { port: context.remotePort, label: value } : undefined);
},
validationMessage: () => null,
placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"),
startingValue: context.name
});
});
}
return;
return undefined;
};
}
}

View file

@ -36,7 +36,6 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
registerThemingParticipant((theme, collector) => {
const sashHoverBorderColor = theme.getColor(sashHoverBorder);
collector.addRule(`
.monaco-sash:hover,
.monaco-sash.hover,
.monaco-sash.active {
background: ${sashHoverBorderColor}

View file

@ -21,7 +21,7 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer';
import { collectContextMenuActions, getStatusBarActionViewItem } from 'vs/workbench/contrib/scm/browser/util';
import { collectContextMenuActions, getActionViewItemProvider } from 'vs/workbench/contrib/scm/browser/util';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
class ListDelegate implements IListVirtualDelegate<ISCMRepository> {
@ -62,7 +62,7 @@ export class SCMRepositoriesViewPane extends ViewPane {
const listContainer = append(container, $('.scm-view.scm-repositories-view'));
const delegate = new ListDelegate();
const renderer = this.instantiationService.createInstance(RepositoryRenderer, getStatusBarActionViewItem);
const renderer = this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService));
const identityProvider = { getId: (r: ISCMRepository) => r.provider.id };
this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, listContainer, delegate, [renderer], {

View file

@ -23,7 +23,7 @@ import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options,
import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IThemeService, registerThemingParticipant, IFileIconTheme } from 'vs/platform/theme/common/themeService';
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getStatusBarActionViewItem } from './util';
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider } from './util';
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { WorkbenchCompressibleObjectTree, IOpenEvent } from 'vs/platform/list/browser/listService';
import { IConfigurationService, ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
@ -1831,10 +1831,10 @@ export class SCMViewPane extends ViewPane {
this._register(actionRunner.onBeforeRun(() => this.tree.domFocus()));
const renderers: ICompressibleTreeRenderer<any, any, any>[] = [
this.instantiationService.createInstance(RepositoryRenderer, getStatusBarActionViewItem),
this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)),
this.inputRenderer,
this.instantiationService.createInstance(ResourceGroupRenderer, getStatusBarActionViewItem),
this.instantiationService.createInstance(ResourceRenderer, () => this._viewModel, this.listLabels, getStatusBarActionViewItem, actionRunner)
this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)),
this.instantiationService.createInstance(ResourceRenderer, () => this._viewModel, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner)
];
const filter = new SCMTreeFilter();

View file

@ -7,14 +7,15 @@ import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput } from 'vs/w
import { IMenu } from 'vs/platform/actions/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IDisposable, Disposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Action, IAction, IActionViewItem } from 'vs/base/common/actions';
import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { Action, IAction, IActionViewItemProvider } from 'vs/base/common/actions';
import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { equals } from 'vs/base/common/arrays';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Command } from 'vs/editor/common/modes';
import { reset } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export function isSCMRepository(element: any): element is ISCMRepository {
return !!(element as ISCMRepository).provider && !!(element as ISCMRepository).input;
@ -107,10 +108,12 @@ class StatusBarActionViewItem extends ActionViewItem {
}
}
export function getStatusBarActionViewItem(action: IAction): IActionViewItem | undefined {
if (action instanceof StatusBarAction) {
return new StatusBarActionViewItem(action);
}
export function getActionViewItemProvider(instaService: IInstantiationService): IActionViewItemProvider {
return action => {
if (action instanceof StatusBarAction) {
return new StatusBarActionViewItem(action);
}
return undefined;
return createActionViewItem(instaService, action);
};
}

View file

@ -17,7 +17,7 @@ import { Action2, ICommandAction, MenuId, MenuRegistry, registerAction2, SyncAct
import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyAndExpr, ContextKeyEqualsExpr, ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyEqualsExpr, ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IFileService } from 'vs/platform/files/common/files';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@ -637,8 +637,6 @@ const viewDescriptor: IViewDescriptor = {
mnemonicTitle: nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search"),
keybindings: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F,
// View: Show Search is used for the keybindings in the View menu and the sidebar #115556, but it should only be enabled when search.mode == view, or else it will steal priority over opening a search editor #115511
when: ContextKeyAndExpr.create([Constants.SearchViewVisibleKey.toNegated(), ContextKeyEqualsExpr.create(`config.${SEARCH_MODE_CONFIG}`, 'view')]),
},
order: 1
}
@ -712,7 +710,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
]
},
id: Constants.FindInFilesActionId,
weight: KeybindingWeight.WorkbenchContrib,
// Give more weightage to this keybinding than of `View: Show Search` keybinding. See #116188, #115556, #115511
weight: KeybindingWeight.WorkbenchContrib + 1,
when: null,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F,
handler: FindInFilesCommand

View file

@ -678,12 +678,18 @@ export class SearchView extends ViewPane {
private clearMessage(): HTMLElement {
this.searchWithoutFolderMessageElement = undefined;
const wasHidden = this.messagesElement.style.display === 'none';
dom.clearNode(this.messagesElement);
dom.show(this.messagesElement);
dispose(this.messageDisposables);
this.messageDisposables = [];
return dom.append(this.messagesElement, $('.message'));
const newMessage = dom.append(this.messagesElement, $('.message'));
if (wasHidden) {
this.reLayout();
}
return newMessage;
}
private createSearchResultsView(container: HTMLElement): void {
@ -1085,6 +1091,7 @@ export class SearchView extends ViewPane {
this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search");
aria.status(nls.localize('ariaSearchResultsClearStatus', "The search results have been cleared"));
this.reLayout();
}
clearFilePatternFields(): void {

View file

@ -237,12 +237,12 @@ export function escapeNonWindowsPath(path: string): string {
if (newPath.indexOf('\\') !== 0) {
newPath = newPath.replace(/\\/g, '\\\\');
}
if (!newPath && (newPath.indexOf('"') !== -1)) {
newPath = '\'' + newPath + '\'';
} else if (newPath.indexOf(' ') !== -1) {
if (newPath.indexOf(' ') !== -1) {
newPath = newPath.replace(/ /g, '\\ ');
}
return newPath;
const bannedChars = /[\`\$\|\&\>\~\#\!\^\*\;\<\"\']/g;
newPath = newPath.replace(bannedChars, '');
return `'${newPath}'`;
}
export type TerminalShellSetting = (

View file

@ -10,13 +10,28 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position';
import { IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { HierarchicalElement, HierarchicalFolder } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes';
import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/browser/explorerProjections/locationStore';
import { NodeChangeList, NodeRenderDirective, NodeRenderFn, peersHaveChildren } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { IComputedStateAccessor, refreshComputedState } from 'vs/workbench/contrib/testing/common/getComputedState';
import { InternalTestItem, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
const computedStateAccessor: IComputedStateAccessor<ITestTreeElement> = {
getOwnState: i => i.state,
getCurrentComputedState: i => i.state,
setComputedState: (i, s) => i.state = s,
getChildren: i => i.children.values(),
*getParents(i) {
for (let parent = i.parentItem; parent; parent = parent.parentItem) {
yield parent;
}
},
};
/**
* Projection that lists tests in their traditional tree view.
*/
@ -40,11 +55,42 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
*/
public readonly onUpdate = this.updateEmitter.event;
constructor(listener: TestSubscriptionListener) {
constructor(listener: TestSubscriptionListener, @ITestResultService private readonly results: ITestResultService) {
super();
this._register(listener.onDiff(([folder, diff]) => this.applyDiff(folder, diff)));
this._register(listener.onFolderChange(this.applyFolderChange, this));
// when test results are cleared, recalculate all state
this._register(results.onResultsChanged((evt) => {
if (!('removed' in evt)) {
return;
}
for (const inTree of [...this.items.values()].sort((a, b) => b.depth - a.depth)) {
const lookup = this.results.getStateByExtId(inTree.test.item.extId)?.[1];
inTree.ownState = lookup?.state.state ?? TestRunState.Unset;
const computed = lookup?.computedState ?? TestRunState.Unset;
if (computed !== inTree.state) {
inTree.state = computed;
this.addUpdated(inTree);
}
}
this.updateEmitter.fire();
}));
// when test states change, reflect in the tree
this._register(results.onTestChanged(([, { item, state, computedState }]) => {
for (const i of this.items.values()) {
if (i.test.item.extId === item.extId) {
i.ownState = state.state;
refreshComputedState(computedStateAccessor, i, this.addUpdated, computedState);
this.updateEmitter.fire();
return;
}
}
}));
for (const [folder, collection] of listener.workspaceFolderCollections) {
for (const node of collection.all) {
this.storeItem(this.createItem(node, folder.folder));
@ -96,7 +142,7 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
const locationChanged = !locationsEqual(existing.location, item.item.location);
if (locationChanged) { this.locations.remove(existing); }
existing.update(item, this.addUpdated);
existing.update(item);
if (locationChanged) { this.locations.add(existing); }
this.addUpdated(existing);
break;
@ -172,5 +218,11 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
item.parentItem.children.add(item);
this.items.set(item.test.id, item);
this.locations.add(item);
const prevState = this.results.getStateByExtId(item.test.item.extId)?.[1];
if (prevState) {
item.ownState = prevState.state.state;
refreshComputedState(computedStateAccessor, item, this.addUpdated, prevState.computedState);
}
}
}

View file

@ -10,6 +10,7 @@ import { HierarchicalByLocationProjection as HierarchicalByLocationProjection }
import { HierarchicalElement, HierarchicalFolder } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes';
import { NodeRenderDirective } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
/**
@ -64,9 +65,9 @@ export class HierarchicalByNameElement extends HierarchicalElement {
/**
* @override
*/
public update(actual: InternalTestItem, addUpdated: (n: ITestTreeElement) => void) {
public update(actual: InternalTestItem) {
const wasRunnable = this.test.item.runnable;
super.update(actual, addUpdated);
super.update(actual);
if (this.test.item.runnable !== wasRunnable) {
this.updateLeafTestState();
@ -117,8 +118,8 @@ export class HierarchicalByNameElement extends HierarchicalElement {
* test root rather than the heirarchal parent.
*/
export class HierarchicalByNameProjection extends HierarchicalByLocationProjection {
constructor(listener: TestSubscriptionListener) {
super(listener);
constructor(listener: TestSubscriptionListener, @ITestResultService results: ITestResultService) {
super(listener, results);
const originalRenderNode = this.renderNode.bind(this);
this.renderNode = (node, recurse) => {

View file

@ -7,7 +7,6 @@ import { Iterable } from 'vs/base/common/iterator';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { maxPriority, statePriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { InternalTestItem, TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
/**
@ -15,7 +14,6 @@ import { InternalTestItem, TestIdWithProvider } from 'vs/workbench/contrib/testi
*/
export class HierarchicalElement implements ITestTreeElement {
public readonly children = new Set<HierarchicalElement>();
public computedState: TestRunState | undefined;
public readonly depth: number = this.parentItem.depth + 1;
public get treeId() {
@ -26,10 +24,6 @@ export class HierarchicalElement implements ITestTreeElement {
return this.test.item.label;
}
public get state() {
return this.test.item.state.runState;
}
public get location() {
return this.test.item.location;
}
@ -46,16 +40,15 @@ export class HierarchicalElement implements ITestTreeElement {
: Iterable.empty();
}
public state = TestRunState.Unset;
public ownState = TestRunState.Unset;
constructor(public readonly test: InternalTestItem, public readonly parentItem: HierarchicalFolder | HierarchicalElement) {
this.test = { ...test, item: { ...test.item } }; // clone since we Object.assign updatese
}
public update(actual: InternalTestItem, addUpdated: (n: ITestTreeElement) => void) {
const stateChange = actual.item.state.runState !== this.state;
public update(actual: InternalTestItem) {
Object.assign(this.test, actual);
if (stateChange) {
refreshComputedState(this, addUpdated);
}
}
}
@ -80,67 +73,12 @@ export class HierarchicalFolder implements ITestTreeElement {
return Iterable.concatNested(Iterable.map(this.children, c => c.debuggable));
}
public state = TestRunState.Unset;
public ownState = TestRunState.Unset;
constructor(private readonly folder: IWorkspaceFolder) { }
public get label() {
return this.folder.name;
}
}
/**
* Gets the computed state for the node.
*/
export const getComputedState = (node: ITestTreeElement) => {
if (node.computedState === undefined) {
node.computedState = node.state ?? TestRunState.Unset;
for (const child of node.children) {
node.computedState = maxPriority(node.computedState, getComputedState(child));
}
}
return node.computedState;
};
/**
* Refreshes the computed state for the node and its parents. Any changes
* elements cause `addUpdated` to be called.
*/
export const refreshComputedState = (node: ITestTreeElement, addUpdated: (n: ITestTreeElement) => void) => {
if (node.computedState === undefined) {
return;
}
const oldPriority = statePriority[node.computedState];
node.computedState = undefined;
const newState = getComputedState(node);
const newPriority = statePriority[getComputedState(node)];
if (newPriority === oldPriority) {
return;
}
addUpdated(node);
if (newPriority > oldPriority) {
// Update all parents to ensure they're at least this priority.
for (let parent = node.parentItem; parent; parent = parent.parentItem) {
const prev = parent.computedState;
if (prev !== undefined && statePriority[prev] >= newPriority) {
break;
}
parent.computedState = newState;
addUpdated(parent);
}
} else if (newPriority < oldPriority) {
// Re-render all parents of this node whose computed priority might have come from this node
for (let parent = node.parentItem; parent; parent = parent.parentItem) {
const prev = parent.computedState;
if (prev === undefined || statePriority[prev] > oldPriority) {
break;
}
parent.computedState = undefined;
parent.computedState = getComputedState(parent);
addUpdated(parent);
}
}
};

View file

@ -40,13 +40,6 @@ export interface ITestTreeProjection extends IDisposable {
export interface ITestTreeElement {
/**
* Computed element state. Will be set automatically if not initially provided.
* The projection is responsible for clearing (or updating) this if it
* becomes invalid.
*/
computedState: TestRunState | undefined;
readonly children: Set<ITestTreeElement>;
/**
@ -85,9 +78,11 @@ export interface ITestTreeElement {
readonly debuggable: Iterable<TestIdWithProvider>;
/**
* State of of the tree item. Mostly used for deriving the computed state.
* Element state to display.
*/
readonly state?: TestRunState;
state: TestRunState;
readonly ownState: TestRunState;
readonly label: string;
readonly parentItem: ITestTreeElement | null;
}

View file

@ -1,321 +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 { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { Emitter } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { Iterable } from 'vs/base/common/iterator';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position';
import { Location as ModeLocation } from 'vs/editor/common/modes';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/browser/explorerProjections/locationStore';
import { NodeChangeList, NodeRenderDirective, NodeRenderFn, peersHaveChildren } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { isRunningState, statesInOrder } from 'vs/workbench/contrib/testing/common/testingStates';
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
interface IStatusTestItem extends IncrementalTestCollectionItem {
treeElements: Map<TestRunState, TestStateElement>;
previousState: TestRunState;
depth: number;
parentItem?: IStatusTestItem;
location?: ModeLocation;
}
type TreeElement = StateElement<TestStateElement> | TestStateElement;
class TestStateElement implements ITestTreeElement {
public computedState = this.state;
public get treeId() {
return `sltest:${this.test.id}`;
}
public get label() {
return this.test.item.label;
}
public get location() {
return this.test.item.location;
}
public get runnable(): Iterable<TestIdWithProvider> {
// if this item is runnable and all its children are in the same state,
// we can run all of them in one go. This will eventually be true
// for leaf nodes, whose treeElements contain only their own state.
if (this.test.item.runnable && this.test.treeElements.size === 1) {
return [{ testId: this.test.id, providerId: this.test.providerId }];
}
return Iterable.concatNested(Iterable.map(this.children, c => c.runnable));
}
public get debuggable(): Iterable<TestIdWithProvider> {
// same logic as runnable above
if (this.test.item.debuggable && this.test.treeElements.size === 1) {
return [{ testId: this.test.id, providerId: this.test.providerId }];
}
return Iterable.concatNested(Iterable.map(this.children, c => c.debuggable));
}
public readonly depth = this.test.depth;
public readonly children = new Set<TestStateElement>();
constructor(
public readonly state: TestRunState,
public readonly test: IStatusTestItem,
public readonly parentItem: TestStateElement | StateElement<TestStateElement>,
) {
parentItem.children.add(this);
}
public remove() {
this.parentItem.children.delete(this);
}
}
/**
* Shows tests in a hierarchical way, but grouped by status. This is more
* complex than it may look at first glance, because nodes can appear in
* multiple places if they have children with different statuses.
*/
export class StateByLocationProjection extends AbstractIncrementalTestCollection<IStatusTestItem> implements ITestTreeProjection {
private readonly updateEmitter = new Emitter<void>();
private readonly changes = new NodeChangeList<TreeElement>();
private readonly locations = new TestLocationStore<IStatusTestItem>();
private readonly disposable = new DisposableStore();
/**
* @inheritdoc
*/
public readonly onUpdate = this.updateEmitter.event;
/**
* Root elements for states in the tree.
*/
protected readonly stateRoots = new Map<TestRunState, StateElement<TestStateElement>>();
constructor(listener: TestSubscriptionListener) {
super();
this.disposable.add(listener.onDiff(([, diff]) => this.apply(diff)));
const firstDiff: TestsDiff = [];
for (const [, collection] of listener.workspaceFolderCollections) {
firstDiff.push(...collection.getReviverDiff());
}
this.apply(firstDiff);
}
/**
* Frees listeners associated with the projection.
*/
public dispose() {
this.disposable.dispose();
}
/**
* @inheritdoc
*/
public getTestAtPosition(uri: URI, position: Position) {
const item = this.locations.getTestAtPosition(uri, position);
if (!item) {
return undefined;
}
for (const state of statesInOrder) {
const element = item.treeElements.get(state);
if (element) {
return element;
}
}
return undefined;
}
/**
* @inheritdoc
*/
public applyTo(tree: ObjectTree<ITestTreeElement, FuzzyScore>) {
this.changes.applyTo(tree, this.renderNode, () => this.stateRoots.values());
}
private readonly renderNode: NodeRenderFn<TreeElement> = (node, recurse) => {
if (node.depth === 1 /* test provider */) {
if (node.children.size === 0) {
return NodeRenderDirective.Omit;
} else if (!peersHaveChildren(node, () => this.stateRoots.values())) {
return NodeRenderDirective.Concat;
}
}
return {
element: node,
children: recurse(node.children),
};
};
/**
* @override
*/
protected createChangeCollector(): IncrementalChangeCollector<IStatusTestItem> {
return {
add: node => {
this.resolveNodesRecursive(node);
this.locations.add(node);
},
remove: (node, isNested) => {
this.locations.remove(node);
if (!isNested) {
for (const state of node.treeElements.keys()) {
this.pruneStateElements(node, state, true);
}
}
},
update: node => {
const isRunning = isRunningState(node.item.state.runState);
if (node.item.state.runState !== node.previousState) {
if (isRunning && node.treeElements.has(node.previousState)) {
node.treeElements.get(node.previousState)!.computedState = TestRunState.Running;
} else {
this.pruneStateElements(node, node.previousState);
this.resolveNodesRecursive(node);
}
} else if (!isRunning) {
const previous = node.treeElements.get(node.item.state.runState);
if (previous) {
previous.computedState = node.item.state.runState;
}
}
const locationChanged = !locationsEqual(node.location, node.item.location);
if (locationChanged) {
this.locations.remove(node);
node.location = node.item.location;
this.locations.add(node);
}
const treeNode = node.treeElements.get(node.previousState)!;
this.changes.updated(treeNode);
},
complete: () => {
this.updateEmitter.fire();
}
};
}
/**
* Ensures tree nodes for the item state are present in the tree.
*/
protected resolveNodesRecursive(item: IStatusTestItem) {
const state = item.item.state.runState;
item.previousState = item.item.state.runState;
// Create a list of items until the current item who don't have a tree node for the status yet
let chain: IStatusTestItem[] = [];
for (let i: IStatusTestItem | undefined = item; i && !i.treeElements.has(state); i = i.parentItem) {
chain.push(i);
}
for (let i = chain.length - 1; i >= 0; i--) {
const item2 = chain[i];
// the loop would have stopped pushing parents when either it reaches
// the root, or it reaches a parent who already has a node for this state.
const parent = item2.parentItem?.treeElements.get(state) ?? this.getOrCreateStateElement(state);
const node = this.createElement(state, item2, parent);
item2.treeElements.set(state, node);
parent.children.add(node);
if (i === chain.length - 1) {
this.changes.added(node);
}
}
}
protected createElement(state: TestRunState, item: IStatusTestItem, parent: TreeElement) {
return new TestStateElement(state, item, parent);
}
/**
* Recursively (from the leaf to the root) removes tree elements if there's
* no children who have the given state left.
*
* Returns true if it resulted in a node being removed.
*/
protected pruneStateElements(item: IStatusTestItem | undefined, state: TestRunState, force = false) {
if (!item) {
const stateRoot = this.stateRoots.get(state);
if (stateRoot?.children.size === 0) {
this.changes.removed(stateRoot);
this.stateRoots.delete(state);
return true;
}
return false;
}
const node = item.treeElements.get(state);
if (!node) {
return false;
}
// Check to make sure we aren't in the state, and there's no child with the
// state. For the unset state, only show the node if it's a leaf or it
// has children in the unset state.
if (!force) {
if (item.item.state.runState === state && !(state === TestRunState.Unset && item.children.size > 0)) {
return false;
}
for (const childId of item.children) {
if (this.items.get(childId)?.treeElements.has(state)) {
return false;
}
}
}
// If so, proceed to deletion and recurse upwards.
item.treeElements.delete(state);
node.remove();
if (!this.pruneStateElements(item.parentItem, state)) {
this.changes.removed(node);
}
return true;
}
protected getOrCreateStateElement(state: TestRunState) {
let s = this.stateRoots.get(state);
if (!s) {
s = new StateElement(state);
this.changes.added(s);
this.stateRoots.set(state, s);
}
return s;
}
protected createItem(item: InternalTestItem, parentItem?: IStatusTestItem): IStatusTestItem {
return {
...item,
depth: parentItem ? parentItem.depth + 1 : 1,
parentItem: parentItem,
previousState: item.item.state.runState,
location: item.item.location,
children: new Set(),
treeElements: new Map(),
};
}
}

View file

@ -1,289 +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 { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { Emitter } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { Iterable } from 'vs/base/common/iterator';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position';
import { Location as ModeLocation } from 'vs/editor/common/modes';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { ListElementType } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/browser/explorerProjections/locationStore';
import { NodeChangeList, NodeRenderFn } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { isRunningState } from 'vs/workbench/contrib/testing/common/testingStates';
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
class ListTestStateElement implements ITestTreeElement {
public computedState = this.test.item.state.runState;
public get treeId() {
return `sntest:${this.test.id}`;
}
public get label() {
return this.test.item.label;
}
public get location() {
return this.test.item.location;
}
public get runnable(): Iterable<TestIdWithProvider> {
return this.test.item.runnable
? [{ testId: this.test.id, providerId: this.test.providerId }]
: Iterable.empty();
}
public get debuggable(): Iterable<TestIdWithProvider> {
return this.test.item.debuggable
? [{ testId: this.test.id, providerId: this.test.providerId }]
: Iterable.empty();
}
public get description() {
let description: string | undefined;
for (let parent = this.test.parentItem; parent && parent.depth > 0; parent = parent.parentItem) {
description = description ? `${parent.item.label} ${description}` : parent.item.label;
}
return description;
}
public readonly depth = 1;
public readonly children = new Set<never>();
constructor(
public readonly test: IStatusListTestItem,
public readonly parentItem: StateElement<ListTestStateElement>,
) {
parentItem.children.add(this);
}
public remove() {
this.parentItem.children.delete(this);
}
}
interface IStatusListTestItem extends IncrementalTestCollectionItem {
node?: ListTestStateElement;
type: ListElementType;
previousState: TestRunState;
depth: number;
parentItem?: IStatusListTestItem;
location?: ModeLocation;
}
type TreeElement = StateElement<ListTestStateElement> | ListTestStateElement;
/**
* Projection that shows tests in a flat list (grouped by status).
*/
export class StateByNameProjection extends AbstractIncrementalTestCollection<IStatusListTestItem> implements ITestTreeProjection {
private readonly updateEmitter = new Emitter<void>();
private readonly changes = new NodeChangeList<TreeElement>();
private readonly locations = new TestLocationStore<IStatusListTestItem>();
private readonly disposable = new DisposableStore();
/**
* @inheritdoc
*/
public readonly onUpdate = this.updateEmitter.event;
/**
* Root elements for states in the tree.
*/
protected readonly stateRoots = new Map<TestRunState, StateElement<ListTestStateElement>>();
constructor(listener: TestSubscriptionListener) {
super();
this.disposable.add(listener.onDiff(([, diff]) => this.apply(diff)));
const firstDiff: TestsDiff = [];
for (const [, collection] of listener.workspaceFolderCollections) {
firstDiff.push(...collection.getReviverDiff());
}
this.apply(firstDiff);
}
/**
* Frees listeners associated with the projection.
*/
public dispose() {
this.disposable.dispose();
}
/**
* @inheritdoc
*/
public getTestAtPosition(uri: URI, position: Position) {
return this.locations.getTestAtPosition(uri, position)?.node;
}
/**
* @inheritdoc
*/
public applyTo(tree: ObjectTree<ITestTreeElement, FuzzyScore>) {
this.changes.applyTo(tree, this.renderNode, () => this.stateRoots.values());
}
private readonly renderNode: NodeRenderFn<TreeElement> = (node, recurse) => {
return {
element: node,
children: node instanceof StateElement ? recurse(node.children) : undefined,
};
};
/**
* @override
*/
protected createChangeCollector(): IncrementalChangeCollector<IStatusListTestItem> {
return {
add: node => {
this.resolveNodesRecursive(node);
this.locations.add(node);
},
remove: (node, isNested) => {
if (node.node) {
this.locations.remove(node);
}
// for the top node being deleted, we need to update parents. For
// others we only need to remove them from the locations cache.
if (isNested) {
this.removeNodeSingle(node);
} else {
this.removeNode(node);
}
},
update: node => {
if (node.item.state.runState !== node.previousState && node.node) {
if (isRunningState(node.item.state.runState)) {
node.node.computedState = node.item.state.runState;
} else {
this.removeNode(node);
}
}
node.previousState = node.item.state.runState;
this.resolveNodesRecursive(node);
const locationChanged = !locationsEqual(node.location, node.item.location);
if (locationChanged) {
this.locations.remove(node);
node.location = node.item.location;
this.locations.add(node);
}
if (node.node) {
this.changes.updated(node.node);
}
},
complete: () => {
this.updateEmitter.fire();
}
};
}
/**
* Ensures tree nodes for the item state are present in the tree.
*/
protected resolveNodesRecursive(item: IStatusListTestItem) {
const newType = Iterable.some(item.children, c => this.items.get(c)?.type !== ListElementType.BranchWithoutLeaf)
? ListElementType.BranchWithLeaf
: item.item.runnable
? ListElementType.TestLeaf
: ListElementType.BranchWithoutLeaf;
if (newType === item.type) {
return;
}
const isVisible = newType === ListElementType.TestLeaf;
const wasVisible = item.type === ListElementType.TestLeaf;
item.type = newType;
if (!isVisible && wasVisible && item.node) {
this.removeNodeSingle(item);
} else if (isVisible && !wasVisible) {
const state = item.item.state.runState;
item.node = item.node || new ListTestStateElement(item, this.getOrCreateStateElement(state));
this.changes.added(item.node);
}
if (item.parentItem) {
this.resolveNodesRecursive(item.parentItem);
}
}
/**
* Recursively (from the leaf to the root) removes tree elements if there's
* no children who have the given state left.
*
* Returns true if it resulted in a node being removed.
*/
private removeNode(item: IStatusListTestItem) {
if (!item.node) {
return;
}
this.removeNodeSingle(item);
if (item.parentItem) {
this.resolveNodesRecursive(item.parentItem);
}
}
private removeNodeSingle(item: IStatusListTestItem) {
if (!item.node) {
return;
}
item.node.remove();
this.changes.removed(item.node);
const parent = item.node.parentItem;
item.node = undefined;
item.type = ListElementType.Unset;
if (parent.children.size === 0) {
this.changes.removed(parent);
this.stateRoots.delete(parent.state);
}
}
private getOrCreateStateElement(state: TestRunState) {
let s = this.stateRoots.get(state);
if (!s) {
s = new StateElement(state);
this.changes.added(s);
this.stateRoots.set(state, s);
}
return s;
}
/**
* @override
*/
protected createItem(item: InternalTestItem, parentItem?: IStatusListTestItem): IStatusListTestItem {
return {
...item,
type: ListElementType.Unset,
depth: parentItem ? parentItem.depth + 1 : 0,
parentItem: parentItem,
previousState: item.item.state.runState,
location: item.item.location,
children: new Set(),
};
}
}

View file

@ -1,35 +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 { Iterable } from 'vs/base/common/iterator';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { testStateNames } from 'vs/workbench/contrib/testing/common/constants';
/**
* Base state node element, used in both name and location grouping.
*/
export class StateElement<T extends ITestTreeElement> implements ITestTreeElement {
public computedState = this.state;
public get treeId() {
return `sestate:${this.state}`;
}
public readonly depth = 0;
public readonly label = testStateNames[this.state];
public readonly parentItem = null;
public readonly children = new Set<T>();
public get runnable() {
return Iterable.concatNested(Iterable.map(this.children, c => c.runnable));
}
public get debuggable() {
return Iterable.concatNested(Iterable.map(this.children, c => c.debuggable));
}
constructor(public readonly state: TestRunState) { }
}

View file

@ -20,10 +20,10 @@ export const testingShowAsList = registerIcon('testing-show-as-list-icon', Codic
export const testingShowAsTree = registerIcon('testing-show-as-list-icon', Codicon.listFlat, localize('testingShowAsTree', 'Icon shown when the test explorer is disabled as a list.'));
export const testingStatesToIcons = new Map<TestRunState, ThemeIcon>([
[TestRunState.Errored, registerIcon('testing-error-icon', Codicon.warning, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))],
[TestRunState.Failed, registerIcon('testing-failed-icon', Codicon.close, localize('testingFailedIcon', 'Icon shown for tests that failed.'))],
[TestRunState.Errored, registerIcon('testing-error-icon', Codicon.issues, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))],
[TestRunState.Failed, registerIcon('testing-failed-icon', Codicon.error, localize('testingFailedIcon', 'Icon shown for tests that failed.'))],
[TestRunState.Passed, registerIcon('testing-passed-icon', Codicon.pass, localize('testingPassedIcon', 'Icon shown for tests that passed.'))],
[TestRunState.Queued, registerIcon('testing-queued-icon', Codicon.watch, localize('testingQueuedIcon', 'Icon shown for tests that are queued.'))],
[TestRunState.Queued, registerIcon('testing-queued-icon', Codicon.history, localize('testingQueuedIcon', 'Icon shown for tests that are queued.'))],
[TestRunState.Running, ThemeIcon.modify(Codicon.loading, 'spin')],
[TestRunState.Skipped, registerIcon('testing-skipped-icon', Codicon.debugStepOver, localize('testingSkippedIcon', 'Icon shown for tests that are skipped.'))],
[TestRunState.Unset, registerIcon('testing-unset-icon', Codicon.circleOutline, localize('testingUnsetIcon', 'Icon shown for tests that are in an unset state.'))],

View file

@ -21,9 +21,10 @@ import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
import { FocusedViewContext } from 'vs/workbench/common/views';
import * as icons from 'vs/workbench/contrib/testing/browser/icons';
import { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
import { TestExplorerViewGrouping, TestExplorerViewMode, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { EMPTY_TEST_RESULT, InternalTestItem, RunTestsResult, TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestExplorerViewSorting, TestExplorerViewMode, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { InternalTestItem, TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestResult, ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService, waitForAllRoots } from 'vs/workbench/contrib/testing/common/testService';
import { IWorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
@ -101,10 +102,10 @@ abstract class RunOrDebugAction extends ViewAction<TestingExplorerView> {
/**
* @override
*/
public runInView(accessor: ServicesAccessor, view: TestingExplorerView): Promise<RunTestsResult> {
public runInView(accessor: ServicesAccessor, view: TestingExplorerView): Promise<ITestResult | undefined> {
const tests = this.getActionableTests(accessor.get(IWorkspaceTestCollectionService), view.viewModel);
if (!tests.length) {
return Promise.resolve(EMPTY_TEST_RESULT);
return Promise.resolve(undefined);
}
return accessor.get(ITestService).runTests({ tests, debug: this.debug });
@ -327,18 +328,18 @@ export class TestingViewAsTreeAction extends ViewAction<TestingExplorerView> {
}
export class TestingGroupByLocationAction extends ViewAction<TestingExplorerView> {
export class TestingSortByNameAction extends ViewAction<TestingExplorerView> {
constructor() {
super({
id: 'testing.groupByLocation',
id: 'testing.sortByName',
viewId: Testing.ExplorerViewId,
title: localize('testing.groupByLocation', "Sort by Name"),
title: localize('testing.sortByName', "Sort by Name"),
f1: false,
toggled: TestingContextKeys.viewGrouping.isEqualTo(TestExplorerViewGrouping.ByLocation),
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByName),
menu: {
id: MenuId.ViewTitle,
order: 10,
group: 'groupBy',
group: 'sortBy',
when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId)
}
});
@ -348,22 +349,22 @@ export class TestingGroupByLocationAction extends ViewAction<TestingExplorerView
* @override
*/
public runInView(_accessor: ServicesAccessor, view: TestingExplorerView) {
view.viewModel.viewGrouping = TestExplorerViewGrouping.ByLocation;
view.viewModel.viewSorting = TestExplorerViewSorting.ByName;
}
}
export class TestingGroupByStatusAction extends ViewAction<TestingExplorerView> {
export class TestingSortByLocationAction extends ViewAction<TestingExplorerView> {
constructor() {
super({
id: 'testing.groupByStatus',
id: 'testing.sortByLocation',
viewId: Testing.ExplorerViewId,
title: localize('testing.groupByStatus', "Sort by Status"),
title: localize('testing.sortByLocation', "Sort by Location"),
f1: false,
toggled: TestingContextKeys.viewGrouping.isEqualTo(TestExplorerViewGrouping.ByStatus),
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByLocation),
menu: {
id: MenuId.ViewTitle,
order: 10,
group: 'groupBy',
group: 'sortBy',
when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId)
}
});
@ -373,7 +374,7 @@ export class TestingGroupByStatusAction extends ViewAction<TestingExplorerView>
* @override
*/
public runInView(_accessor: ServicesAccessor, view: TestingExplorerView) {
view.viewModel.viewGrouping = TestExplorerViewGrouping.ByStatus;
view.viewModel.viewSorting = TestExplorerViewSorting.ByLocation;
}
}
@ -427,6 +428,24 @@ export class RefreshTestsAction extends Action2 {
}
}
export class ClearTestResultsAction extends Action2 {
constructor() {
super({
id: 'testing.clearTestResults',
title: localize('testing.clearResults', "Clear All Results"),
category,
f1: true
});
}
/**
* @override
*/
public run(accessor: ServicesAccessor) {
accessor.get(ITestResultService).clear();
}
}
export class EditFocusedTest extends ViewAction<TestingExplorerView> {
constructor() {
super({

View file

@ -83,13 +83,14 @@ registerAction2(Action.TestingViewAsTreeAction);
registerAction2(Action.CancelTestRunAction);
registerAction2(Action.RunSelectedAction);
registerAction2(Action.DebugSelectedAction);
registerAction2(Action.TestingGroupByLocationAction);
registerAction2(Action.TestingGroupByStatusAction);
registerAction2(Action.TestingSortByNameAction);
registerAction2(Action.TestingSortByLocationAction);
registerAction2(Action.RefreshTestsAction);
registerAction2(Action.CollapseAllAction);
registerAction2(Action.RunAllAction);
registerAction2(Action.DebugAllAction);
registerAction2(Action.EditFocusedTest);
registerAction2(Action.ClearTestResultsAction);
registerAction2(CloseTestPeek);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Eventually);

View file

@ -28,8 +28,8 @@ import { testingRunAllIcon, testingRunIcon, testingStatesToIcons } from 'vs/work
import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
import { testMessageSeverityColors } from 'vs/workbench/contrib/testing/browser/theme';
import { IncrementalTestCollectionItem, ITestMessage } from 'vs/workbench/contrib/testing/common/testCollection';
import { maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { IMainThreadTestCollection, ITestService } from 'vs/workbench/contrib/testing/common/testService';
export class TestingDecorations extends Disposable implements IEditorContribution {
@ -39,6 +39,7 @@ export class TestingDecorations extends Disposable implements IEditorContributio
constructor(
private readonly editor: ICodeEditor,
@ITestService private readonly testService: ITestService,
@ITestResultService private readonly results: ITestResultService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
@ -62,6 +63,15 @@ export class TestingDecorations extends Disposable implements IEditorContributio
}
this.collection.value = this.testService.subscribeToDiffs(ExtHostTestingResource.TextDocument, uri, () => this.setDecorations(uri));
this._register(this.results.onTestChanged(([, changed]) => {
if (changed.item.location?.uri.toString() === uri.toString()) {
this.setDecorations(uri);
}
}));
this._register(this.results.onResultsChanged(() => {
this.setDecorations(uri);
}));
this.setDecorations(uri);
}
@ -74,19 +84,25 @@ export class TestingDecorations extends Disposable implements IEditorContributio
this.editor.changeDecorations(accessor => {
const newDecorations: ITestDecoration[] = [];
for (const test of ref.object.all) {
const stateLookup = this.results.getStateByExtId(test.item.extId);
if (hasValidLocation(uri, test.item)) {
newDecorations.push(this.instantiationService.createInstance(
RunTestDecoration, test, ref.object, test.item.location, this.editor));
RunTestDecoration, test, ref.object, test.item.location, this.editor, stateLookup?.[1].computedState));
}
for (let i = 0; i < test.item.state.messages.length; i++) {
const m = test.item.state.messages[i];
if (!stateLookup) {
continue;
}
const [result, stateItem] = stateLookup;
for (let i = 0; i < stateItem.state.messages.length; i++) {
const m = stateItem.state.messages[i];
if (hasValidLocation(uri, m)) {
const uri = buildTestUri({
type: TestUriType.LiveMessage,
type: TestUriType.ResultActualOutput,
messageIndex: i,
providerId: test.providerId,
testId: test.id,
resultId: result.id,
testId: stateItem.item.extId,
});
newDecorations.push(this.instantiationService.createInstance(TestMessageDecoration, m, uri, m.location, this.editor));
@ -138,7 +154,7 @@ const firstLineRange = (originalRange: IRange) => ({
endColumn: 1,
});
class RunTestDecoration implements ITestDecoration {
class RunTestDecoration extends Disposable implements ITestDecoration {
/**
* @inheritdoc
*/
@ -156,25 +172,16 @@ class RunTestDecoration implements ITestDecoration {
private readonly collection: IMainThreadTestCollection,
private readonly location: ModeLocation,
private readonly editor: ICodeEditor,
computedState: TestRunState | undefined,
@ITestService private readonly testService: ITestService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
this.line = location.range.startLineNumber;
const queue = [test.children];
let state = this.test.item.state.runState;
while (queue.length) {
for (const child of queue.pop()!) {
const node = collection.getNodeById(child);
if (node) {
state = maxPriority(node.item.state.runState, state);
}
}
}
const icon = state !== TestRunState.Unset
? testingStatesToIcons.get(state)!
const icon = computedState !== undefined && computedState !== TestRunState.Unset
? testingStatesToIcons.get(computedState)!
: test.children.size > 0 ? testingRunAllIcon : testingRunIcon;
this.editorDecoration = {

View file

@ -5,12 +5,12 @@
import * as dom from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { DefaultKeyboardNavigationDelegate, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { Action, IAction, IActionViewItem } from 'vs/base/common/actions';
import { DeferredPromise } from 'vs/base/common/async';
import { Color, RGBA } from 'vs/base/common/color';
@ -47,14 +47,10 @@ import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/comm
import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation';
import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
import { getComputedState } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes';
import { StateByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateByLocation';
import { StateByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateByName';
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
import { testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
import { ITestExplorerFilterState, TestExplorerFilterState, TestingExplorerFilter } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
import { TestExplorerViewGrouping, TestExplorerViewMode, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants';
import { TestExplorerViewMode, TestExplorerViewSorting, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
@ -188,7 +184,7 @@ export class TestingExplorerViewModel extends Disposable {
public projection!: ITestTreeProjection;
private readonly _viewMode = TestingContextKeys.viewMode.bindTo(this.contextKeyService);
private readonly _viewGrouping = TestingContextKeys.viewGrouping.bindTo(this.contextKeyService);
private readonly _viewSorting = TestingContextKeys.viewSorting.bindTo(this.contextKeyService);
/**
* Fires when the selected tests change.
@ -210,18 +206,18 @@ export class TestingExplorerViewModel extends Disposable {
}
public get viewGrouping() {
return this._viewGrouping.get() ?? TestExplorerViewGrouping.ByLocation;
public get viewSorting() {
return this._viewSorting.get() ?? TestExplorerViewSorting.ByLocation;
}
public set viewGrouping(newGrouping: TestExplorerViewGrouping) {
if (newGrouping === this._viewGrouping.get()) {
public set viewSorting(newSorting: TestExplorerViewSorting) {
if (newSorting === this._viewSorting.get()) {
return;
}
this._viewGrouping.set(newGrouping);
this.updatePreferredProjection();
this.storageService.store('testing.viewGrouping', newGrouping, StorageScope.WORKSPACE, StorageTarget.USER);
this._viewSorting.set(newSorting);
this.tree.resort(null);
this.storageService.store('testing.viewSorting', newSorting, StorageScope.WORKSPACE, StorageTarget.USER);
}
constructor(
@ -229,16 +225,17 @@ export class TestingExplorerViewModel extends Disposable {
onDidChangeVisibility: Event<boolean>,
private listener: TestSubscriptionListener | undefined,
@ITestExplorerFilterState filterState: TestExplorerFilterState,
@IInstantiationService instantiationService: IInstantiationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@IStorageService private readonly storageService: IStorageService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@ITestResultService private readonly testResults: ITestResultService,
) {
super();
this._viewMode.set(this.storageService.get('testing.viewMode', StorageScope.WORKSPACE, TestExplorerViewMode.Tree) as TestExplorerViewMode);
this._viewGrouping.set(this.storageService.get('testing.viewGrouping', StorageScope.WORKSPACE, TestExplorerViewGrouping.ByLocation) as TestExplorerViewGrouping);
this._viewSorting.set(this.storageService.get('testing.viewSorting', StorageScope.WORKSPACE, TestExplorerViewSorting.ByLocation) as TestExplorerViewSorting);
const labels = this._register(instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: onDidChangeVisibility }));
@ -261,7 +258,7 @@ export class TestingExplorerViewModel extends Disposable {
simpleKeyboardNavigation: true,
identityProvider: instantiationService.createInstance(IdentityProvider),
hideTwistiesOfChildlessElements: true,
sorter: instantiationService.createInstance(TreeSorter),
sorter: instantiationService.createInstance(TreeSorter, this),
keyboardNavigationLabelProvider: instantiationService.createInstance(TreeKeyboardNavigationLabelProvider),
accessibilityProvider: instantiationService.createInstance(ListAccessibilityProvider),
filter: this.filter,
@ -293,6 +290,10 @@ export class TestingExplorerViewModel extends Disposable {
tracker.deactivate();
}
}));
this._register(testResults.onResultsChanged(() => {
this.tree.resort(null);
}));
}
/**
@ -380,16 +381,18 @@ export class TestingExplorerViewModel extends Disposable {
* Tries to peek the first test error, if the item is in a failed state.
*/
private async tryPeekError(item: ITestTreeElement) {
if (!item.test || !isFailedState(item.test.item.state.runState)) {
const lookup = item.test && this.testResults.getStateByExtId(item.test.item.extId);
if (!lookup || !isFailedState(lookup[1].state.state)) {
return false;
}
const index = item.test.item.state.messages.findIndex(m => !!m.location);
const [result, test] = lookup;
const index = test.state.messages.findIndex(m => !!m.location);
if (index === -1) {
return;
}
const message = item.test.item.state.messages[index];
const message = test.state.messages[index];
const pane = await this.editorService.openEditor({
resource: message.location!.uri,
options: { selection: message.location!.range, preserveFocus: true }
@ -401,10 +404,10 @@ export class TestingExplorerViewModel extends Disposable {
}
TestingOutputPeekController.get(control).show(buildTestUri({
type: TestUriType.LiveMessage,
type: TestUriType.ResultMessage,
messageIndex: index,
providerId: item.test.providerId,
testId: item.test.id,
resultId: result.id,
testId: item.test!.item.extId,
}));
return true;
@ -417,18 +420,10 @@ export class TestingExplorerViewModel extends Disposable {
return;
}
if (this._viewGrouping.get() === TestExplorerViewGrouping.ByLocation) {
if (this._viewMode.get() === TestExplorerViewMode.List) {
this.projection = new HierarchicalByNameProjection(this.listener);
} else {
this.projection = new HierarchicalByLocationProjection(this.listener);
}
if (this._viewMode.get() === TestExplorerViewMode.List) {
this.projection = this.instantiationService.createInstance(HierarchicalByNameProjection, this.listener);
} else {
if (this._viewMode.get() === TestExplorerViewMode.List) {
this.projection = new StateByNameProjection(this.listener);
} else {
this.projection = new StateByLocationProjection(this.listener);
}
this.projection = this.instantiationService.createInstance(HierarchicalByLocationProjection, this.listener);
}
this.projection.onUpdate(this.deferUpdate, this);
@ -569,9 +564,19 @@ class TestsFilter implements ITreeFilter<ITestTreeElement> {
}
class TreeSorter implements ITreeSorter<ITestTreeElement> {
constructor(private readonly viewModel: TestingExplorerViewModel) { }
public compare(a: ITestTreeElement, b: ITestTreeElement): number {
if (a instanceof StateElement && b instanceof StateElement) {
return cmpPriority(a.computedState, b.computedState);
let delta = cmpPriority(a.state, b.state);
if (delta !== 0) {
return delta;
}
if (this.viewModel.viewSorting === TestExplorerViewSorting.ByLocation && a.location && b.location && a.location.uri.toString() === b.location.uri.toString()) {
delta = a.location.range.startLineNumber - b.location.range.startLineNumber;
if (delta !== 0) {
return delta;
}
}
return a.label.localeCompare(b.label);
@ -587,7 +592,7 @@ class ListAccessibilityProvider implements IListAccessibilityProvider<ITestTreeE
return localize({
key: 'testing.treeElementLabel',
comment: ['label then the unit tests state, for example "Addition Tests (Running)"'],
}, '{0} ({1})', element.label, testStateNames[getComputedState(element)]);
}, '{0} ({1})', element.label, testStateNames[element.state]);
}
}
@ -662,8 +667,7 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
const options: IResourceLabelOptions = {};
data.actionBar.clear();
const state = getComputedState(element);
const icon = testingStatesToIcons.get(state);
const icon = testingStatesToIcons.get(element.state);
data.icon.className = 'computed-state ' + (icon ? ThemeIcon.asClassName(icon) : '');
const test = element.test;
if (test) {
@ -683,7 +687,7 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
options.fileKind = FileKind.ROOT_FOLDER;
}
const running = state === TestRunState.Running;
const running = element.state === TestRunState.Running;
if (!Iterable.isEmpty(element.runnable)) {
data.actionBar.push(
this.instantiationService.createInstance(RunAction, element.runnable, running),
@ -740,12 +744,16 @@ const getProgressText = ({ passed, runSoFar, skipped, failed }: CountSummary) =>
class TestRunProgress {
private current?: { update: IProgress<IProgressStep>; deferred: DeferredPromise<void> };
private badge = new MutableDisposable();
private readonly resultLister = this.resultService.onNewTestResult(result => {
private readonly resultLister = this.resultService.onResultsChanged(result => {
if (!('started' in result)) {
return;
}
this.updateProgress();
this.updateBadge();
result.onChange(this.throttledProgressUpdate, this);
result.onComplete(() => {
result.started.onChange(this.throttledProgressUpdate, this);
result.started.onComplete(() => {
this.throttledProgressUpdate();
this.updateBadge();
});

View file

@ -27,15 +27,15 @@ import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeServic
import { EditorModel } from 'vs/workbench/common/editor';
import { testingPeekBorder } from 'vs/workbench/contrib/testing/browser/theme';
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
import { InternalTestItem, ITestMessage } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestItem, ITestMessage, ITestState } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { buildTestUri, parseTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
interface ITestDto {
test: ITestItem,
messageIndex: number;
test: InternalTestItem;
state: ITestState;
expectedUri: URI;
actualUri: URI;
messageUri: URI;
@ -66,7 +66,6 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo
private readonly editor: ICodeEditor,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ITestResultService private readonly testResults: ITestResultService,
@ITestService private readonly testService: ITestService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
super();
@ -83,7 +82,7 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo
return;
}
const message = dto.test.item.state.messages[dto.messageIndex];
const message = dto.state.messages[dto.messageIndex];
if (!message?.location) {
return;
}
@ -120,28 +119,14 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo
return undefined;
}
if ('resultId' in parts) {
const test = this.testResults.lookup(parts.resultId)?.tests.find(t => t.id === parts.testId);
return test && {
test,
messageIndex: parts.messageIndex,
expectedUri: buildTestUri({ ...parts, type: TestUriType.ResultExpectedOutput }),
actualUri: buildTestUri({ ...parts, type: TestUriType.ResultActualOutput }),
messageUri: buildTestUri({ ...parts, type: TestUriType.ResultMessage }),
};
}
const test = await this.testService.lookupTest({ providerId: parts.providerId, testId: parts.testId });
if (!test) {
return;
}
return {
test,
const test = this.testResults.getResult(parts.resultId)?.getStateByExtId(parts.testId);
return test && {
test: test.item,
state: test.state,
messageIndex: parts.messageIndex,
expectedUri: buildTestUri({ ...parts, type: TestUriType.LiveActualOutput }),
actualUri: buildTestUri({ ...parts, type: TestUriType.LiveExpectedOutput }),
messageUri: buildTestUri({ ...parts, type: TestUriType.LiveMessage }),
expectedUri: buildTestUri({ ...parts, type: TestUriType.ResultExpectedOutput }),
actualUri: buildTestUri({ ...parts, type: TestUriType.ResultActualOutput }),
messageUri: buildTestUri({ ...parts, type: TestUriType.ResultMessage }),
};
}
}
@ -236,14 +221,14 @@ class TestingDiffOutputPeek extends TestingOutputPeek {
/**
* @override
*/
public async setModel({ test, messageIndex, expectedUri, actualUri }: ITestDto) {
const message = test.item.state.messages[messageIndex];
public async setModel({ test, state, messageIndex, expectedUri, actualUri }: ITestDto) {
const message = state.messages[messageIndex];
if (!message?.location) {
return;
}
this.show(message.location.range, hintDiffPeekHeight(message));
this.setTitle(message.message.toString(), test.item.label);
this.setTitle(message.message.toString(), test.label);
const [original, modified] = await Promise.all([
this.modelService.createModelReference(expectedUri),
@ -285,14 +270,14 @@ class TestingMessageOutputPeek extends TestingOutputPeek {
/**
* @override
*/
public async setModel({ test, messageIndex, messageUri }: ITestDto) {
const message = test.item.state.messages[messageIndex];
public async setModel({ state, test, messageIndex, messageUri }: ITestDto) {
const message = state.messages[messageIndex];
if (!message?.location) {
return;
}
this.show(message.location.range, hintPeekStrHeight(message.message.toString()));
this.setTitle(message.message.toString(), test.item.label);
this.setTitle(message.message.toString(), test.label);
const modelRef = this.model.value = await this.modelService.createModelReference(messageUri);
if (this.preview.value) {

Some files were not shown because too many files have changed in this diff Show more