Merge branch 'main' into joh/voluminous-lobster

This commit is contained in:
Johannes 2022-06-09 16:47:09 +02:00
commit eda80f2065
No known key found for this signature in database
GPG key ID: 6DEF802A22264FCA
24 changed files with 445 additions and 120 deletions

View file

@ -110,7 +110,7 @@ const tasks = compilations.map(function (tsconfigFile) {
overrideOptions.inlineSources = Boolean(build);
overrideOptions.base = path.dirname(absolutePath);
const compilation = tsb.create(absolutePath, overrideOptions, false, err => reporter(err.toString()));
const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false }, err => reporter(err.toString()));
const pipeline = function () {
const input = es.through();

View file

@ -42,7 +42,7 @@ function createCompile(src, build, emitError) {
if (!build) {
overrideOptions.inlineSourceMap = true;
}
const compilation = tsb.create(projectPath, overrideOptions, false, err => reporter(err));
const compilation = tsb.create(projectPath, overrideOptions, { verbose: false }, err => reporter(err));
function pipeline(token) {
const bom = require('gulp-bom');
const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path));

View file

@ -50,7 +50,7 @@ function createCompile(src: string, build: boolean, emitError?: boolean) {
overrideOptions.inlineSourceMap = true;
}
const compilation = tsb.create(projectPath, overrideOptions, false, err => reporter(err));
const compilation = tsb.create(projectPath, overrideOptions, { verbose: false }, err => reporter(err));
function pipeline(token?: util.ICancellationToken) {
const bom = require('gulp-bom') as typeof import('gulp-bom');

View file

@ -9,7 +9,6 @@ const fs_1 = require("fs");
const path = require("path");
const crypto = require("crypto");
const utils = require("./utils");
const log = require("fancy-log");
const colors = require("ansi-colors");
const ts = require("typescript");
const Vinyl = require("vinyl");
@ -23,11 +22,7 @@ function normalize(path) {
return path.replace(/\\/g, '/');
}
function createTypeScriptBuilder(config, projectFile, cmd) {
function _log(topic, message) {
if (config.verbose) {
log(colors.cyan(topic), message);
}
}
const _log = config.logFn;
const host = new LanguageServiceHost(cmd, projectFile, _log);
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
const lastBuildVersion = Object.create(null);
@ -290,12 +285,10 @@ function createTypeScriptBuilder(config, projectFile, cmd) {
});
oldErrors = newErrors;
// print stats
if (config.verbose) {
const headNow = process.memoryUsage().heapUsed;
const MB = 1024 * 1024;
log('[tsb]', 'time:', colors.yellow((Date.now() - t1) + 'ms'), 'mem:', colors.cyan(Math.ceil(headNow / MB) + 'MB'), colors.bgCyan('delta: ' + Math.ceil((headNow - headUsed) / MB)));
headUsed = headNow;
}
const headNow = process.memoryUsage().heapUsed;
const MB = 1024 * 1024;
_log('[tsb]', `time: ${colors.yellow((Date.now() - t1) + 'ms')} + \nmem: ${colors.cyan(Math.ceil(headNow / MB) + 'MB')} ${colors.bgCyan('delta: ' + Math.ceil((headNow - headUsed) / MB))}`);
headUsed = headNow;
});
}
return {

View file

@ -7,13 +7,12 @@ import { statSync, readFileSync } from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import * as utils from './utils';
import * as log from 'fancy-log';
import * as colors from 'ansi-colors';
import * as ts from 'typescript';
import * as Vinyl from 'vinyl';
export interface IConfiguration {
verbose: boolean;
logFn: (topic: string, message: string) => void;
_emitWithoutBasePath?: boolean;
}
@ -39,11 +38,7 @@ function normalize(path: string): string {
export function createTypeScriptBuilder(config: IConfiguration, projectFile: string, cmd: ts.ParsedCommandLine): ITypeScriptBuilder {
function _log(topic: string, message: string): void {
if (config.verbose) {
log(colors.cyan(topic), message);
}
}
const _log = config.logFn;
const host = new LanguageServiceHost(cmd, projectFile, _log);
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
@ -57,7 +52,6 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
// always emit declaraction files
host.getCompilationSettings().declaration = true;
function file(file: Vinyl): void {
// support gulp-sourcemaps
if ((<any>file).sourceMap) {
@ -355,15 +349,13 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
oldErrors = newErrors;
// print stats
if (config.verbose) {
const headNow = process.memoryUsage().heapUsed;
const MB = 1024 * 1024;
log('[tsb]',
'time:', colors.yellow((Date.now() - t1) + 'ms'),
'mem:', colors.cyan(Math.ceil(headNow / MB) + 'MB'), colors.bgCyan('delta: ' + Math.ceil((headNow - headUsed) / MB))
);
headUsed = headNow;
}
const headNow = process.memoryUsage().heapUsed;
const MB = 1024 * 1024;
_log(
'[tsb]',
`time: ${colors.yellow((Date.now() - t1) + 'ms')} + \nmem: ${colors.cyan(Math.ceil(headNow / MB) + 'MB')} ${colors.bgCyan('delta: ' + Math.ceil((headNow - headUsed) / MB))}`
);
headUsed = headNow;
});
}

View file

@ -13,6 +13,8 @@ const stream_1 = require("stream");
const path_1 = require("path");
const utils_1 = require("./utils");
const fs_1 = require("fs");
const log = require("fancy-log");
const colors = require("ansi-colors");
class EmptyDuplex extends stream_1.Duplex {
_write(_chunk, _encoding, callback) { callback(); }
_read() { this.push(null); }
@ -23,7 +25,7 @@ function createNullCompiler() {
return result;
}
const _defaultOnError = (err) => console.log(JSON.stringify(err, null, 4));
function create(projectPath, existingOptions, verbose = false, onError = _defaultOnError) {
function create(projectPath, existingOptions, config, onError = _defaultOnError) {
function printDiagnostic(diag) {
if (!diag.file || !diag.start) {
onError(ts.flattenDiagnosticMessageText(diag.messageText, '\n'));
@ -43,8 +45,14 @@ function create(projectPath, existingOptions, verbose = false, onError = _defaul
cmdLine.errors.forEach(printDiagnostic);
return createNullCompiler();
}
const _builder = builder.createTypeScriptBuilder({ verbose }, projectPath, cmdLine);
function createStream(token) {
function logFn(topic, message) {
if (config.verbose) {
log(colors.cyan(topic), message);
}
}
// FULL COMPILE stream doing transpile, syntax and semantic diagnostics
function createCompileStream(token) {
const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine);
return through(function (file) {
// give the file to the compiler
if (file.isStream()) {
@ -57,7 +65,38 @@ function create(projectPath, existingOptions, verbose = false, onError = _defaul
_builder.build(file => this.queue(file), printDiagnostic, token).catch(e => console.error(e)).then(() => this.queue(null));
});
}
const result = (token) => createStream(token);
// TRANSPILE ONLY stream doing just TS to JS conversion
function createTranspileStream() {
return through(function (file) {
// give the file to the compiler
if (file.isStream()) {
this.emit('error', 'no support for streams');
return;
}
if (!file.contents) {
return;
}
const out = ts.transpileModule(String(file.contents), {
compilerOptions: { ...cmdLine.options, declaration: false, sourceMap: false }
});
if (out.diagnostics) {
out.diagnostics.forEach(printDiagnostic);
}
const outFile = new Vinyl({
path: file.path.replace(/\.ts$/, '.js'),
cwd: file.cwd,
base: file.base,
contents: Buffer.from(out.outputText),
});
this.push(outFile);
logFn('Transpiled', file.path);
});
}
const result = (token) => {
return config.transplileOnly
? createTranspileStream()
: createCompileStream(token);
};
result.src = (opts) => {
let _pos = 0;
const _fileNames = cmdLine.fileNames.slice(0);

View file

@ -11,6 +11,8 @@ import { Readable, Writable, Duplex } from 'stream';
import { dirname } from 'path';
import { strings } from './utils';
import { readFileSync, statSync } from 'fs';
import * as log from 'fancy-log';
import colors = require('ansi-colors');
export interface IncrementalCompiler {
(token?: any): Readable & Writable;
@ -33,7 +35,7 @@ const _defaultOnError = (err: string) => console.log(JSON.stringify(err, null, 4
export function create(
projectPath: string,
existingOptions: Partial<ts.CompilerOptions>,
verbose: boolean = false,
config: { verbose?: boolean; transplileOnly?: boolean },
onError: (message: string) => void = _defaultOnError
): IncrementalCompiler {
@ -64,9 +66,16 @@ export function create(
return createNullCompiler();
}
const _builder = builder.createTypeScriptBuilder({ verbose }, projectPath, cmdLine);
function logFn(topic: string, message: string): void {
if (config.verbose) {
log(colors.cyan(topic), message);
}
}
function createStream(token?: builder.CancellationToken): Readable & Writable {
// FULL COMPILE stream doing transpile, syntax and semantic diagnostics
function createCompileStream(token?: builder.CancellationToken): Readable & Writable {
const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine);
return through(function (this: through.ThroughStream, file: Vinyl) {
// give the file to the compiler
@ -86,7 +95,48 @@ export function create(
});
}
const result = (token: builder.CancellationToken) => createStream(token);
// TRANSPILE ONLY stream doing just TS to JS conversion
function createTranspileStream(): Readable & Writable {
return through(function (this: through.ThroughStream, file: Vinyl) {
// give the file to the compiler
if (file.isStream()) {
this.emit('error', 'no support for streams');
return;
}
if (!file.contents) {
return;
}
const out = ts.transpileModule(String(file.contents), {
compilerOptions: { ...cmdLine.options, declaration: false, sourceMap: false }
});
if (out.diagnostics) {
out.diagnostics.forEach(printDiagnostic);
}
const outFile = new Vinyl({
path: file.path.replace(/\.ts$/, '.js'),
cwd: file.cwd,
base: file.base,
contents: Buffer.from(out.outputText),
});
this.push(outFile);
logFn('Transpiled', file.path);
});
}
const result = (token: builder.CancellationToken) => {
return config.transplileOnly
? createTranspileStream()
: createCompileStream(token);
};
result.src = (opts?: { cwd?: string; base?: string }) => {
let _pos = 0;
const _fileNames = cmdLine.fileNames.slice(0);

View file

@ -62,9 +62,15 @@ class Settings {
const minimapOpts = options.get(EditorOption.minimap);
const minimapEnabled = minimapOpts.enabled;
const minimapSide = minimapOpts.side;
const backgroundColor = minimapEnabled
? theme.getColor(editorOverviewRulerBackground) || TokenizationRegistry.getDefaultBackground()
: null;
const themeColor = theme.getColor(editorOverviewRulerBackground);
const defaultBackground = TokenizationRegistry.getDefaultBackground();
let backgroundColor: Color | null = null;
if (themeColor !== undefined) {
backgroundColor = themeColor;
} else if (minimapEnabled) {
backgroundColor = defaultBackground;
}
if (backgroundColor === null || minimapSide === 'left') {
this.backgroundColor = null;

View file

@ -4996,7 +4996,7 @@ export const EditorOptions = {
scrollbar: register(new EditorScrollbar()),
scrollBeyondLastColumn: register(new EditorIntOption(
EditorOption.scrollBeyondLastColumn, 'scrollBeyondLastColumn',
5, 0, Constants.MAX_SAFE_SMALL_INTEGER,
4, 0, Constants.MAX_SAFE_SMALL_INTEGER,
{ description: nls.localize('scrollBeyondLastColumn', "Controls the number of extra characters beyond which the editor will scroll horizontally.") }
)),
scrollBeyondLastLine: register(new EditorBooleanOption(

View file

@ -308,8 +308,8 @@ export class ViewLayout extends Disposable implements IViewLayout {
const options = this._configuration.options;
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const fontInfo = options.get(EditorOption.fontInfo);
const layoutInfo = options.get(EditorOption.layoutInfo);
if (wrappingInfo.isViewportWrapping) {
const layoutInfo = options.get(EditorOption.layoutInfo);
const minimap = options.get(EditorOption.minimap);
if (maxLineWidth > layoutInfo.contentWidth + fontInfo.typicalHalfwidthCharacterWidth) {
// This is a case where viewport wrapping is on, but the line extends above the viewport
@ -322,7 +322,7 @@ export class ViewLayout extends Disposable implements IViewLayout {
} else {
const extraHorizontalSpace = options.get(EditorOption.scrollBeyondLastColumn) * fontInfo.typicalHalfwidthCharacterWidth;
const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth();
return Math.max(maxLineWidth + extraHorizontalSpace, whitespaceMinWidth);
return Math.max(maxLineWidth + extraHorizontalSpace + layoutInfo.verticalScrollbarWidth, whitespaceMinWidth);
}
}

View file

@ -140,13 +140,16 @@ flakySuite('BackupMainService', () => {
return pfs.Promises.rm(testDir);
});
test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () {
test('service validates backup workspaces on startup and cleans up (folder workspaces) (1)', async function () {
// 1) backup workspace path does not exist
service.registerFolderBackupSync(toFolderBackupInfo(fooFile));
service.registerFolderBackupSync(toFolderBackupInfo(barFile));
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('service validates backup workspaces on startup and cleans up (folder workspaces) (2)', async function () {
// 2) backup workspace path exists with empty contents within
fs.mkdirSync(service.toBackupPath(fooFile));
@ -157,6 +160,9 @@ flakySuite('BackupMainService', () => {
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
});
test('service validates backup workspaces on startup and cleans up (folder workspaces) (3)', async function () {
// 3) backup workspace path exists with empty folders within
fs.mkdirSync(service.toBackupPath(fooFile));
@ -169,6 +175,9 @@ flakySuite('BackupMainService', () => {
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
});
test('service validates backup workspaces on startup and cleans up (folder workspaces) (4)', async function () {
// 4) backup workspace path points to a workspace that no longer exists
// so it should convert the backup worspace to an empty workspace backup
@ -185,13 +194,16 @@ flakySuite('BackupMainService', () => {
assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1);
});
test('service validates backup workspaces on startup and cleans up (root workspaces)', async function () {
test('service validates backup workspaces on startup and cleans up (root workspaces) (1)', async function () {
// 1) backup workspace path does not exist
service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath));
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('service validates backup workspaces on startup and cleans up (root workspaces) (2)', async function () {
// 2) backup workspace path exists with empty contents within
fs.mkdirSync(service.toBackupPath(fooFile));
@ -202,6 +214,9 @@ flakySuite('BackupMainService', () => {
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
});
test('service validates backup workspaces on startup and cleans up (root workspaces) (3)', async function () {
// 3) backup workspace path exists with empty folders within
fs.mkdirSync(service.toBackupPath(fooFile));
@ -214,6 +229,9 @@ flakySuite('BackupMainService', () => {
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
});
test('service validates backup workspaces on startup and cleans up (root workspaces) (4)', async function () {
// 4) backup workspace path points to a workspace that no longer exists
// so it should convert the backup worspace to an empty workspace backup
@ -273,13 +291,19 @@ flakySuite('BackupMainService', () => {
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => {
test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON (1)', async () => {
fs.writeFileSync(backupWorkspacesPath, '');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON (2)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{]');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON (3)', async () => {
fs.writeFileSync(backupWorkspacesPath, 'foo');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
@ -291,22 +315,37 @@ flakySuite('BackupMainService', () => {
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array', async () => {
test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (1)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{}}');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (2)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": ["bar"]}}');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (3)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": []}}');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (4)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": "bar"}}');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (5)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":"foo"}');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (6)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":1}');
await service.initialize();
assertEqualFolderInfos(service.getFolderBackupPaths(), []);
@ -333,13 +372,19 @@ flakySuite('BackupMainService', () => {
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON', async () => {
test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON (1)', async () => {
fs.writeFileSync(backupWorkspacesPath, '');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON (2)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{]');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON (3)', async () => {
fs.writeFileSync(backupWorkspacesPath, 'foo');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
@ -351,43 +396,73 @@ flakySuite('BackupMainService', () => {
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array', async () => {
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (1)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (2)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": ["bar"]}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (3)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": []}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (4)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": "bar"}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (5)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":"foo"}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (6)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":1}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array', async () => {
test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (1)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (2)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": ["bar"]}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (3)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": []}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (4)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": "bar"}}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (5)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":"foo"}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (6)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":1}');
await service.initialize();
assert.deepStrictEqual(service.getWorkspaceBackups(), []);
@ -407,13 +482,19 @@ flakySuite('BackupMainService', () => {
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => {
test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON (1)', async () => {
fs.writeFileSync(backupWorkspacesPath, '');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON (2)', async () => {
fs.writeFileSync(backupWorkspacesPath, '{]');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON (3)', async () => {
fs.writeFileSync(backupWorkspacesPath, 'foo');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
@ -425,22 +506,37 @@ flakySuite('BackupMainService', () => {
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', async function () {
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (1)', async function () {
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{}}');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (2)', async function () {
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": ["bar"]}}');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (3)', async function () {
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": []}}');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (4)', async function () {
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": "bar"}}');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (5)', async function () {
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":"foo"}');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (6)', async function () {
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":1}');
await service.initialize();
assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []);

View file

@ -99,6 +99,9 @@ export class CommandCenterControl {
// label: just workspace name and optional decorations
const { prefix, suffix } = windowTitle.getTitleDecorations();
let label = windowTitle.workspaceName;
if (!label) {
label = localize('label.dfl', "Search");
}
if (prefix) {
label = localize('label1', "{0} {1}", prefix, label);
}

View file

@ -38,6 +38,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor';
import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@ -383,12 +384,11 @@ abstract class CodeEditorView extends Disposable {
public readonly view: IView = {
element: this.htmlElements.root,
minimumWidth: 10,
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumHeight: 10,
maximumHeight: Number.MAX_SAFE_INTEGER,
minimumWidth: DEFAULT_EDITOR_MIN_DIMENSIONS.width,
maximumWidth: DEFAULT_EDITOR_MAX_DIMENSIONS.width,
minimumHeight: DEFAULT_EDITOR_MIN_DIMENSIONS.height,
maximumHeight: DEFAULT_EDITOR_MAX_DIMENSIONS.height,
onDidChange: this._onDidViewChange.event,
layout: (width: number, height: number, top: number, left: number) => {
setStyle(this.htmlElements.root, { width, height, top, left });
this.editor.layout({

View file

@ -539,6 +539,12 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP
});
}
public clearKeyboardShortcutSearchHistory(): void {
this.searchWidget.inputBox.clearHistory();
this.getMemento(StorageScope.GLOBAL, StorageTarget.USER)['searchHistory'] = this.searchWidget.inputBox.getHistory();
this.saveState();
}
private renderKeybindingsEntries(reset: boolean, preserveFocus?: boolean): void {
if (this.keybindingsEditorModel) {
const filter = this.searchWidget.getValue();

View file

@ -34,7 +34,7 @@ import { ConfigureLanguageBasedSettingsAction } from 'vs/workbench/contrib/prefe
import { SettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor';
import { preferencesOpenSettingsIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { SettingsEditor2, SettingsFocusContext } from 'vs/workbench/contrib/preferences/browser/settingsEditor2';
import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, CONTEXT_WHEN_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences';
import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, CONTEXT_WHEN_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_HISTORY, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences';
import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@ -887,6 +887,28 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_HISTORY,
title: nls.localize('clearHistory', "Clear Keyboard Shortcuts Search History"),
category,
menu: [
{
id: MenuId.CommandPalette,
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR),
}
]
});
}
run(accessor: ServicesAccessor) {
const editorPane = accessor.get(IEditorService).activeEditorPane;
if (editorPane instanceof KeybindingsEditor) {
editorPane.clearKeyboardShortcutSearchHistory();
}
}
});
this.registerKeybindingEditorActions();
}

View file

@ -56,6 +56,7 @@ export const CONTEXT_WHEN_FOCUS = new RawContextKey<boolean>('whenFocus', false)
export const KEYBINDINGS_EDITOR_COMMAND_SEARCH = 'keybindings.editor.searchKeybindings';
export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.editor.clearSearchResults';
export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_HISTORY = 'keybindings.editor.clearSearchHistory';
export const KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS = 'keybindings.editor.recordSearchKeys';
export const KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE = 'keybindings.editor.toggleSortByPrecedence';
export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding';

View file

@ -15,6 +15,12 @@
position: relative;
}
.test-explorer .test-item .label,
.test-output-peek-tree .test-peek-item .name {
display: flex;
align-items: center;
}
.test-explorer .test-item,
.test-output-peek-tree .test-peek-item {
display: flex;
@ -210,3 +216,10 @@
.test-message-inline-content-clickable {
cursor: pointer;
}
.test-label-description {
opacity: .7;
margin-left: 0.5em;
font-size: .9em;
white-space: pre;
}

View file

@ -49,6 +49,7 @@ import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testPro
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { getContextForTestItem, ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
import { stripIcons } from 'vs/base/common/iconLabels';
const MAX_INLINE_MESSAGE_LENGTH = 128;
@ -818,7 +819,7 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio
const testSubmenus = this.tests.map(({ test, resultItem }) => {
const actions = this.getTestContextMenuActions(test, resultItem);
disposable.add(actions);
return new SubmenuAction(test.item.extId, test.item.label, actions.object);
return new SubmenuAction(test.item.extId, stripIcons(test.item.label), actions.object);
});
return { object: Separator.join(allActions, testSubmenus), dispose: () => disposable.dispose() };

View file

@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { Button } from 'vs/base/browser/ui/button/button';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { DefaultKeyboardNavigationDelegate, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { AbstractTreeViewState, IAbstractTreeViewState } from 'vs/base/browser/ui/tree/abstractTree';
@ -32,7 +33,6 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { FileKind } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
@ -44,7 +44,6 @@ import { foreground } from 'vs/platform/theme/common/colorRegistry';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from 'vs/workbench/browser/labels';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IViewDescriptorService } from 'vs/workbench/common/views';
@ -58,7 +57,6 @@ import { ITestingProgressUiService } from 'vs/workbench/contrib/testing/browser/
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
import { labelForTestInState, TestCommandId, TestExplorerViewMode, TestExplorerViewSorting, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
import { InternalTestItem, ITestRunProfile, TestItemExpandState, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestExplorerFilterState, TestExplorerFilterState, TestFilterTerm } from 'vs/workbench/contrib/testing/common/testExplorerFilterState';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
@ -68,6 +66,7 @@ import { canUseProfileWithTest, ITestProfileService } from 'vs/workbench/contrib
import { TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { IMainThreadTestCollection, ITestService, testCollectionIsEmpty } from 'vs/workbench/contrib/testing/common/testService';
import { InternalTestItem, ITestRunProfile, TestItemExpandState, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export class TestingExplorerView extends ViewPane {
@ -474,8 +473,6 @@ export class TestingExplorerViewModel extends Disposable {
this._viewMode.set(this.storageService.get('testing.viewMode', StorageScope.WORKSPACE, TestExplorerViewMode.Tree) as TestExplorerViewMode);
this._viewSorting.set(this.storageService.get('testing.viewSorting', StorageScope.WORKSPACE, TestExplorerViewSorting.ByLocation) as TestExplorerViewSorting);
const labels = this._register(instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: onDidChangeVisibility }));
this.reevaluateWelcomeState();
this.filter = this.instantiationService.createInstance(TestsFilter, testService.collection);
this.tree = instantiationService.createInstance(
@ -484,7 +481,7 @@ export class TestingExplorerViewModel extends Disposable {
listContainer,
new ListDelegate(),
[
instantiationService.createInstance(TestItemRenderer, labels, this.actionRunner),
instantiationService.createInstance(TestItemRenderer, this.actionRunner),
instantiationService.createInstance(ErrorRenderer),
],
{
@ -1095,7 +1092,7 @@ class ErrorRenderer implements ITreeRenderer<TestTreeErrorMessage, FuzzyScore, I
}
interface IActionableElementTemplateData {
label: IResourceLabel;
label: HTMLElement;
icon: HTMLElement;
wrapper: HTMLElement;
actionBar: ActionBar;
@ -1106,7 +1103,6 @@ interface IActionableElementTemplateData {
abstract class ActionableItemTemplateData<T extends TestItemTreeElement> extends Disposable
implements ITreeRenderer<T, FuzzyScore, IActionableElementTemplateData> {
constructor(
protected readonly labels: ResourceLabels,
private readonly actionRunner: TestExplorerActionRunner,
@IMenuService private readonly menuService: IMenuService,
@ITestService protected readonly testService: ITestService,
@ -1129,8 +1125,7 @@ abstract class ActionableItemTemplateData<T extends TestItemTreeElement> extends
const wrapper = dom.append(container, dom.$('.test-item'));
const icon = dom.append(wrapper, dom.$('.computed-state'));
const name = dom.append(wrapper, dom.$('.name'));
const label = this.labels.create(name, { supportHighlights: true });
const label = dom.append(wrapper, dom.$('.label'));
dom.append(wrapper, dom.$(ThemeIcon.asCSSSelector(icons.testingHiddenIcon)));
const actionBar = new ActionBar(wrapper, {
@ -1141,7 +1136,7 @@ abstract class ActionableItemTemplateData<T extends TestItemTreeElement> extends
: undefined
});
return { wrapper, label, actionBar, icon, elementDisposable: [], templateDisposable: [label, actionBar] };
return { wrapper, label, actionBar, icon, elementDisposable: [], templateDisposable: [actionBar] };
}
/**
@ -1192,10 +1187,6 @@ class TestItemRenderer extends ActionableItemTemplateData<TestItemTreeElement> {
public override renderElement(node: ITreeNode<TestItemTreeElement, FuzzyScore>, depth: number, data: IActionableElementTemplateData): void {
super.renderElement(node, depth, data);
const label: IResourceLabelProps = { name: node.element.label };
const options: IResourceLabelOptions = {};
data.label.setResource(label, options);
const testHidden = this.testService.excluded.contains(node.element.test);
data.wrapper.classList.toggle('test-is-hidden', testHidden);
@ -1205,18 +1196,20 @@ class TestItemRenderer extends ActionableItemTemplateData<TestItemTreeElement> {
: node.element.state);
data.icon.className = 'computed-state ' + (icon ? ThemeIcon.asClassName(icon) : '');
label.resource = node.element.test.item.uri;
options.title = getLabelForTestTreeElement(node.element);
options.fileKind = FileKind.FILE;
label.description = node.element.description || undefined;
data.label.title = getLabelForTestTreeElement(node.element);
dom.reset(data.label, ...renderLabelWithIcons(node.element.label));
let description = node.element.description;
if (node.element.duration !== undefined) {
label.description = label.description
? `${label.description}: ${formatDuration(node.element.duration)}`
description = description
? `${description}: ${formatDuration(node.element.duration)}`
: formatDuration(node.element.duration);
}
data.label.setResource(label, options);
if (description) {
dom.append(data.label, dom.$('span.test-label-description', {}, description));
}
}
}

View file

@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom';
import { renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview';
@ -20,6 +21,7 @@ import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { stripIcons } from 'vs/base/common/iconLabels';
import { Iterable } from 'vs/base/common/iterator';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Lazy } from 'vs/base/common/lazy';
@ -53,7 +55,6 @@ import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listSe
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { flatTestItemDelimiter } from 'vs/workbench/contrib/testing/browser/explorerProjections/display';
@ -65,7 +66,6 @@ import { AutoOpenPeekViewWhen, getTestingConfiguration, TestingConfigKeys } from
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
import { IObservableValue, MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestExplorerFilterState } from 'vs/workbench/contrib/testing/common/testExplorerFilterState';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
@ -75,6 +75,7 @@ import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testPro
import { ITestResult, maxCountPriority, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
class TestDto {
@ -769,7 +770,7 @@ class TestingOutputPeek extends PeekViewWidget {
*/
public async showInPlace(dto: TestDto) {
const message = dto.messages[dto.messageIndex];
this.setTitle(firstLine(renderStringAsPlaintext(message.message)), dto.test.label);
this.setTitle(firstLine(renderStringAsPlaintext(message.message)), stripIcons(dto.test.label));
this.didReveal.fire(dto);
this.visibilityChange.fire(true);
await Promise.all(this.contentProviders.map(p => p.update(dto, message)));
@ -1084,6 +1085,7 @@ interface ITreeElement {
context: unknown;
id: string;
label: string;
labelWithIcons?: readonly (HTMLSpanElement | string)[];
icon?: ThemeIcon;
description?: string;
ariaLabel?: string;
@ -1111,6 +1113,7 @@ export class TestCaseElement implements ITreeElement {
public readonly context = this.test.item.extId;
public readonly id = `${this.results.id}/${this.test.item.extId}`;
public readonly label = this.test.item.label;
public readonly labelWithIcons = renderLabelWithIcons(this.label);
public readonly description?: string;
public get icon() {
@ -1209,7 +1212,6 @@ class OutputPeekTree extends Disposable {
super();
this.treeActions = instantiationService.createInstance(TreeActionsProvider);
const labels = instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility });
const diffIdentityProvider: IIdentityProvider<TreeElement> = {
getId(e: TreeElement) {
return e.id;
@ -1224,7 +1226,7 @@ class OutputPeekTree extends Disposable {
getHeight: () => 22,
getTemplateId: () => TestRunElementRenderer.ID,
},
[instantiationService.createInstance(TestRunElementRenderer, labels, this.treeActions)],
[instantiationService.createInstance(TestRunElementRenderer, this.treeActions)],
{
compressionEnabled: true,
hideTwistiesOfChildlessElements: true,
@ -1418,7 +1420,7 @@ class OutputPeekTree extends Disposable {
}
interface TemplateData {
label: IResourceLabel;
label: HTMLElement;
icon: HTMLElement;
actionBar: ActionBar;
elementDisposable: DisposableStore;
@ -1430,7 +1432,6 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer<ITreeElement,
public readonly templateId = TestRunElementRenderer.ID;
constructor(
private readonly labels: ResourceLabels,
private readonly treeActions: TreeActionsProvider,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) { }
@ -1451,10 +1452,7 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer<ITreeElement,
const templateDisposable = new DisposableStore();
const wrapper = dom.append(container, dom.$('.test-peek-item'));
const icon = dom.append(wrapper, dom.$('.state'));
const name = dom.append(wrapper, dom.$('.name'));
const label = this.labels.create(name, { supportHighlights: true });
templateDisposable.add(label);
const label = dom.append(wrapper, dom.$('.name'));
const actionBar = new ActionBar(wrapper, {
actionViewItemProvider: action =>
@ -1486,7 +1484,13 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer<ITreeElement,
private doRender(element: ITreeElement, templateData: TemplateData) {
templateData.elementDisposable.clear();
templateData.label.setLabel(element.label, element.description);
if (element.labelWithIcons) {
dom.reset(templateData.label, ...element.labelWithIcons);
} else if (element.description) {
dom.reset(templateData.label, element.label, dom.$('span.test-label-description', {}, element.description));
} else {
dom.reset(templateData.label, element.label);
}
const icon = element.icon;
templateData.icon.className = `computed-state ${icon ? ThemeIcon.asClassName(icon) : ''}`;

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { stripIcons } from 'vs/base/common/iconLabels';
import { localize } from 'vs/nls';
import { TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
@ -44,7 +45,7 @@ export const testStateNames: { [K in TestResultState]: string } = {
export const labelForTestInState = (label: string, state: TestResultState) => localize({
key: 'testing.treeElementLabel',
comment: ['label then the unit tests state, for example "Addition Tests (Running)"'],
}, '{0} ({1})', label, testStateNames[state]);
}, '{0} ({1})', stripIcons(label), testStateNames[state]);
export const testConfigurationGroupNames: { [K in TestRunProfileBitset]: string } = {
[TestRunProfileBitset.Debug]: localize('testGroup.debug', 'Debug'),

View file

@ -26,6 +26,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
import { Schemas } from 'vs/base/common/network';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Memento } from 'vs/workbench/common/memento';
import { firstOrDefault } from 'vs/base/common/arrays';
const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoint<ResourceLabelFormatter[]>({
extensionPoint: 'resourceLabelFormatters',
@ -223,8 +224,23 @@ export class LabelService extends Disposable implements ILabelService {
}
// Relative label
if (options.relative) {
const folder = this.contextService?.getWorkspaceFolder(resource);
if (options.relative && this.contextService) {
let folder = this.contextService.getWorkspaceFolder(resource);
if (!folder) {
// It is possible that the resource we want to resolve the
// workspace folder for is not using the same scheme as
// the folders in the workspace, so we help by trying again
// to resolve a workspace folder by trying again with a
// scheme that is workspace contained.
const workspace = this.contextService.getWorkspace();
const firstFolder = firstOrDefault(workspace.folders);
if (firstFolder && resource.scheme !== firstFolder.uri.scheme && resource.path.startsWith(posix.sep)) {
folder = this.contextService.getWorkspaceFolder(firstFolder.uri.with({ path: resource.path }));
}
}
if (folder) {
const folderLabel = this.formatUri(folder.uri, formatting, options.noPrefix);

View file

@ -423,7 +423,7 @@ suite('StoredFileWorkingCopy', function () {
assert.strictEqual(backupContents, 'hello backup');
});
test('save (no errors)', async () => {
test('save (no errors) - simple', async () => {
let savedCounter = 0;
let lastSaveEvent: IStoredFileWorkingCopySaveEvent | undefined = undefined;
workingCopy.onDidSave(e => {
@ -453,21 +453,49 @@ suite('StoredFileWorkingCopy', function () {
assert.ok(lastSaveEvent!.stat);
assert.ok(isStoredFileWorkingCopySaveEvent(lastSaveEvent!));
assert.strictEqual(workingCopy.model?.pushedStackElement, true);
});
test('save (no errors) - save reason', async () => {
let savedCounter = 0;
let lastSaveEvent: IStoredFileWorkingCopySaveEvent | undefined = undefined;
workingCopy.onDidSave(e => {
savedCounter++;
lastSaveEvent = e;
});
let saveErrorCounter = 0;
workingCopy.onDidSaveError(() => {
saveErrorCounter++;
});
// save reason
await workingCopy.resolve();
workingCopy.model?.updateContents('hello save');
const source = SaveSourceRegistry.registerSource('testSource', 'Hello Save');
await workingCopy.save({ reason: SaveReason.AUTO, source });
assert.strictEqual(savedCounter, 2);
assert.strictEqual(savedCounter, 1);
assert.strictEqual(saveErrorCounter, 0);
assert.strictEqual(workingCopy.isDirty(), false);
assert.strictEqual((lastSaveEvent as IStoredFileWorkingCopySaveEvent).reason, SaveReason.AUTO);
assert.strictEqual((lastSaveEvent as IStoredFileWorkingCopySaveEvent).source, source);
assert.strictEqual((lastSaveEvent! as IStoredFileWorkingCopySaveEvent).reason, SaveReason.AUTO);
assert.strictEqual((lastSaveEvent! as IStoredFileWorkingCopySaveEvent).source, source);
});
test('save (no errors) - multiple', async () => {
let savedCounter = 0;
workingCopy.onDidSave(e => {
savedCounter++;
});
let saveErrorCounter = 0;
workingCopy.onDidSaveError(() => {
saveErrorCounter++;
});
// multiple saves in parallel are fine and result
// in a single save when content does not change
await workingCopy.resolve();
workingCopy.model?.updateContents('hello save');
await Promises.settled([
workingCopy.save({ reason: SaveReason.AUTO }),
@ -475,34 +503,87 @@ suite('StoredFileWorkingCopy', function () {
workingCopy.save({ reason: SaveReason.WINDOW_CHANGE })
]);
assert.strictEqual(savedCounter, 3);
assert.strictEqual(savedCounter, 1);
assert.strictEqual(saveErrorCounter, 0);
assert.strictEqual(workingCopy.isDirty(), false);
});
test('save (no errors) - multiple, cancellation', async () => {
let savedCounter = 0;
workingCopy.onDidSave(e => {
savedCounter++;
});
let saveErrorCounter = 0;
workingCopy.onDidSaveError(() => {
saveErrorCounter++;
});
// multiple saves in parallel are fine and result
// in just one save operation (the second one
// cancels the first)
await workingCopy.resolve();
workingCopy.model?.updateContents('hello save');
const firstSave = workingCopy.save();
workingCopy.model?.updateContents('hello save more');
const secondSave = workingCopy.save();
await Promises.settled([firstSave, secondSave]);
assert.strictEqual(savedCounter, 4);
assert.strictEqual(savedCounter, 1);
assert.strictEqual(saveErrorCounter, 0);
assert.strictEqual(workingCopy.isDirty(), false);
});
test('save (no errors) - not forced but not dirty', async () => {
let savedCounter = 0;
workingCopy.onDidSave(e => {
savedCounter++;
});
let saveErrorCounter = 0;
workingCopy.onDidSaveError(() => {
saveErrorCounter++;
});
// no save when not forced and not dirty
await workingCopy.resolve();
await workingCopy.save();
assert.strictEqual(savedCounter, 4);
assert.strictEqual(savedCounter, 0);
assert.strictEqual(saveErrorCounter, 0);
assert.strictEqual(workingCopy.isDirty(), false);
});
test('save (no errors) - forced but not dirty', async () => {
let savedCounter = 0;
workingCopy.onDidSave(e => {
savedCounter++;
});
let saveErrorCounter = 0;
workingCopy.onDidSaveError(() => {
saveErrorCounter++;
});
// save when forced even when not dirty
await workingCopy.resolve();
await workingCopy.save({ force: true });
assert.strictEqual(savedCounter, 5);
assert.strictEqual(savedCounter, 1);
assert.strictEqual(saveErrorCounter, 0);
assert.strictEqual(workingCopy.isDirty(), false);
});
test('save (no errors) - save clears orphaned', async () => {
let savedCounter = 0;
workingCopy.onDidSave(e => {
savedCounter++;
});
let saveErrorCounter = 0;
workingCopy.onDidSaveError(() => {
saveErrorCounter++;
});
await workingCopy.resolve();
// save clears orphaned
const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
@ -514,7 +595,7 @@ suite('StoredFileWorkingCopy', function () {
assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
await workingCopy.save({ force: true });
assert.strictEqual(savedCounter, 6);
assert.strictEqual(savedCounter, 1);
assert.strictEqual(saveErrorCounter, 0);
assert.strictEqual(workingCopy.isDirty(), false);
assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);

View file

@ -540,9 +540,11 @@ flakySuite('WorkingCopyBackupService', () => {
const backupId2 = toTypedWorkingCopyId(fooFile, 'type1');
const backupId3 = toTypedWorkingCopyId(fooFile, 'type2');
await service.backup(backupId1);
await service.backup(backupId2);
await service.backup(backupId3);
await Promise.all([
service.backup(backupId1),
service.backup(backupId2),
service.backup(backupId3)
]);
assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3);
@ -609,9 +611,11 @@ flakySuite('WorkingCopyBackupService', () => {
const backupId2 = toTypedWorkingCopyId(fooFile, 'type1');
const backupId3 = toTypedWorkingCopyId(fooFile, 'type2');
await service.backup(backupId1);
await service.backup(backupId2);
await service.backup(backupId3);
await Promise.all([
service.backup(backupId1),
service.backup(backupId2),
service.backup(backupId3)
]);
assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3);
@ -716,9 +720,11 @@ flakySuite('WorkingCopyBackupService', () => {
suite('getBackups', () => {
test('text file', async () => {
await service.backup(toUntypedWorkingCopyId(fooFile), bufferToReadable(VSBuffer.fromString('test')));
await service.backup(toTypedWorkingCopyId(fooFile, 'type1'), bufferToReadable(VSBuffer.fromString('test')));
await service.backup(toTypedWorkingCopyId(fooFile, 'type2'), bufferToReadable(VSBuffer.fromString('test')));
await Promise.all([
service.backup(toUntypedWorkingCopyId(fooFile), bufferToReadable(VSBuffer.fromString('test'))),
service.backup(toTypedWorkingCopyId(fooFile, 'type1'), bufferToReadable(VSBuffer.fromString('test'))),
service.backup(toTypedWorkingCopyId(fooFile, 'type2'), bufferToReadable(VSBuffer.fromString('test')))
]);
let backups = await service.getBackups();
assert.strictEqual(backups.length, 3);
@ -742,9 +748,11 @@ flakySuite('WorkingCopyBackupService', () => {
});
test('untitled file', async () => {
await service.backup(toUntypedWorkingCopyId(untitledFile), bufferToReadable(VSBuffer.fromString('test')));
await service.backup(toTypedWorkingCopyId(untitledFile, 'type1'), bufferToReadable(VSBuffer.fromString('test')));
await service.backup(toTypedWorkingCopyId(untitledFile, 'type2'), bufferToReadable(VSBuffer.fromString('test')));
await Promise.all([
service.backup(toUntypedWorkingCopyId(untitledFile), bufferToReadable(VSBuffer.fromString('test'))),
service.backup(toTypedWorkingCopyId(untitledFile, 'type1'), bufferToReadable(VSBuffer.fromString('test'))),
service.backup(toTypedWorkingCopyId(untitledFile, 'type2'), bufferToReadable(VSBuffer.fromString('test')))
]);
const backups = await service.getBackups();
assert.strictEqual(backups.length, 3);