Merge branch 'master' into sandy081/userDataProvider

This commit is contained in:
Sandeep Somavarapu
2019-09-15 19:52:27 +02:00
281 changed files with 7541 additions and 5362 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
disturl "https://atom.io/download/electron"
target "4.2.10"
target "6.0.9"
runtime "electron"
+2 -6
View File
@@ -1,12 +1,8 @@
pool:
vmImage: 'Ubuntu-16.04'
trigger:
branches:
include: ['master']
pr:
branches:
include: ['master']
trigger: none
pr: none
steps:
- task: NodeTool@0
@@ -100,7 +100,7 @@ steps:
- script: |
set -e
DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests"
DISPLAY=:10 ./scripts/test.sh --build --tfs --no-sandbox "Unit Tests"
displayName: Run unit tests
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
+1 -1
View File
@@ -1,7 +1,7 @@
[
{
"name": "ms-vscode.node-debug",
"version": "1.38.4",
"version": "1.38.5",
"repo": "https://github.com/Microsoft/vscode-node-debug",
"metadata": {
"id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6",
+29 -1
View File
@@ -17,6 +17,7 @@ const vfs = require('vinyl-fs');
const path = require('path');
const fs = require('fs');
const pall = require('p-all');
const task = require('./lib/task');
/**
* Hygiene works by creating cascading subsets of all our files and
@@ -205,6 +206,33 @@ gulp.task('tslint', () => {
]).pipe(es.through());
});
function checkPackageJSON(actualPath) {
const actual = require(path.join(__dirname, '..', actualPath));
const rootPackageJSON = require('../package.json');
for (let depName in actual.dependencies) {
const depVersion = actual.dependencies[depName];
const rootDepVersion = rootPackageJSON.dependencies[depName];
if (!rootDepVersion) {
// missing in root is allowed
continue;
}
if (depVersion !== rootDepVersion) {
this.emit('error', `The dependency ${depName} in '${actualPath}' (${depVersion}) is different than in the root package.json (${rootDepVersion})`);
}
}
}
const checkPackageJSONTask = task.define('check-package-json', () => {
return gulp.src('package.json')
.pipe(es.through(function() {
checkPackageJSON.call(this, 'remote/package.json');
checkPackageJSON.call(this, 'remote/web/package.json');
}));
});
gulp.task(checkPackageJSONTask);
function hygiene(some) {
let errorCount = 0;
@@ -393,7 +421,7 @@ function createGitIndexVinyls(paths) {
.then(r => r.filter(p => !!p));
}
gulp.task('hygiene', () => hygiene());
gulp.task('hygiene', task.series(checkPackageJSONTask, () => hygiene()));
// this allows us to run hygiene as a git pre-commit hook
if (require.main === module) {
+6 -153
View File
@@ -6,158 +6,11 @@
'use strict';
const gulp = require('gulp');
const path = require('path');
const es = require('event-stream');
const util = require('./lib/util');
const task = require('./lib/task');
const common = require('./lib/optimize');
const product = require('../product.json');
const rename = require('gulp-rename');
const filter = require('gulp-filter');
const json = require('gulp-json-editor');
const _ = require('underscore');
const deps = require('./dependencies');
const vfs = require('vinyl-fs');
const packageJson = require('../package.json');
const { compileBuildTask } = require('./gulpfile.compile');
const REPO_ROOT = path.dirname(__dirname);
const commit = util.getVersion(REPO_ROOT);
const BUILD_ROOT = path.dirname(REPO_ROOT);
const WEB_FOLDER = path.join(REPO_ROOT, 'remote', 'web');
const noop = () => { return Promise.resolve(); };
const productionDependencies = deps.getProductionDependencies(WEB_FOLDER);
const nodeModules = Object.keys(product.dependencies || {})
.concat(_.uniq(productionDependencies.map(d => d.name)));
const vscodeWebResources = [
// Workbench
'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png}',
'out-build/vs/code/browser/workbench/*.html',
'out-build/vs/base/browser/ui/octiconLabel/octicons/**',
'out-build/vs/**/markdown.css',
// Webview
'out-build/vs/workbench/contrib/webview/browser/pre/*.js',
// Extension Worker
'out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js',
// Excludes
'!out-build/vs/**/{node,electron-browser,electron-main}/**',
'!out-build/vs/editor/standalone/**',
'!out-build/vs/workbench/**/*-tb.png',
'!**/test/**'
];
const buildfile = require('../src/buildfile');
const vscodeWebEntryPoints = _.flatten([
buildfile.entrypoint('vs/workbench/workbench.web.api'),
buildfile.base,
buildfile.workerExtensionHost,
buildfile.keyboardMaps,
buildfile.workbenchWeb
]);
const optimizeVSCodeWebTask = task.define('optimize-vscode-web', task.series(
util.rimraf('out-vscode-web'),
common.optimizeTask({
src: 'out-build',
entryPoints: _.flatten(vscodeWebEntryPoints),
otherSources: [],
resources: vscodeWebResources,
loaderConfig: common.loaderConfig(nodeModules),
out: 'out-vscode-web',
bundleInfo: undefined
})
));
const minifyVSCodeWebTask = task.define('minify-vscode-web', task.series(
optimizeVSCodeWebTask,
util.rimraf('out-vscode-web-min'),
common.minifyTask('out-vscode-web', `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`)
));
gulp.task(minifyVSCodeWebTask);
function packageTask(sourceFolderName, destinationFolderName) {
const destination = path.join(BUILD_ROOT, destinationFolderName);
return () => {
const src = gulp.src(sourceFolderName + '/**', { base: '.' })
.pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); }))
.pipe(filter(['**', '!**/*.js.map']));
const sources = es.merge(src);
let version = packageJson.version;
const quality = product.quality;
if (quality && quality !== 'stable') {
version += '-' + quality;
}
const name = product.nameShort;
const packageJsonStream = gulp.src(['remote/web/package.json'], { base: 'remote/web' })
.pipe(json({ name, version }));
const date = new Date().toISOString();
const productJsonStream = gulp.src(['product.json'], { base: '.' })
.pipe(json({ commit, date }));
const license = gulp.src(['remote/LICENSE'], { base: 'remote' });
const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]));
const deps = gulp.src(dependenciesSrc, { base: 'remote/web', dot: true })
.pipe(filter(['**', '!**/package-lock.json']))
.pipe(util.cleanNodeModules(path.join(__dirname, '.nativeignore')));
const favicon = gulp.src('resources/server/favicon.ico', { base: 'resources/server' });
const manifest = gulp.src('resources/server/manifest.json', { base: 'resources/server' });
const pwaicons = es.merge(
gulp.src('resources/server/code-192.png', { base: 'resources/server' }),
gulp.src('resources/server/code-512.png', { base: 'resources/server' })
);
let all = es.merge(
packageJsonStream,
productJsonStream,
license,
sources,
deps,
favicon,
manifest,
pwaicons
);
let result = all
.pipe(util.skipDirectories())
.pipe(util.fixWin32DirectoryPermissions());
return result.pipe(vfs.dest(destination));
};
}
const dashed = (str) => (str ? `-${str}` : ``);
['', 'min'].forEach(minified => {
const sourceFolderName = `out-vscode-web${dashed(minified)}`;
const destinationFolderName = `vscode-web`;
const vscodeWebTaskCI = task.define(`vscode-web${dashed(minified)}-ci`, task.series(
minified ? minifyVSCodeWebTask : optimizeVSCodeWebTask,
util.rimraf(path.join(BUILD_ROOT, destinationFolderName)),
packageTask(sourceFolderName, destinationFolderName)
));
gulp.task(vscodeWebTaskCI);
const vscodeWebTask = task.define(`vscode-web${dashed(minified)}`, task.series(
compileBuildTask,
vscodeWebTaskCI
));
gulp.task(vscodeWebTask);
});
gulp.task('minify-vscode-web', noop);
gulp.task('vscode-web', noop);
gulp.task('vscode-web-min', noop);
gulp.task('vscode-web-ci', noop);
gulp.task('vscode-web-min-ci', noop);
+1 -1
View File
@@ -31,4 +31,4 @@ if (!/yarn\.js$|yarnpkg$/.test(process.env['npm_execpath'])) {
if (err) {
console.error('');
process.exit(1);
}
}
-52
View File
@@ -46,58 +46,6 @@
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
},
{
// Reason: The npm module does not contain a repository field.
// waiting for https://github.com/xtermjs/xterm.js/issues/2395
"name": "xterm-addon-search",
"fullLicenseText": [
"Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)",
"",
"Permission is hereby granted, free of charge, to any person obtaining a copy",
"of this software and associated documentation files (the \"Software\"), to deal",
"in the Software without restriction, including without limitation the rights",
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
"copies of the Software, and to permit persons to whom the Software is",
"furnished to do so, subject to the following conditions:",
"",
"The above copyright notice and this permission notice shall be included in",
"all copies or substantial portions of the Software.",
"",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN",
"THE SOFTWARE."
]
},
{
// Reason: The npm module does not contain a repository field.
// waiting for https://github.com/xtermjs/xterm.js/issues/2395
"name": "xterm-addon-web-links",
"fullLicenseText": [
"Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)",
"",
"Permission is hereby granted, free of charge, to any person obtaining a copy",
"of this software and associated documentation files (the \"Software\"), to deal",
"in the Software without restriction, including without limitation the rights",
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
"copies of the Software, and to permit persons to whom the Software is",
"furnished to do so, subject to the following conditions:",
"",
"The above copyright notice and this permission notice shall be included in",
"all copies or substantial portions of the Software.",
"",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN",
"THE SOFTWARE."
]
},
{
// Reason: The license at https://git.coolaj86.com/coolaj86/atob.js/src/branch/master/LICENSE
// cannot be found by the OSS tool automatically.
+6 -6
View File
@@ -6,7 +6,7 @@
"git": {
"name": "chromium",
"repositoryUrl": "https://chromium.googlesource.com/chromium/src",
"commitHash": "c6a08e5368de4352903e702cde750b33239a50ab"
"commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa"
}
},
"licenseDetail": [
@@ -40,7 +40,7 @@
"SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
],
"isOnlyProductionDependency": true,
"version": "69.0.3497.128"
"version": "76.0.3809.146"
},
{
"component": {
@@ -48,11 +48,11 @@
"git": {
"name": "nodejs",
"repositoryUrl": "https://github.com/nodejs/node",
"commitHash": "8c70b2084ce5f76ea1e3b3c4ccdeee4483fe338b"
"commitHash": "64219741218aa87e259cf8257596073b8e747f0a"
}
},
"isOnlyProductionDependency": true,
"version": "10.11.0"
"version": "12.4.0"
},
{
"component": {
@@ -60,12 +60,12 @@
"git": {
"name": "electron",
"repositoryUrl": "https://github.com/electron/electron",
"commitHash": "4e4c7527c63fcf27dffaeb58bde996b8d859c0ed"
"commitHash": "407747b48c47cdeed156a73dde1c47609470c95a"
}
},
"isOnlyProductionDependency": true,
"license": "MIT",
"version": "4.2.10"
"version": "6.0.9"
},
{
"component": {
@@ -59,6 +59,12 @@
"default": true,
"description": "%css.completion.triggerPropertyValueCompletion.desc%"
},
"css.completion.completePropertyWithSemicolon": {
"type": "boolean",
"scope": "resource",
"default": true,
"description": "%css.completion.completePropertyWithSemicolon.desc%"
},
"css.validate": {
"type": "boolean",
"scope": "resource",
@@ -4,6 +4,7 @@
"css.title": "CSS",
"css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/Microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its CSS support for the custom CSS properties, at directives, pseudo classes and pseudo elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.",
"css.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.",
"css.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end when completing CSS properties",
"css.lint.argumentsInColorFunction.desc": "Invalid number of parameters.",
"css.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.",
"css.lint.compatibleVendorPrefixes.desc": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties.",
@@ -9,7 +9,7 @@
},
"main": "./out/cssServerMain",
"dependencies": {
"vscode-css-languageservice": "^4.0.3-next.6",
"vscode-css-languageservice": "^4.0.3-next.8",
"vscode-languageserver": "^5.3.0-next.8"
},
"devDependencies": {
@@ -781,10 +781,10 @@ supports-color@^5.3.0:
dependencies:
has-flag "^3.0.0"
vscode-css-languageservice@^4.0.3-next.6:
version "4.0.3-next.6"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.6.tgz#c9b70ae6dc7418d080188ba9772bf47343b929e7"
integrity sha512-Ip9EzhgZmwYEmmTw4oIN8oaZ1gUEJqVDrfcvPJ/bfc9lcYeSxexQ+9O5TiF106nX2g5f93sWYX/rDiAhxSgf4A==
vscode-css-languageservice@^4.0.3-next.8:
version "4.0.3-next.8"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.8.tgz#0b81693b6ea9d10f78775a1dcad2c0f464fbde16"
integrity sha512-agBPPu86bPKIK5v6CFnWeBXN4jvnCzc67GZa/pvrIWeRdG7nvTu5Y2wYdwdesdpWzno9/5tfFEPp0KJbKQ4l+A==
dependencies:
vscode-languageserver-types "^3.15.0-next.2"
vscode-nls "^4.1.1"
+1 -1
View File
@@ -6,7 +6,7 @@
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
@@ -10,6 +10,7 @@
"activationEvents": [
"onDebugResolve"
],
"enableProposedApi": true,
"main": "./out/extension",
"scripts": {
"compile": "gulp compile-extension:debug-server-ready",
@@ -78,11 +78,11 @@ class ServerReadyDetector extends vscode.Disposable {
}
this.shellPid = undefined;
terminals.forEach(terminal => {
this.disposables.push(terminal.onDidWriteData(s => {
this.detectPattern(s);
}));
});
this.disposables.push(vscode.window.onDidWriteTerminalData(e => {
if (terminals.indexOf(e.terminal) !== -1) {
this.detectPattern(e.data);
}
}));
}
detectPattern(s: string): void {
@@ -9,7 +9,7 @@
},
"main": "./out/htmlServerMain",
"dependencies": {
"vscode-css-languageservice": "^4.0.3-next.6",
"vscode-css-languageservice": "^4.0.3-next.8",
"vscode-html-languageservice": "^3.0.4-next.3",
"vscode-languageserver": "^5.3.0-next.8",
"vscode-languageserver-types": "3.15.0-next.2",
@@ -229,10 +229,10 @@ supports-color@5.4.0:
dependencies:
has-flag "^3.0.0"
vscode-css-languageservice@^4.0.3-next.6:
version "4.0.3-next.6"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.6.tgz#c9b70ae6dc7418d080188ba9772bf47343b929e7"
integrity sha512-Ip9EzhgZmwYEmmTw4oIN8oaZ1gUEJqVDrfcvPJ/bfc9lcYeSxexQ+9O5TiF106nX2g5f93sWYX/rDiAhxSgf4A==
vscode-css-languageservice@^4.0.3-next.8:
version "4.0.3-next.8"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.8.tgz#0b81693b6ea9d10f78775a1dcad2c0f464fbde16"
integrity sha512-agBPPu86bPKIK5v6CFnWeBXN4jvnCzc67GZa/pvrIWeRdG7nvTu5Y2wYdwdesdpWzno9/5tfFEPp0KJbKQ4l+A==
dependencies:
vscode-languageserver-types "^3.15.0-next.2"
vscode-nls "^4.1.1"
+6 -2
View File
@@ -58,7 +58,8 @@
20
];
const isMac = getSettings().isMac;
const settings = getSettings();
const isMac = settings.isMac;
const vscode = acquireVsCodeApi();
@@ -71,7 +72,7 @@
// Elements
const container = /** @type {HTMLElement} */(document.querySelector('body'));
const image = document.querySelector('img');
const image = document.createElement('img');
function updateScale(newScale) {
if (!image || !image.parentElement) {
@@ -248,6 +249,9 @@
}
});
image.src = decodeURI(settings.src);
document.body.append(image);
window.addEventListener('message', e => {
switch (e.data.type) {
case 'setScale':
+2 -2
View File
@@ -80,7 +80,8 @@ export class Preview extends Disposable {
private getWebiewContents(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri): string {
const settings = {
isMac: process.platform === 'darwin'
isMac: process.platform === 'darwin',
src: encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true))
};
return /* html */`<!DOCTYPE html>
@@ -95,7 +96,6 @@ export class Preview extends Disposable {
<meta id="image-preview-settings" data-settings="${escapeAttribute(JSON.stringify(settings))}">
</head>
<body class="container image scale-to-fit">
<img src="${escapeAttribute(webviewEditor.webview.asWebviewUri(resource))}">
<script src="${escapeAttribute(this.extensionResource('/media/main.js'))}"></script>
</body>
</html>`;
@@ -231,12 +231,6 @@
"description": "%markdown.preview.scrollPreviewWithEditor.desc%",
"scope": "resource"
},
"markdown.preview.scrollPreviewWithEditorSelection": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.scrollPreviewWithEditorSelection.desc%",
"deprecationMessage": "%markdown.preview.scrollPreviewWithEditorSelection.deprecationMessage%"
},
"markdown.preview.markEditorSelection": {
"type": "boolean",
"default": true,
@@ -10,8 +10,6 @@
"markdown.preview.markEditorSelection.desc": "Mark the current editor selection in the markdown preview.",
"markdown.preview.scrollEditorWithPreview.desc": "When a markdown preview is scrolled, update the view of the editor.",
"markdown.preview.scrollPreviewWithEditor.desc": "When a markdown editor is scrolled, update the view of the preview.",
"markdown.preview.scrollPreviewWithEditorSelection.desc": "[Deprecated] Scrolls the markdown preview to reveal the currently selected line from the editor.",
"markdown.preview.scrollPreviewWithEditorSelection.deprecationMessage": "This setting has been replaced by 'markdown.preview.scrollPreviewWithEditor' and no longer has any effect.",
"markdown.preview.title": "Open Preview",
"markdown.previewSide.title": "Open Preview to the Side",
"markdown.showLockedPreviewToSide.title": "Open Locked Preview to the Side",
@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Disposable } from './util/dispose';
export class DocumentIndex extends Disposable {
private readonly _uriMap = new Map();
constructor() {
super();
for (let doc of vscode.workspace.textDocuments) {
this._registerDoc(doc);
}
this._register(
vscode.workspace.onDidOpenTextDocument((doc) => {
this._registerDoc(doc);
})
);
this._register(
vscode.workspace.onDidCloseTextDocument((doc) => {
this._unregisterDoc(doc.uri);
})
);
}
getByUri(uri: vscode.Uri): vscode.TextDocument | undefined {
return this._uriMap.get(uri.toString());
}
private _registerDoc(doc: vscode.TextDocument) {
const uri = doc.uri.toString();
if (this._uriMap.has(uri)) {
throw new Error(`The document ${uri} is already registered.`);
}
this._uriMap.set(uri, doc);
}
private _unregisterDoc(uri: vscode.Uri) {
if (!this._uriMap.has(uri.toString())) {
throw new Error(`The document ${uri.toString()} is not registered.`);
}
this._uriMap.delete(uri.toString());
}
}
@@ -288,12 +288,15 @@ export class MarkdownPreview extends Disposable {
const editor = vscode.window.activeTextEditor;
// Reposition scroll preview, position scroll to the top if active text editor
// doesn't corresponds with preview
if (editor && editor.document.uri.fsPath === resource.fsPath) {
this.line = getVisibleLine(editor);
} else {
this.line = 0;
if (editor) {
if (editor.document.uri.fsPath === resource.fsPath) {
this.line = getVisibleLine(editor);
} else {
this.line = 0;
}
}
// If we have changed resources, cancel any pending updates
const isResourceChange = resource.fsPath !== this._resource.fsPath;
if (isResourceChange) {
@@ -8,8 +8,9 @@ import { Disposable } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
import { Lazy, lazy } from '../util/lazy';
import MDDocumentSymbolProvider from './documentSymbolProvider';
import { SkinnyTextDocument } from '../tableOfContentsProvider';
import { SkinnyTextDocument, SkinnyTextLine } from '../tableOfContentsProvider';
import { flatten } from '../util/arrays';
import { DocumentIndex } from '../docIndex';
export interface WorkspaceMarkdownDocumentProvider {
getAllMarkdownDocuments(): Thenable<Iterable<SkinnyTextDocument>>;
@@ -26,6 +27,7 @@ class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements Work
private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<vscode.Uri>());
private _watcher: vscode.FileSystemWatcher | undefined;
private _docIndex: DocumentIndex = this._register(new DocumentIndex());
async getAllMarkdownDocuments() {
const resources = await vscode.workspace.findFiles('**/*.md', '**/node_modules/**');
@@ -81,12 +83,39 @@ class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements Work
}
private async getMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
const doc = await vscode.workspace.openTextDocument(resource);
return doc && isMarkdownFile(doc) ? doc : undefined;
const existingDocument = this._docIndex.getByUri(resource);
if (existingDocument) {
return existingDocument;
}
const bytes = await vscode.workspace.fs.readFile(resource);
// We assume that markdown is in UTF-8
const text = Buffer.from(bytes).toString('utf-8');
const lines: SkinnyTextLine[] = [];
const parts = text.split(/(\r?\n)/);
const lineCount = Math.floor(parts.length / 2) + 1;
for (let line = 0; line < lineCount; line++) {
lines.push({
text: parts[line * 2]
});
}
return {
uri: resource,
version: 0,
lineCount: lineCount,
lineAt: (index) => {
return lines[index];
},
getText: () => {
return text;
}
};
}
}
export default class MarkdownWorkspaceSymbolProvider extends Disposable implements vscode.WorkspaceSymbolProvider {
private _symbolCache = new Map<string, Lazy<Thenable<vscode.SymbolInformation[]>>>();
private _symbolCachePopulated: boolean = false;
@@ -15,12 +15,17 @@ export interface TocEntry {
readonly location: vscode.Location;
}
export interface SkinnyTextLine {
text: string;
}
export interface SkinnyTextDocument {
readonly uri: vscode.Uri;
readonly version: number;
readonly lineCount: number;
lineAt(line: number): SkinnyTextLine;
getText(): string;
lineAt(line: number): vscode.TextLine;
}
export class TableOfContentsProvider {
@@ -72,7 +77,8 @@ export class TableOfContentsProvider {
text: TableOfContentsProvider.getHeaderText(line.text),
level: TableOfContentsProvider.getHeaderLevel(heading.markup),
line: lineNumber,
location: new vscode.Location(document.uri, line.range)
location: new vscode.Location(document.uri,
new vscode.Range(lineNumber, 0, lineNumber, line.text.length))
});
}
@@ -85,13 +91,13 @@ export class TableOfContentsProvider {
break;
}
}
const endLine = typeof end === 'number' ? end : document.lineCount - 1;
const endLine = end !== undefined ? end : document.lineCount - 1;
return {
...entry,
location: new vscode.Location(document.uri,
new vscode.Range(
entry.location.range.start,
new vscode.Position(endLine, document.lineAt(endLine).range.end.character)))
new vscode.Position(endLine, document.lineAt(endLine).text.length)))
};
});
}
+1 -1
View File
@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "Dependencies shared by all extensions",
"dependencies": {
"typescript": "3.6.3-insiders.20190909"
"typescript": "3.6.3"
},
"scripts": {
"postinstall": "node ./postinstall"
@@ -358,6 +358,10 @@
"diffEditor.removedTextBackground": "#892F4688",
// "diffEditor.removedTextBorder": "",
// Editor: Minimap
"minimap.selectionHighlight": "#750000",
// Workbench: Title
"titleBar.activeBackground": "#10192c",
// "titleBar.activeForeground": "",
@@ -439,4 +443,4 @@
"terminal.ansiBrightCyan": "#78ffff",
"terminal.ansiBrightWhite": "#ffffff"
}
}
}
@@ -17,6 +17,7 @@
"inputOption.activeBorder": "#a57a4c",
"selection.background": "#84613daa",
"editor.selectionBackground": "#84613daa",
"minimap.selectionHighlight": "#84613daa",
"editorWidget.background": "#131510",
"editorHoverWidget.background": "#221a14",
"editorGroupHeader.tabsBackground": "#131510",
@@ -398,4 +399,4 @@
}
}
]
}
}
@@ -11,6 +11,7 @@
"editor.background": "#1e1e1e",
"editor.foreground": "#c5c8c6",
"editor.selectionBackground": "#676b7180",
"minimap.selectionHighlight": "#676b7180",
"editor.selectionHighlightBackground": "#575b6180",
"editor.lineHighlightBackground": "#303030",
"editorLineNumber.activeForeground": "#949494",
@@ -588,4 +589,4 @@
}
}
]
}
}
@@ -23,6 +23,7 @@
"selection.background": "#ccccc7",
"editor.selectionHighlightBackground": "#575b6180",
"editor.selectionBackground": "#878b9180",
"minimap.selectionHighlight": "#878b9180",
"editor.wordHighlightBackground": "#4a4a7680",
"editor.wordHighlightStrongBackground": "#6a6a9680",
"editor.lineHighlightBackground": "#3e3d32",
@@ -499,6 +499,7 @@
"editor.lineHighlightBackground": "#E4F6D4",
"editorLineNumber.activeForeground": "#9769dc",
"editor.selectionBackground": "#C9D0D9",
"minimap.selectionHighlight": "#C9D0D9",
"tab.modifiedBorder": "#f1897f",
"panel.background": "#F5F5F5",
"sideBar.background": "#F2F2F2",
@@ -534,6 +535,8 @@
"errorForeground": "#f1897f",
"badge.background": "#705697AA",
"progressBar.background": "#705697",
"walkThrough.embeddedEditorBackground": "#00000014"
"walkThrough.embeddedEditorBackground": "#00000014",
"editorIndentGuide.background": "#aaaaaa60",
"editorIndentGuide.activeBackground": "#777777b0"
}
}
@@ -21,6 +21,7 @@
"editor.foreground": "#F8F8F8",
"editorWhitespace.foreground": "#c10000",
"editor.selectionBackground": "#750000",
"minimap.selectionHighlight": "#750000",
"editorLineNumber.foreground": "#ff777788",
"editorLineNumber.activeForeground": "#ffbbbb88",
"editorWidget.background": "#300000",
@@ -411,4 +412,4 @@
}
}
]
}
}
@@ -359,6 +359,7 @@
"editor.lineHighlightBackground": "#073642",
"editorLineNumber.activeForeground": "#949494",
"editor.selectionBackground": "#274642",
"minimap.selectionHighlight": "#274642",
"editorIndentGuide.background": "#93A1A180",
"editorIndentGuide.activeBackground": "#C3E1E180",
"editorHoverWidget.background": "#004052",
@@ -350,6 +350,7 @@
"editorWhitespace.foreground": "#586E7580",
"editor.lineHighlightBackground": "#EEE8D5",
"editor.selectionBackground": "#EEE8D5",
"minimap.selectionHighlight": "#EEE8D5",
"editorIndentGuide.background": "#586E7580",
"editorIndentGuide.activeBackground": "#081E2580",
"editorHoverWidget.background": "#CCC4B0",
@@ -486,4 +487,4 @@
// Interactive Playground
"walkThrough.embeddedEditorBackground": "#00000014"
}
}
}
@@ -14,6 +14,7 @@
"editor.background": "#002451",
"editor.foreground": "#ffffff",
"editor.selectionBackground": "#003f8e",
"minimap.selectionHighlight": "#003f8e",
"editor.lineHighlightBackground": "#00346e",
"editorLineNumber.activeForeground": "#949494",
"editorCursor.foreground": "#ffffff",
@@ -255,4 +256,4 @@
}
}
]
}
}
@@ -264,6 +264,22 @@
"description": "%format.placeOpenBraceOnNewLineForControlBlocks%",
"scope": "resource"
},
"typescript.format.semicolons": {
"type": "string",
"default": "ignore",
"description": "%format.semicolons%",
"scope": "resource",
"enum": [
"ignore",
"insert",
"remove"
],
"enumDescriptions": [
"%format.semicolons.ignore%",
"%format.semicolons.insert%",
"%format.semicolons.remove%"
]
},
"javascript.validate.enable": {
"type": "boolean",
"default": true,
@@ -360,6 +376,22 @@
"description": "%format.placeOpenBraceOnNewLineForControlBlocks%",
"scope": "resource"
},
"javascript.format.semicolons": {
"type": "string",
"default": "ignore",
"description": "%format.semicolons%",
"scope": "resource",
"enum": [
"ignore",
"insert",
"remove"
],
"enumDescriptions": [
"%format.semicolons.ignore%",
"%format.semicolons.insert%",
"%format.semicolons.remove%"
]
},
"javascript.implicitProjectConfig.checkJs": {
"type": "boolean",
"default": false,
@@ -28,6 +28,10 @@
"format.insertSpaceAfterTypeAssertion": "Defines space handling after type assertions in TypeScript. Requires using TypeScript 2.4 or newer in the workspace.",
"format.placeOpenBraceOnNewLineForFunctions": "Defines whether an open brace is put onto a new line for functions or not.",
"format.placeOpenBraceOnNewLineForControlBlocks": "Defines whether an open brace is put onto a new line for control blocks or not.",
"format.semicolons": "Defines handling of optional semicolons. Requires using TypeScript 3.7 or newer in the workspace.",
"format.semicolons.ignore": "Dont insert or remove any semicolons.",
"format.semicolons.insert": "Insert semicolons at statement ends.",
"format.semicolons.remove": "Remove unnecessary semicolons.",
"javascript.validate.enable": "Enable/disable JavaScript validation.",
"goToProjectConfig.title": "Go to Project Configuration",
"javascript.referencesCodeLens.enabled": "Enable/disable references CodeLens in JavaScript files.",
@@ -70,4 +74,4 @@
"configuration.surveys.enabled": "Enabled/disable occasional surveys that help us improve VS Code's JavaScript and TypeScript support.",
"configuration.suggest.completeJSDocs": "Enable/disable suggestion to complete JSDoc comments.",
"typescript.preferences.renameShorthandProperties": "Enable/disable introducing aliases for object shorthand properties during renames. Requires using TypeScript 3.4 or newer in the workspace."
}
}
@@ -177,11 +177,9 @@ class SyncedBuffer {
fileContent: this.document.getText(),
};
if (this.client.apiVersion.gte(API.v203)) {
const scriptKind = mode2ScriptKind(this.document.languageId);
if (scriptKind) {
args.scriptKindName = scriptKind;
}
const scriptKind = mode2ScriptKind(this.document.languageId);
if (scriptKind) {
args.scriptKindName = scriptKind;
}
if (this.client.apiVersion.gte(API.v230)) {
@@ -144,7 +144,9 @@ export default class FileConfigurationManager extends Disposable {
isTypeScriptDocument(document) ? 'typescript.format' : 'javascript.format',
document.uri);
return {
// `semicolons` added to `Proto.FormatCodeSettings` in TypeScript 3.7:
// remove intersection type after upgrading TypeScript.
const settings: Proto.FormatCodeSettings & { semicolons?: string } = {
tabSize: options.tabSize,
indentSize: options.tabSize,
convertTabsToSpaces: options.insertSpaces,
@@ -165,7 +167,10 @@ export default class FileConfigurationManager extends Disposable {
insertSpaceAfterTypeAssertion: config.get<boolean>('insertSpaceAfterTypeAssertion'),
placeOpenBraceOnNewLineForFunctions: config.get<boolean>('placeOpenBraceOnNewLineForFunctions'),
placeOpenBraceOnNewLineForControlBlocks: config.get<boolean>('placeOpenBraceOnNewLineForControlBlocks'),
semicolons: config.get<string>('semicolons'),
};
return settings;
}
private getPreferences(document: vscode.TextDocument): Proto.UserPreferences {
@@ -201,4 +206,4 @@ function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguratio
case 'non-relative': return 'non-relative';
default: return undefined;
}
}
}
@@ -11,7 +11,6 @@ import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { applyCodeActionCommands, getEditForCodeAction } from '../utils/codeAction';
import { Command, CommandManager } from '../utils/commandManager';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import { memoize } from '../utils/memoize';
import TelemetryReporter from '../utils/telemetry';
import * as typeConverters from '../utils/typeConverters';
@@ -174,7 +173,6 @@ class SupportedCodeActionProvider {
}
class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
public static readonly minVersion = API.v213;
public static readonly metadata: vscode.CodeActionProviderMetadata = {
providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]
@@ -343,8 +341,7 @@ export function register(
diagnosticsManager: DiagnosticsManager,
telemetryReporter: TelemetryReporter
) {
return new VersionDependentRegistration(client, TypeScriptQuickFixProvider.minVersion, () =>
vscode.languages.registerCodeActionsProvider(selector,
new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, telemetryReporter),
TypeScriptQuickFixProvider.metadata));
return vscode.languages.registerCodeActionsProvider(selector,
new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, telemetryReporter),
TypeScriptQuickFixProvider.metadata);
}
@@ -5,10 +5,8 @@
import * as vscode from 'vscode';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import * as typeConverters from '../utils/typeConverters';
class TypeScriptReferenceSupport implements vscode.ReferenceProvider {
public constructor(
private readonly client: ITypeScriptServiceClient) { }
@@ -31,9 +29,8 @@ class TypeScriptReferenceSupport implements vscode.ReferenceProvider {
}
const result: vscode.Location[] = [];
const has203Features = this.client.apiVersion.gte(API.v203);
for (const ref of response.body.refs) {
if (!options.includeDeclaration && has203Features && ref.isDefinition) {
if (!options.includeDeclaration && ref.isDefinition) {
continue;
}
const url = this.client.toResource(ref.file);
@@ -50,4 +47,4 @@ export function register(
) {
return vscode.languages.registerReferenceProvider(selector,
new TypeScriptReferenceSupport(client));
}
}
@@ -7,18 +7,15 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { ConfigurationDependentRegistration, VersionDependentRegistration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider';
import { CachedResponse } from '../tsServer/cachedResponse';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
import { getSymbolRange, ReferencesCodeLens, TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider';
const localize = nls.loadMessageBundle();
class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider {
public static readonly minVersion = API.v206;
public async resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise<vscode.CodeLens> {
const codeLens = inputCodeLens as ReferencesCodeLens;
const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start);
@@ -99,9 +96,8 @@ export function register(
client: ITypeScriptServiceClient,
cachedResponse: CachedResponse<Proto.NavTreeResponse>,
) {
return new VersionDependentRegistration(client, TypeScriptReferencesCodeLensProvider.minVersion, () =>
new ConfigurationDependentRegistration(modeId, 'referencesCodeLens.enabled', () => {
return vscode.languages.registerCodeLensProvider(selector,
new TypeScriptReferencesCodeLensProvider(client, cachedResponse));
}));
}
return new ConfigurationDependentRegistration(modeId, 'referencesCodeLens.enabled', () => {
return vscode.languages.registerCodeLensProvider(selector,
new TypeScriptReferencesCodeLensProvider(client, cachedResponse));
});
}
@@ -5,13 +5,9 @@
import * as vscode from 'vscode';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import DefinitionProviderBase from './definitionProviderBase';
export default class TypeScriptTypeDefinitionProvider extends DefinitionProviderBase implements vscode.TypeDefinitionProvider {
public static readonly minVersion = API.v213;
public provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Definition | undefined> {
return this.getSymbolLocations('typeDefinition', document, position, token);
}
@@ -21,8 +17,6 @@ export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient,
) {
return new VersionDependentRegistration(client, TypeScriptTypeDefinitionProvider.minVersion, () => {
return vscode.languages.registerTypeDefinitionProvider(selector,
new TypeScriptTypeDefinitionProvider(client));
});
}
return vscode.languages.registerTypeDefinitionProvider(selector,
new TypeScriptTypeDefinitionProvider(client));
}
@@ -108,19 +108,17 @@ export class TypeScriptServerSpawner {
args.push('--syntaxOnly');
}
if (apiVersion.gte(API.v206)) {
if (apiVersion.gte(API.v250)) {
args.push('--useInferredProjectPerProjectRoot');
} else {
args.push('--useSingleInferredProject');
}
if (configuration.disableAutomaticTypeAcquisition || kind === 'syntax') {
args.push('--disableAutomaticTypingAcquisition');
}
if (apiVersion.gte(API.v250)) {
args.push('--useInferredProjectPerProjectRoot');
} else {
args.push('--useSingleInferredProject');
}
if (apiVersion.gte(API.v208) && kind !== 'syntax') {
if (configuration.disableAutomaticTypeAcquisition || kind === 'syntax') {
args.push('--disableAutomaticTypingAcquisition');
}
if (kind !== 'syntax') {
args.push('--enableTelemetry');
}
@@ -226,4 +224,4 @@ class ChildServerProcess implements TsServerProcess {
kill(): void {
this._process.kill();
}
}
}
@@ -245,7 +245,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.logger.error(message, data);
}
private logTelemetry(eventName: string, properties?: { [prop: string]: string }) {
private logTelemetry(eventName: string, properties?: { readonly [prop: string]: string }) {
this.telemetryReporter.logTelemetry(eventName, properties);
}
@@ -287,13 +287,23 @@ export default class TypeScriptServiceClient extends Disposable implements IType
const apiVersion = this.versionPicker.currentVersion.apiVersion || API.defaultVersion;
this.onDidChangeTypeScriptVersion(currentVersion);
let mytoken = ++this.token;
const handle = this.typescriptServerSpawner.spawn(currentVersion, this.configuration, this.pluginManager);
this.serverState = new ServerState.Running(handle, apiVersion, undefined, true);
this.lastStart = Date.now();
/* __GDPR__
"tsserver.spawned" : {
"${include}": [
"${TypeScriptCommonProperties}"
],
"localTypeScriptVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.logTelemetry('tsserver.spawned', {
localTypeScriptVersion: this.versionProvider.localVersion ? this.versionProvider.localVersion.versionString : '',
});
handle.onError((err: Error) => {
if (this.token !== mytoken) {
// this is coming from an old process
@@ -454,10 +464,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
private setCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): void {
if (this.apiVersion.lt(API.v206)) {
return;
}
const args: Proto.SetCompilerOptionsForInferredProjectsArgs = {
options: this.getCompilerOptionsForInferredProjects(configuration)
};
@@ -534,12 +540,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
public normalizedPath(resource: vscode.Uri): string | undefined {
if (this.apiVersion.gte(API.v213)) {
if (resource.scheme === fileSchemes.walkThroughSnippet || resource.scheme === fileSchemes.untitled) {
const dirName = path.dirname(resource.path);
const fileName = this.inMemoryResourcePrefix + path.basename(resource.path);
return resource.with({ path: path.posix.join(dirName, fileName) }).toString(true);
}
if (resource.scheme === fileSchemes.walkThroughSnippet || resource.scheme === fileSchemes.untitled) {
const dirName = path.dirname(resource.path);
const fileName = this.inMemoryResourcePrefix + path.basename(resource.path);
return resource.with({ path: path.posix.join(dirName, fileName) }).toString(true);
}
if (resource.scheme !== fileSchemes.file) {
@@ -572,20 +576,19 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
public toResource(filepath: string): vscode.Uri {
if (this.apiVersion.gte(API.v213)) {
if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
) {
let resource = vscode.Uri.parse(filepath);
if (this.inMemoryResourcePrefix) {
const dirName = path.dirname(resource.path);
const fileName = path.basename(resource.path);
if (fileName.startsWith(this.inMemoryResourcePrefix)) {
resource = resource.with({ path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) });
}
if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
) {
let resource = vscode.Uri.parse(filepath);
if (this.inMemoryResourcePrefix) {
const dirName = path.dirname(resource.path);
const fileName = path.basename(resource.path);
if (fileName.startsWith(this.inMemoryResourcePrefix)) {
resource = resource.with({ path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) });
}
return resource;
}
return resource;
}
return this.bufferSyncSupport.toResource(filepath);
}
@@ -13,10 +13,6 @@ export default class API {
}
public static readonly defaultVersion = API.fromSimpleString('1.0.0');
public static readonly v203 = API.fromSimpleString('2.0.3');
public static readonly v206 = API.fromSimpleString('2.0.6');
public static readonly v208 = API.fromSimpleString('2.0.8');
public static readonly v213 = API.fromSimpleString('2.1.3');
public static readonly v220 = API.fromSimpleString('2.2.0');
public static readonly v222 = API.fromSimpleString('2.2.2');
public static readonly v230 = API.fromSimpleString('2.3.0');
@@ -66,4 +62,4 @@ export default class API {
public lt(other: API): boolean {
return !this.gte(other);
}
}
}
@@ -14,7 +14,7 @@ interface PackageInfo {
}
export default interface TelemetryReporter {
logTelemetry(eventName: string, properties?: { [prop: string]: string }): void;
logTelemetry(eventName: string, properties?: { readonly [prop: string]: string }): void;
dispose(): void;
}
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { env, extensions, ExtensionKind } from 'vscode';
import { env, extensions, ExtensionKind, UIKind } from 'vscode';
suite('env-namespace', () => {
@@ -44,4 +44,9 @@ suite('env-namespace', () => {
assert.fail();
}
});
test('env.uiKind', function () {
const kind = env.uiKind;
assert.equal(kind, UIKind.Desktop);
});
});
@@ -152,22 +152,6 @@ suite('window namespace tests', () => {
// });
suite('hideFromUser', () => {
// test('should fire onDidWriteData correctly', done => {
// const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
// let data = '';
// terminal.onDidWriteData(e => {
// data += e;
// if (data.indexOf('foo') !== -1) {
// const reg3 = window.onDidCloseTerminal(() => {
// reg3.dispose();
// done();
// });
// terminal.dispose();
// }
// });
// terminal.sendText('foo');
// });
test('should be available to terminals API', done => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
window.onDidOpenTerminal(t => {
@@ -247,33 +231,6 @@ suite('window namespace tests', () => {
window.createTerminal({ name: 'c', pty });
});
test('should fire Terminal.onData on write', (done) => {
const reg1 = window.onDidOpenTerminal(async term => {
equal(terminal, term);
reg1.dispose();
const reg2 = terminal.onDidWriteData(data => {
equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
await startPromise;
writeEmitter.fire('bar');
});
let startResolve: () => void;
const startPromise: Promise<void> = new Promise<void>(r => startResolve = r);
const writeEmitter = new EventEmitter<string>();
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
open: () => startResolve(),
close: () => {}
};
const terminal = window.createTerminal({ name: 'foo', pty });
});
// The below tests depend on global UI state and each other
// test('should not provide dimensions on start as the terminal has not been shown yet', (done) => {
// const reg1 = window.onDidOpenTerminal(term => {
@@ -21,9 +21,9 @@ suite('workspace-namespace', () => {
const taskName = 'First custom task';
const reg1 = vscode.window.onDidOpenTerminal(term => {
reg1.dispose();
const reg2 = term.onDidWriteData(e => {
const reg2 = vscode.window.onDidWriteTerminalData(e => {
reg2.dispose();
assert.equal(e, 'testing\r\n');
assert.equal(e.data, 'testing\r\n');
term.dispose();
});
});
@@ -897,4 +897,23 @@ suite('workspace-namespace', () => {
await delay(10);
}
}
test('The api workspace.applyEdit failed for some case of mixing resourceChange and textEdit #80688', async function () {
const file1 = await createRandomFile();
const file2 = await createRandomFile();
let we = new vscode.WorkspaceEdit();
we.insert(file1, new vscode.Position(0, 0), 'import1;');
const file2Name = basename(file2.fsPath);
const file2NewUri = vscode.Uri.parse(file2.toString().replace(file2Name, `new/${file2Name}`));
we.renameFile(file2, file2NewUri);
we.insert(file1, new vscode.Position(0, 0), 'import2;');
await vscode.workspace.applyEdit(we);
const document = await vscode.workspace.openTextDocument(file1);
// const expected = 'import1;import2;';
const expected2 = 'import2;import1;';
assert.equal(document.getText(), expected2);
});
});
+4 -4
View File
@@ -2,7 +2,7 @@
# yarn lockfile v1
typescript@3.6.3-insiders.20190909:
version "3.6.3-insiders.20190909"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3-insiders.20190909.tgz#65b6b2d809288311a970819849e1964ac73e1fda"
integrity sha512-Lr7ONd8Y05EhrI+zKoI5tgvO5dhuRDrK5pyOLG33DeMln8zb8w7Yc8AoIEyqvxB5Btj9F7zBmXBXJdTI3SuX0Q==
typescript@3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
+4 -3
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.39.0",
"distro": "cb09cfe757e21433d881c094f41fa24e0545d05d",
"distro": "bb5d0e1cef8b8321ebe0f7a208cf1d70720c1dec",
"author": {
"name": "Microsoft Corporation"
},
@@ -38,7 +38,7 @@
"keytar": "^4.11.0",
"native-is-elevated": "0.3.0",
"native-keymap": "2.0.0",
"native-watchdog": "1.0.0",
"native-watchdog": "1.2.0",
"node-pty": "0.9.0-beta19",
"nsfw": "1.2.5",
"onigasm-umd": "^2.2.2",
@@ -97,7 +97,7 @@
"gulp-rename": "^1.2.0",
"gulp-replace": "^0.5.4",
"gulp-shell": "^0.6.5",
"gulp-tsb": "4.0.2",
"gulp-tsb": "4.0.4",
"gulp-tslint": "^8.1.3",
"gulp-untar": "^0.0.7",
"gulp-vinyl-zip": "^2.1.2",
@@ -136,6 +136,7 @@
"vscode-debugprotocol": "1.36.0",
"vscode-nls-dev": "^3.3.1",
"webpack": "^4.16.5",
"webpack-cli": "^3.3.8",
"webpack-stream": "^5.1.1"
},
"repository": {
+1 -1
View File
@@ -1,3 +1,3 @@
disturl "http://nodejs.org/dist"
target "10.11.0"
target "12.4.0"
runtime "node"
+1 -1
View File
@@ -10,7 +10,7 @@
"https-proxy-agent": "^2.2.1",
"iconv-lite": "0.5.0",
"jschardet": "1.6.0",
"native-watchdog": "1.0.0",
"native-watchdog": "1.2.0",
"node-pty": "0.9.0-beta19",
"nsfw": "1.2.5",
"onigasm-umd": "^2.2.2",
+4 -4
View File
@@ -725,10 +725,10 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
native-watchdog@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.0.0.tgz#97344e83cd6815a8c8e6c44a52e7be05832e65ca"
integrity sha512-HKQATz5KLUMPyQQ/QaalzgTXaGz2plYPBxjyalaR4ECIu/UznXY8YJD+a9SLkkcvtxnJ8/zHLY3xik06vUZ7uA==
native-watchdog@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.2.0.tgz#9c710093ac6e9e60b19517cb1ef4ac9d7c997395"
integrity sha512-jOOoA3PLSxt1adaeuEW7ymV9cApZiDxn4y4iFs7b4sP73EG+5Lsz+OgUNFcGMyrtznTw6ZvlLcilIN4jeAIgaQ==
node-addon-api@1.6.2:
version "1.6.2"
+7 -6
View File
@@ -8,6 +8,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
else
ROOT=$(dirname $(dirname $(readlink -f $0)))
VSCODEUSERDATADIR=`mktemp -d 2>/dev/null`
LINUX_NO_SANDBOX="--no-sandbox" # workaround Electron 6 issue on Linux when running tests in container
fi
cd $ROOT
@@ -33,16 +34,16 @@ else
fi
# Integration tests in AMD
./scripts/test.sh --runGlob **/*.integrationTest.js "$@"
./scripts/test.sh $LINUX_NO_SANDBOX --runGlob **/*.integrationTest.js "$@"
# Tests in the extension host
"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
mkdir -p $ROOT/extensions/emmet/test-fixtures
"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR
rm -rf $ROOT/extensions/emmet/test-fixtures
# Remote Integration Tests
+8 -10
View File
@@ -21,6 +21,7 @@ process.on('SIGPIPE', () => {
//#endregion
//#region Add support for redirecting the loading of node modules
exports.injectNodeModuleLookupPath = function (injectPath) {
if (!injectPath) {
throw new Error('Missing injectPath');
@@ -36,10 +37,8 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
const originalResolveLookupPaths = Module._resolveLookupPaths;
// @ts-ignore
Module._resolveLookupPaths = function (moduleName, parent, newReturn) {
const result = originalResolveLookupPaths(moduleName, parent, newReturn);
const paths = newReturn ? result : result[1];
Module._resolveLookupPaths = function (moduleName, parent) {
const paths = originalResolveLookupPaths(moduleName, parent);
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === nodeModulesPath) {
paths.splice(i, 0, injectPath);
@@ -47,7 +46,7 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
}
}
return result;
return paths;
};
};
//#endregion
@@ -71,11 +70,10 @@ exports.enableASARSupport = function (nodeModulesPath) {
// @ts-ignore
const originalResolveLookupPaths = Module._resolveLookupPaths;
// @ts-ignore
Module._resolveLookupPaths = function (request, parent, newReturn) {
const result = originalResolveLookupPaths(request, parent, newReturn);
const paths = newReturn ? result : result[1];
// @ts-ignore
Module._resolveLookupPaths = function (request, parent) {
const paths = originalResolveLookupPaths(request, parent);
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === NODE_MODULES_PATH) {
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
@@ -83,7 +81,7 @@ exports.enableASARSupport = function (nodeModulesPath) {
}
}
return result;
return paths;
};
};
//#endregion
+6 -1
View File
@@ -17,7 +17,7 @@ const paths = require('./paths');
// @ts-ignore
const product = require('../product.json');
// @ts-ignore
const app = require('electron').app;
const { app, protocol } = require('electron');
// Enable portable support
const portable = bootstrap.configurePortable();
@@ -33,6 +33,11 @@ app.setPath('userData', userDataPath);
// Update cwd based on environment and platform
setCurrentWorkingDirectory();
// Register custom schemes with privileges
protocol.registerSchemesAsPrivileged([
{ scheme: 'vscode-resource', privileges: { secure: true, supportFetchAPI: true, corsEnabled: true } }
]);
// Global app listeners
registerListeners();
+1293 -448
View File
File diff suppressed because it is too large Load Diff
+5 -7
View File
@@ -559,13 +559,11 @@ class SizeUtils {
// Position & Dimension
export class Dimension {
public width: number;
public height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
constructor(
public readonly width: number,
public readonly height: number,
) { }
static equals(a: Dimension | undefined, b: Dimension | undefined): boolean {
if (a === b) {
@@ -1193,7 +1191,7 @@ export function asDomUri(uri: URI): URI {
return uri;
}
if (Schemas.vscodeRemote === uri.scheme) {
return RemoteAuthorities.rewrite(uri.authority, uri.path);
return RemoteAuthorities.rewrite(uri);
}
return uri;
}
@@ -581,6 +581,19 @@ export class InputBox extends Widget {
}
}
public insertAtCursor(text: string): void {
const inputElement = this.inputElement;
const start = inputElement.selectionStart;
const end = inputElement.selectionEnd;
const content = inputElement.value;
if (start !== null && end !== null) {
this.value = content.substr(0, start) + text + content.substr(end);
inputElement.setSelectionRange(start + 1, start + 1);
this.layout();
}
}
public dispose(): void {
this._hideMessage();
+1 -1
View File
@@ -862,7 +862,7 @@ export interface IListStyles {
}
const defaultStyles: IListStyles = {
listFocusBackground: Color.fromHex('#073655'),
listFocusBackground: Color.fromHex('#7FB0D0'),
listActiveSelectionBackground: Color.fromHex('#0E639C'),
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
listFocusAndSelectionBackground: Color.fromHex('#094771'),
@@ -10,5 +10,5 @@
}
.octicon-animation-spin {
animation: octicon-spin 2s linear infinite;
}
animation: octicon-spin 1.5s linear infinite;
}
@@ -10,10 +10,6 @@ body {
font-family: var(--version) !important;
}
body[data-octicons-update="enabled"] .monaco-workbench .part.statusbar > .items-container > .statusbar-item span.octicon {
font-size: 16px;
}
body[data-octicons-update="enabled"] .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a {
display: flex;
align-items: center;
@@ -1,6 +1,6 @@
@font-face {
font-family: "octicons2";
src: url("./octicons2.ttf?999646dfb2baa29479ee3c93bf9c964e") format("truetype");
src: url("./octicons2.ttf?90586c9ac0aa804395e9c9c0d15d1094") format("truetype");
}
.octicon, .mega-octicon {
+35 -28
View File
@@ -98,9 +98,11 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}
if (result.bubble === TreeDragOverBubble.Up) {
const parentNode = targetNode.parent;
const model = this.modelProvider();
const parentIndex = parentNode && model.getListIndex(model.getNodeLocation(parentNode));
const ref = model.getNodeLocation(targetNode);
const parentRef = model.getParentNodeLocation(ref);
const parentNode = model.getNode(parentRef);
const parentIndex = parentRef && model.getListIndex(parentRef);
return this.onDragOver(data, parentNode, parentIndex, originalEvent, false);
}
@@ -155,7 +157,12 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
enableKeyboardNavigation: options.simpleKeyboardNavigation,
ariaProvider: {
getSetSize(node) {
return node.parent!.visibleChildrenCount;
const model = modelProvider();
const ref = model.getNodeLocation(node);
const parentRef = model.getParentNodeLocation(ref);
const parentNode = model.getNode(parentRef);
return parentNode.visibleChildrenCount;
},
getPosInSet(node) {
return node.visibleChildIndex + 1;
@@ -233,7 +240,7 @@ class EventCollection<T> implements Collection<T> {
}
}
class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
private static DefaultIndent = 8;
@@ -251,6 +258,7 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
constructor(
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
private modelProvider: () => ITreeModel<T, TFilterData, TRef>,
onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>,
private activeNodes: Collection<ITreeNode<T, TFilterData>>,
options: ITreeRendererOptions = {}
@@ -381,10 +389,19 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
}
const disposableStore = new DisposableStore();
const model = this.modelProvider();
let node = target;
while (node.parent && node.parent.parent) {
const parent = node.parent;
while (true) {
const ref = model.getNodeLocation(node);
const parentRef = model.getParentNodeLocation(ref);
if (!parentRef) {
break;
}
const parent = model.getNode(parentRef);
const guide = $<HTMLDivElement>('.indent-guide', { style: `width: ${this.indent}px` });
if (this.activeIndentNodes.has(parent)) {
@@ -412,12 +429,16 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
}
const set = new Set<ITreeNode<T, TFilterData>>();
const model = this.modelProvider();
nodes.forEach(node => {
const ref = model.getNodeLocation(node);
const parentRef = model.getParentNodeLocation(ref);
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
set.add(node);
} else if (node.parent) {
set.add(node.parent);
} else if (parentRef) {
set.add(model.getNode(parentRef));
}
});
@@ -1153,7 +1174,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable {
protected view: TreeNodeList<T, TFilterData, TRef>;
private renderers: TreeRenderer<T, TFilterData, any>[];
private renderers: TreeRenderer<T, TFilterData, TRef, any>[];
protected model: ITreeModel<T, TFilterData, TRef>;
private focus: Trait<T>;
private selection: Trait<T>;
@@ -1211,7 +1232,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
const activeNodes = new EventCollection(onDidChangeActiveNodes.event);
this.disposables.push(activeNodes);
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, any>(r, onDidChangeCollapseStateRelay.event, activeNodes, _options));
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, TRef, any>(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, _options));
this.disposables.push(...this.renderers);
let filter: TypeFilter<T> | undefined;
@@ -1383,7 +1404,9 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
// Tree navigation
getParentElement(location: TRef): T {
return this.model.getParentElement(location);
const parentRef = this.model.getParentNodeLocation(location);
const parentNode = this.model.getNode(parentRef);
return parentNode.element;
}
getFirstElementChild(location: TRef): T | undefined {
@@ -1539,7 +1562,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
if (!didChange) {
const parentLocation = this.model.getParentNodeLocation(location);
if (parentLocation === null) {
if (!parentLocation) {
return;
}
@@ -1641,22 +1664,6 @@ class TreeNavigator<T extends NonNullable<any>, TFilterData, TRef> implements IT
return this.current();
}
parent(): T | null {
if (this.index < 0 || this.index >= this.view.length) {
return null;
}
const node = this.view.element(this.index);
if (!node.parent) {
this.index = -1;
return this.current();
}
this.index = this.model.getListIndex(this.model.getNodeLocation(node.parent));
return this.current();
}
first(): T | null {
this.index = 0;
return this.current();
+163 -34
View File
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError } from 'vs/base/browser/ui/tree/tree';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
@@ -18,6 +18,7 @@ import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors
import { toggleClass } from 'vs/base/browser/dom';
import { values } from 'vs/base/common/map';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
interface IAsyncDataTreeNode<TInput, T> {
element: TInput | T;
@@ -66,10 +67,11 @@ interface IDataTreeListTemplateData<T> {
templateData: T;
}
type AsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>, ITreeNode<TInput | T, TFilterData>>;
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
get element(): T { return this.node.element!.element as T; }
get parent(): ITreeNode<T, TFilterData> | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); }
get children(): ITreeNode<T, TFilterData>[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
get depth(): number { return this.node.depth; }
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
@@ -82,14 +84,15 @@ class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInp
constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
}
class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
readonly templateId: string;
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
private disposables: IDisposable[] = [];
constructor(
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
protected renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
) {
this.templateId = renderer.templateId;
@@ -101,7 +104,7 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
}
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
@@ -111,7 +114,7 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
}
@@ -243,25 +246,6 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
};
}
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
let collapsed: boolean | undefined;
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
collapsed = false;
} else {
collapsed = node.collapsedByDefault;
}
node.collapsedByDefault = undefined;
return {
element: node,
children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)) : [],
collapsible: node.hasChildren,
collapsed
};
}
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, Pick<IAbstractTreeOptions<T, TFilterData>, Exclude<keyof IAbstractTreeOptions<T, TFilterData>, 'collapseByDefault'>> {
@@ -304,7 +288,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
private readonly autoExpandSingleChildren: boolean;
private readonly _onDidRender = new Emitter<void>();
private readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
protected readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
protected readonly nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new AsyncDataTreeNodeWrapper(node));
protected readonly disposables: IDisposable[] = [];
@@ -351,11 +337,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.sorter = options.sorter;
this.collapseByDefault = options.collapseByDefault;
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
this.tree = new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
this.tree = this.createTree(user, container, delegate, renderers, options);
this.root = createAsyncDataTreeNode({
element: undefined!,
@@ -375,6 +357,20 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
}
protected createTree(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<T, TFilterData, any>[],
options: IAsyncDataTreeOptions<T, TFilterData>
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new AsyncDataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
}
updateOptions(options: IAsyncDataTreeOptionsUpdate = {}): void {
this.tree.updateOptions(options);
}
@@ -510,7 +506,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
getNode(element: TInput | T = this.root.element): ITreeNode<TInput | T, TFilterData> {
const dataNode = this.getDataNode(element);
const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
return new AsyncDataTreeNodeWrapper<TInput, T, TFilterData>(node);
return this.nodeMapper.map(node);
}
collapse(element: T, recursive: boolean = false): boolean {
@@ -876,7 +872,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
}
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
const children = node.children.map(c => asTreeElement(c, viewStateContext));
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
this.tree.setChildren(node === this.root ? null : node, children);
if (node !== this.root) {
@@ -886,6 +882,25 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this._onDidRender.fire();
}
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
let collapsed: boolean | undefined;
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
collapsed = false;
} else {
collapsed = node.collapsedByDefault;
}
node.collapsedByDefault = undefined;
return {
element: node,
children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => this.asTreeElement(child, viewStateContext)) : [],
collapsible: node.hasChildren,
collapsed
};
}
// view state
getViewState(): IAsyncDataTreeViewState {
@@ -918,3 +933,117 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
dispose(this.disposables);
}
}
type CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>>;
class CompressibleAsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData> {
get element(): ICompressedTreeNode<TInput | T> {
return {
elements: this.node.element.elements.map(e => e.element),
incompressible: this.node.element.incompressible
};
}
get children(): ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>[] { return this.node.children.map(node => new CompressibleAsyncDataTreeNodeWrapper(node)); }
get depth(): number { return this.node.depth; }
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
get collapsible(): boolean { return this.node.collapsible; }
get collapsed(): boolean { return this.node.collapsed; }
get visible(): boolean { return this.node.visible; }
get filterData(): TFilterData | undefined { return this.node.filterData; }
constructor(private node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>) { }
}
class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ICompressibleTreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
readonly templateId: string;
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
private disposables: IDisposable[] = [];
constructor(
protected renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>,
protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
private compressibleNodeMapperProvider: () => CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData>,
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
) {
this.templateId = renderer.templateId;
}
renderTemplate(container: HTMLElement): IDataTreeListTemplateData<TTemplateData> {
const templateData = this.renderer.renderTemplate(container);
return { templateData };
}
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
this.renderer.renderCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
toggleClass(twistieElement, 'loading', element.slow);
return false;
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.disposeTemplate(templateData.templateData);
}
dispose(): void {
this.renderedNodes.clear();
this.disposables = dispose(this.disposables);
}
}
export interface ITreeCompressionDelegate<T> {
isIncompressible(element: T): boolean;
}
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
constructor(
user: string,
container: HTMLElement,
virtualDelegate: IListVirtualDelegate<T>,
private compressionDelegate: ITreeCompressionDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData> = {}
) {
super(user, container, virtualDelegate, renderers, dataSource, options);
}
protected createTree(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
options: IAsyncDataTreeOptions<T, TFilterData>
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
}
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ICompressedTreeElement<IAsyncDataTreeNode<TInput, T>> {
return {
incompressible: this.compressionDelegate.isIncompressible(node.element as T),
...super.asTreeElement(node, viewStateContext)
};
}
}
@@ -1,57 +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 { Iterator, ISequence } from 'vs/base/common/iterator';
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Event } from 'vs/base/common/event';
import { CompressedTreeModel, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
}
export class CompressedObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<ICompressedTreeNode<T> | null, TFilterData, T | null> {
protected model!: CompressedTreeModel<T, TFilterData>;
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<ICompressedTreeNode<T> | null, TFilterData>> { return this.model.onDidChangeCollapseState; }
constructor(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<ICompressedTreeNode<T>>,
renderers: ITreeRenderer<ICompressedTreeNode<T>, TFilterData, any>[],
options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData> = {}
) {
super(user, container, delegate, renderers, options);
}
setChildren(
element: T | null,
children?: ISequence<ITreeElement<T>>
): Iterator<ITreeElement<T | null>> {
return this.model.setChildren(element, children);
}
rerender(element?: T): void {
if (element === undefined) {
this.view.rerender();
return;
}
this.model.rerender(element);
}
resort(element: T, recursive = true): void {
this.model.resort(element, recursive);
}
protected createModel(user: string, view: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData>): ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
return new CompressedTreeModel(user, view, options);
}
}
@@ -6,19 +6,32 @@
import { ISpliceable } from 'vs/base/common/sequence';
import { Iterator, ISequence } from 'vs/base/common/iterator';
import { Event } from 'vs/base/common/event';
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
// Exported only for test reasons, do not use directly
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
readonly incompressible?: boolean;
}
// Exported only for test reasons, do not use directly
export interface ICompressedTreeNode<T> {
readonly elements: T[];
readonly incompressible: boolean;
}
function noCompress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
const elements = [element.element];
const incompressible = element.incompressible || false;
return {
element: { elements, incompressible },
children: Iterator.map(Iterator.from(element.children), noCompress)
};
}
// Exported only for test reasons, do not use directly
export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
const elements = [element.element];
const incompressible = element.incompressible || false;
@@ -49,7 +62,7 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
};
}
export function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
let children: Iterator<ICompressedTreeElement<T>>;
if (index < element.element.elements.length - 1) {
@@ -65,11 +78,12 @@ export function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, in
return { element: element.element.elements[index], children };
}
// Exported only for test reasons, do not use directly
export function decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>): ICompressedTreeElement<T> {
return _decompress(element, 0);
}
export function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
if (treeElement.element === element) {
return { element, children };
}
@@ -80,9 +94,10 @@ export function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, ch
};
}
export interface ICompressedTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
// Exported only for test reasons, do not use directly
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
readonly rootRef = null;
@@ -92,39 +107,77 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
private enabled: boolean = true;
get size(): number { return this.nodes.size; }
constructor(
private user: string,
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
options: ICompressedTreeModelOptions<T, TFilterData> = {}
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
) {
this.model = new ObjectTreeModel(user, list, options);
}
setChildren(
element: T | null,
children: ISequence<ICompressedTreeElement<T>> | undefined,
onDidCreateNode?: (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => void
): Iterator<ITreeElement<T | null>> {
children: ISequence<ICompressedTreeElement<T>> | undefined
): void {
if (element === null) {
const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
this._setChildren(null, compressedChildren);
return;
}
const compressedNode = this.nodes.get(element);
if (!compressedNode) {
throw new Error('Unknown compressed tree node');
}
const node = this.model.getNode(compressedNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
const compressedParentNode = this.model.getParentNodeLocation(compressedNode);
const parent = this.model.getNode(compressedParentNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
const decompressedElement = decompress(node);
const splicedElement = splice(decompressedElement, element, Iterator.from(children));
const recompressedElement = (this.enabled ? compress : noCompress)(splicedElement);
const parentChildren = parent.children
.map(child => child === node ? recompressedElement : child);
this._setChildren(parent.element, parentChildren);
}
isCompressionEnabled(): boolean {
return this.enabled;
}
setCompressionEnabled(enabled: boolean): void {
if (enabled === this.enabled) {
return;
}
this.enabled = enabled;
const root = this.model.getNode();
const rootChildren = Iterator.from(root.children as ITreeNode<ICompressedTreeNode<T>>[]);
const decompressedRootChildren = Iterator.map(rootChildren, decompress);
const recompressedRootChildren = Iterator.map(decompressedRootChildren, enabled ? compress : noCompress);
this._setChildren(null, recompressedRootChildren);
}
private _setChildren(
node: ICompressedTreeNode<T> | null,
children: ISequence<ITreeElement<ICompressedTreeNode<T>>> | undefined
): void {
const insertedElements = new Set<T | null>();
const _onDidCreateNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
for (const element of node.element.elements) {
insertedElements.add(element);
this.nodes.set(element, node.element);
}
// if (this.identityProvider) {
// const id = this.identityProvider.getId(node.element).toString();
// insertedElementIds.add(id);
// this.nodesByIdentity.set(id, node);
// }
if (onDidCreateNode) {
onDidCreateNode(node);
}
};
const _onDidDeleteNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
@@ -133,40 +186,9 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
this.nodes.delete(element);
}
}
// if (this.identityProvider) {
// const id = this.identityProvider.getId(node.element).toString();
// if (!insertedElementIds.has(id)) {
// this.nodesByIdentity.delete(id);
// }
// }
if (onDidDeleteNode) {
onDidDeleteNode(node);
}
};
if (element === null) {
const compressedChildren = Iterator.map(Iterator.from(children), compress);
const result = this.model.setChildren(null, compressedChildren, _onDidCreateNode, _onDidDeleteNode);
return Iterator.map(result, decompress);
}
const compressedNode = this.nodes.get(element);
const node = this.model.getNode(compressedNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
const parent = node.parent!;
const decompressedElement = decompress(node);
const splicedElement = splice(decompressedElement, element, Iterator.from(children));
const recompressedElement = compress(splicedElement);
const parentChildren = parent.children
.map(child => child === node ? recompressedElement : child);
this.model.setChildren(parent.element, parentChildren, _onDidCreateNode, _onDidDeleteNode);
// TODO
return Iterator.empty();
this.model.setChildren(node, children, _onDidCreateNode, _onDidDeleteNode);
}
getListIndex(location: T | null): number {
@@ -211,11 +233,6 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
return parentNode.elements[parentNode.elements.length - 1];
}
getParentElement(location: T | null): ICompressedTreeNode<T> | null {
const compressedNode = this.getCompressedNode(location);
return this.model.getParentElement(compressedNode);
}
getFirstElementChild(location: T | null): ICompressedTreeNode<T> | null | undefined {
const compressedNode = this.getCompressedNode(location);
return this.model.getFirstElementChild(compressedNode);
@@ -265,7 +282,7 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
this.model.resort(compressedNode, recursive);
}
private getCompressedNode(element: T | null): ICompressedTreeNode<T> | null {
getCompressedNode(element: T | null): ICompressedTreeNode<T> | null {
if (element === null) {
return null;
}
@@ -280,72 +297,113 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
}
}
// Compressible Object Tree
export type ElementMapper<T> = (elements: T[]) => T;
export const DefaultElementMapper: ElementMapper<any> = elements => elements[elements.length - 1];
export type NodeMapper<T, TFilterData> = (node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>) => ITreeNode<T | null, TFilterData>;
export type CompressedNodeUnwrapper<T> = (node: ICompressedTreeNode<T>) => T;
type CompressedNodeWeakMapper<T, TFilterData> = WeakMapper<ITreeNode<ICompressedTreeNode<T> | null, TFilterData>, ITreeNode<T | null, TFilterData>>;
function mapNode<T, TFilterData>(elementMapper: ElementMapper<T>, node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>): ITreeNode<T | null, TFilterData> {
class CompressedTreeNodeWrapper<T, TFilterData> implements ITreeNode<T | null, TFilterData> {
get element(): T | null { return this.node.element === null ? null : this.unwrapper(this.node.element); }
get children(): ITreeNode<T | null, TFilterData>[] { return this.node.children.map(node => new CompressedTreeNodeWrapper(this.unwrapper, node)); }
get depth(): number { return this.node.depth; }
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
get collapsible(): boolean { return this.node.collapsible; }
get collapsed(): boolean { return this.node.collapsed; }
get visible(): boolean { return this.node.visible; }
get filterData(): TFilterData | undefined { return this.node.filterData; }
constructor(
private unwrapper: CompressedNodeUnwrapper<T>,
private node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>
) { }
}
function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilterData>, list: ISpliceable<ITreeNode<T, TFilterData>>): ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>> {
return {
...node,
element: node.element === null ? null : elementMapper(node.element.elements),
children: node.children.map(child => mapNode(elementMapper, child)),
parent: typeof node.parent === 'undefined' ? node.parent : mapNode(elementMapper, node.parent)
splice(start: number, deleteCount: number, toInsert: ITreeNode<ICompressedTreeNode<T>, TFilterData>[]): void {
list.splice(start, deleteCount, toInsert.map(node => nodeMapper.map(node)) as ITreeNode<T, TFilterData>[]);
}
};
}
function createNodeMapper<T, TFilterData>(elementMapper: ElementMapper<T>): NodeMapper<T, TFilterData> {
return node => mapNode(elementMapper, node);
function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwrapper<T>, options: ICompressibleObjectTreeModelOptions<T, TFilterData>): ICompressedObjectTreeModelOptions<T, TFilterData> {
return {
...options,
sorter: options.sorter && {
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
return options.sorter!.compare(compressedNodeUnwrapper(node), compressedNodeUnwrapper(otherNode));
}
},
identityProvider: options.identityProvider && {
getId(node: ICompressedTreeNode<T>): { toString(): string; } {
return options.identityProvider!.getId(compressedNodeUnwrapper(node));
}
},
filter: options.filter && {
filter(node: ICompressedTreeNode<T>, parentVisibility: TreeVisibility): TreeFilterResult<TFilterData> {
return options.filter!.filter(compressedNodeUnwrapper(node), parentVisibility);
}
}
};
}
export interface ICompressedObjectTreeModelOptions<T, TFilterData> extends ICompressedTreeModelOptions<T, TFilterData> {
export interface ICompressibleObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<T, TFilterData> {
readonly elementMapper?: ElementMapper<T>;
}
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
readonly rootRef = null;
get onDidSplice(): Event<ITreeModelSpliceEvent<T | null, TFilterData>> {
return Event.map(this.model.onDidSplice, ({ insertedNodes, deletedNodes }) => ({
insertedNodes: insertedNodes.map(this.mapNode),
deletedNodes: deletedNodes.map(this.mapNode),
insertedNodes: insertedNodes.map(node => this.nodeMapper.map(node)),
deletedNodes: deletedNodes.map(node => this.nodeMapper.map(node)),
}));
}
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T | null, TFilterData>> {
return Event.map(this.model.onDidChangeCollapseState, ({ node, deep }) => ({
node: this.mapNode(node),
node: this.nodeMapper.map(node),
deep
}));
}
get onDidChangeRenderNodeCount(): Event<ITreeNode<T | null, TFilterData>> {
return Event.map(this.model.onDidChangeRenderNodeCount, this.mapNode);
return Event.map(this.model.onDidChangeRenderNodeCount, node => this.nodeMapper.map(node));
}
private mapElement: ElementMapper<T | null>;
private mapNode: NodeMapper<T | null, TFilterData>;
private model: CompressedTreeModel<T, TFilterData>;
private elementMapper: ElementMapper<T>;
private nodeMapper: CompressedNodeWeakMapper<T, TFilterData>;
private model: CompressedObjectTreeModel<T, TFilterData>;
constructor(
user: string,
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
list: ISpliceable<ITreeNode<T, TFilterData>>,
options: ICompressibleObjectTreeModelOptions<T, TFilterData> = {}
) {
this.mapElement = options.elementMapper || DefaultElementMapper;
this.mapNode = createNodeMapper(this.mapElement);
this.model = new CompressedTreeModel(user, list, options);
this.elementMapper = options.elementMapper || DefaultElementMapper;
const compressedNodeUnwrapper: CompressedNodeUnwrapper<T> = node => this.elementMapper(node.elements);
this.nodeMapper = new WeakMapper(node => new CompressedTreeNodeWrapper(compressedNodeUnwrapper, node));
this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options));
}
setChildren(
element: T | null,
children: ISequence<ITreeElement<T>> | undefined
): Iterator<ITreeElement<T>> {
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {
this.model.setChildren(element, children);
}
// TODO
return Iterator.empty();
isCompressionEnabled(): boolean {
return this.model.isCompressionEnabled();
}
setCompressionEnabled(enabled: boolean): void {
this.model.setCompressionEnabled(enabled);
}
getListIndex(location: T | null): number {
@@ -357,7 +415,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
}
getNode(location?: T | null | undefined): ITreeNode<T | null, any> {
return this.mapNode(this.model.getNode(location));
return this.nodeMapper.map(this.model.getNode(location));
}
getNodeLocation(node: ITreeNode<T | null, any>): T | null {
@@ -368,16 +426,6 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
return this.model.getParentNodeLocation(location);
}
getParentElement(location: T | null): T | null {
const result = this.model.getParentElement(location);
if (result === null) {
return result;
}
return this.mapElement(result.elements);
}
getFirstElementChild(location: T | null): T | null | undefined {
const result = this.model.getFirstElementChild(location);
@@ -385,7 +433,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
return result;
}
return this.mapElement(result.elements);
return this.elementMapper(result.elements);
}
getLastElementAncestor(location?: T | null | undefined): T | null | undefined {
@@ -395,7 +443,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
return result;
}
return this.mapElement(result.elements);
return this.elementMapper(result.elements);
}
isCollapsible(location: T | null): boolean {
@@ -429,4 +477,8 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
resort(element: T | null = null, recursive = true): void {
return this.model.resort(element, recursive);
}
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
return this.model.getNode(element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
}
}
+2 -2
View File
@@ -28,8 +28,8 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
super(user, container, delegate, renderers, options);
}
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): Iterator<ITreeElement<T>> {
return this.model.splice(location, deleteCount, toInsert);
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): void {
this.model.splice(location, deleteCount, toInsert);
}
rerender(location?: number[]): void {
+31 -42
View File
@@ -9,9 +9,10 @@ import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
import { ISequence, Iterator } from 'vs/base/common/iterator';
import { ISpliceable } from 'vs/base/common/sequence';
interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
readonly parent: IMutableTreeNode<T, TFilterData> | undefined;
readonly children: IMutableTreeNode<T, TFilterData>[];
// Exported for tests
export interface IIndexTreeNode<T, TFilterData = void> extends ITreeNode<T, TFilterData> {
readonly parent: IIndexTreeNode<T, TFilterData> | undefined;
readonly children: IIndexTreeNode<T, TFilterData>[];
visibleChildrenCount: number;
visibleChildIndex: number;
collapsible: boolean;
@@ -33,13 +34,6 @@ export function getVisibleState(visibility: boolean | TreeVisibility): TreeVisib
}
}
function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
const { element, collapsed } = node;
const children = Iterator.map(Iterator.fromArray(node.children), treeNodeToElement);
return { element, children, collapsed };
}
export interface IIndexTreeModelOptions<T, TFilterData> {
readonly collapseByDefault?: boolean; // defaults to false
readonly filter?: ITreeFilter<T, TFilterData>;
@@ -65,7 +59,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
readonly rootRef = [];
private root: IMutableTreeNode<T, TFilterData>;
private root: IIndexTreeNode<T, TFilterData>;
private eventBufferer = new EventBufferer();
private _onDidChangeCollapseState = new Emitter<ICollapseStateChangeEvent<T, TFilterData>>();
@@ -112,7 +106,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
toInsert?: ISequence<ITreeElement<T>>,
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
): Iterator<ITreeElement<T>> {
): void {
if (location.length === 0) {
throw new TreeError(this.user, 'Invalid tree location');
}
@@ -136,7 +130,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
}
const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
const nodesToInsert: IIndexTreeNode<T, TFilterData>[] = [];
let insertedVisibleChildrenCount = 0;
let renderNodeCount = 0;
@@ -190,9 +184,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
deletedNodes.forEach(visit);
}
const result = Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
return result;
}
rerender(location: number[]): void {
@@ -275,7 +267,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _setListNodeCollapseState(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, update: CollapseStateUpdate): boolean {
private _setListNodeCollapseState(node: IIndexTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, update: CollapseStateUpdate): boolean {
const result = this._setNodeCollapseState(node, update, false);
if (!revealed || !node.visible || !result) {
@@ -290,7 +282,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _setNodeCollapseState(node: IMutableTreeNode<T, TFilterData>, update: CollapseStateUpdate, deep: boolean): boolean {
private _setNodeCollapseState(node: IIndexTreeNode<T, TFilterData>, update: CollapseStateUpdate, deep: boolean): boolean {
let result: boolean;
if (node === this.root) {
@@ -341,13 +333,13 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
private createTreeNode(
treeElement: ITreeElement<T>,
parent: IMutableTreeNode<T, TFilterData>,
parent: IIndexTreeNode<T, TFilterData>,
parentVisibility: TreeVisibility,
revealed: boolean,
treeListElements: ITreeNode<T, TFilterData>[],
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void
): IMutableTreeNode<T, TFilterData> {
const node: IMutableTreeNode<T, TFilterData> = {
): IIndexTreeNode<T, TFilterData> {
const node: IIndexTreeNode<T, TFilterData> = {
parent,
element: treeElement.element,
children: [],
@@ -404,7 +396,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return node;
}
private updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
private updateNodeAfterCollapseChange(node: IIndexTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
const previousRenderNodeCount = node.renderNodeCount;
const result: ITreeNode<T, TFilterData>[] = [];
@@ -414,7 +406,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[]): number {
private _updateNodeAfterCollapseChange(node: IIndexTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[]): number {
if (node.visible === false) {
return 0;
}
@@ -432,7 +424,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return node.renderNodeCount;
}
private updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
private updateNodeAfterFilterChange(node: IIndexTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
const previousRenderNodeCount = node.renderNodeCount;
const result: ITreeNode<T, TFilterData>[] = [];
@@ -442,7 +434,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return result;
}
private _updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
private _updateNodeAfterFilterChange(node: IIndexTreeNode<T, TFilterData>, parentVisibility: TreeVisibility, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
let visibility: TreeVisibility;
if (node !== this.root) {
@@ -496,7 +488,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return node.visible;
}
private _updateAncestorsRenderNodeCount(node: IMutableTreeNode<T, TFilterData> | undefined, diff: number): void {
private _updateAncestorsRenderNodeCount(node: IIndexTreeNode<T, TFilterData> | undefined, diff: number): void {
if (diff === 0) {
return;
}
@@ -508,7 +500,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
}
private _filterNode(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility): TreeVisibility {
private _filterNode(node: IIndexTreeNode<T, TFilterData>, parentVisibility: TreeVisibility): TreeVisibility {
const result = this.filter ? this.filter.filter(node.element, parentVisibility) : TreeVisibility.Visible;
if (typeof result === 'boolean') {
@@ -524,7 +516,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
// cheap
private getTreeNode(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root): IMutableTreeNode<T, TFilterData> {
private getTreeNode(location: number[], node: IIndexTreeNode<T, TFilterData> = this.root): IIndexTreeNode<T, TFilterData> {
if (!location || location.length === 0) {
return node;
}
@@ -539,7 +531,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
// expensive
private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
private getTreeNodeWithListIndex(location: number[]): { node: IIndexTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
if (location.length === 0) {
return { node: this.root, listIndex: -1, revealed: true, visible: false };
}
@@ -556,7 +548,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return { node, listIndex, revealed, visible: visible && node.visible };
}
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
private getParentNodeWithListIndex(location: number[], node: IIndexTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IIndexTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
const [index, ...rest] = location;
if (index < 0 || index > node.children.length) {
@@ -585,27 +577,24 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
// TODO@joao perf!
getNodeLocation(node: ITreeNode<T, TFilterData>): number[] {
const location: number[] = [];
let indexTreeNode = node as IIndexTreeNode<T, TFilterData>; // typing woes
while (node.parent) {
location.push(node.parent.children.indexOf(node));
node = node.parent;
while (indexTreeNode.parent) {
location.push(indexTreeNode.parent.children.indexOf(indexTreeNode));
indexTreeNode = indexTreeNode.parent;
}
return location.reverse();
}
getParentNodeLocation(location: number[]): number[] {
if (location.length <= 1) {
getParentNodeLocation(location: number[]): number[] | undefined {
if (location.length === 0) {
return undefined;
} else if (location.length === 1) {
return [];
} else {
return tail2(location)[0];
}
return tail2(location)[0];
}
getParentElement(location: number[]): T {
const parentLocation = this.getParentNodeLocation(location);
const node = this.getTreeNode(parentLocation);
return node.element;
}
getFirstElementChild(location: number[]): T | undefined {
+114 -6
View File
@@ -3,13 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Iterator, ISequence } from 'vs/base/common/iterator';
import { ISequence } from 'vs/base/common/iterator';
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Event } from 'vs/base/common/event';
import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
@@ -31,11 +32,8 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
super(user, container, delegate, renderers, options);
}
setChildren(
element: T | null,
children?: ISequence<ITreeElement<T>>
): Iterator<ITreeElement<T | null>> {
return this.model.setChildren(element, children);
setChildren(element: T | null, children?: ISequence<ITreeElement<T>>): void {
this.model.setChildren(element, children);
}
rerender(element?: T): void {
@@ -55,3 +53,113 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
return new ObjectTreeModel(user, view, options);
}
}
interface ICompressedTreeNodeProvider<T, TFilterData> {
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData>;
}
export interface ICompressibleObjectTreeOptions<T, TFilterData = void> extends IObjectTreeOptions<T, TFilterData> {
readonly elementMapper?: ElementMapper<T>;
}
export interface ICompressibleTreeRenderer<T, TFilterData = void, TTemplateData = void> extends ITreeRenderer<T, TFilterData, TTemplateData> {
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<T>, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void;
disposeCompressedElements?(node: ITreeNode<ICompressedTreeNode<T>, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void;
}
interface CompressibleTemplateData<T, TFilterData, TTemplateData> {
compressedTreeNode: ITreeNode<ICompressedTreeNode<T>, TFilterData> | undefined;
readonly data: TTemplateData;
}
class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
readonly templateId: string;
readonly onDidChangeTwistieState: Event<T> | undefined;
compressedTreeNodeProvider: ICompressedTreeNodeProvider<T, TFilterData>;
constructor(private renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>) {
this.templateId = renderer.templateId;
if (renderer.onDidChangeTwistieState) {
this.onDidChangeTwistieState = renderer.onDidChangeTwistieState;
}
}
renderTemplate(container: HTMLElement): CompressibleTemplateData<T, TFilterData, TTemplateData> {
const data = this.renderer.renderTemplate(container);
return { compressedTreeNode: undefined, data };
}
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
if (compressedTreeNode.element.elements.length === 1) {
templateData.compressedTreeNode = undefined;
this.renderer.renderElement(node, index, templateData.data, height);
} else {
templateData.compressedTreeNode = compressedTreeNode;
this.renderer.renderCompressedElements(compressedTreeNode, index, templateData.data, height);
}
}
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
if (templateData.compressedTreeNode) {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(templateData.compressedTreeNode, index, templateData.data, height);
}
} else {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(node, index, templateData.data, height);
}
}
}
disposeTemplate(templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>): void {
this.renderer.disposeTemplate(templateData.data);
}
renderTwistie?(element: T, twistieElement: HTMLElement): void {
if (this.renderer.renderTwistie) {
this.renderer.renderTwistie(element, twistieElement);
}
}
}
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {
protected model: CompressibleObjectTreeModel<T, TFilterData>;
constructor(
user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
options: IObjectTreeOptions<T, TFilterData> = {}
) {
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(r));
super(user, container, delegate, compressibleRenderers, options);
compressibleRenderers.forEach(r => r.compressedTreeNodeProvider = this);
}
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {
this.model.setChildren(element, children);
}
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
return new CompressibleObjectTreeModel(user, view, options);
}
isCompressionEnabled(): boolean {
return this.model.isCompressionEnabled();
}
setCompressionEnabled(enabled: boolean): void {
this.model.setCompressionEnabled(enabled);
}
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
return this.model.getCompressedTreeNode(element)!;
}
}
+10 -18
View File
@@ -13,7 +13,7 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
setChildren(element: T | null, children: ISequence<ITreeElement<T>> | undefined): Iterator<ITreeElement<T>>;
setChildren(element: T | null, children: ISequence<ITreeElement<T>> | undefined): void;
resort(element?: T | null, recursive?: boolean): void;
}
@@ -64,9 +64,9 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
children: ISequence<ITreeElement<T>> | undefined,
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
): Iterator<ITreeElement<T>> {
): void {
const location = this.getElementLocation(element);
return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
}
private _setChildren(
@@ -74,7 +74,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
children: ISequence<ITreeElement<T>> | undefined,
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
): Iterator<ITreeElement<T>> {
): void {
const insertedElements = new Set<T | null>();
const insertedElementIds = new Set<string>();
@@ -110,15 +110,13 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
}
};
const result = this.model.splice(
this.model.splice(
[...location, 0],
Number.MAX_VALUE,
children,
_onDidCreateNode,
_onDidDeleteNode
);
return result as Iterator<ITreeElement<T>>;
}
private preserveCollapseState(elements: ISequence<ITreeElement<T>> | undefined): ISequence<ITreeElement<T>> {
@@ -186,11 +184,6 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
}));
}
getParentElement(ref: T | null = null): T | null {
const location = this.getElementLocation(ref);
return this.model.getParentElement(location);
}
getFirstElementChild(ref: T | null = null): T | null | undefined {
const location = this.getElementLocation(ref);
return this.model.getFirstElementChild(location);
@@ -263,13 +256,12 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
}
const node = this.nodes.get(element);
const node = this.nodes.get(element)!;
const location = this.model.getNodeLocation(node);
const parentLocation = this.model.getParentNodeLocation(location);
const parent = this.model.getNode(parentLocation);
if (!node) {
throw new TreeError(this.user, `Tree element not found: ${element}`);
}
return node.parent!.element;
return parent.element;
}
private getElementLocation(element: T | null): number[] {
+19 -4
View File
@@ -81,7 +81,6 @@ export interface ITreeElement<T> {
export interface ITreeNode<T, TFilterData = void> {
readonly element: T;
readonly parent: ITreeNode<T, TFilterData> | undefined;
readonly children: ITreeNode<T, TFilterData>[];
readonly depth: number;
readonly visibleChildrenCount: number;
@@ -113,9 +112,8 @@ export interface ITreeModel<T, TFilterData, TRef> {
getListRenderCount(location: TRef): number;
getNode(location?: TRef): ITreeNode<T, any>;
getNodeLocation(node: ITreeNode<T, any>): TRef;
getParentNodeLocation(location: TRef): TRef;
getParentNodeLocation(location: TRef): TRef | undefined;
getParentElement(location: TRef): T;
getFirstElementChild(location: TRef): T | undefined;
getLastElementAncestor(location?: TRef): T | undefined;
@@ -160,7 +158,6 @@ export interface ITreeContextMenuEvent<T> {
export interface ITreeNavigator<T> {
current(): T | null;
previous(): T | null;
parent(): T | null;
first(): T | null;
last(): T | null;
next(): T | null;
@@ -202,3 +199,21 @@ export class TreeError extends Error {
super(`TreeError [${user}] ${message}`);
}
}
export class WeakMapper<K extends object, V> {
constructor(private fn: (k: K) => V) { }
private _map = new WeakMap<K, V>();
map(key: K): V {
let result = this._map.get(key);
if (!result) {
result = this.fn(key);
this._map.set(key, result);
}
return result;
}
}
+14 -3
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
export namespace Schemas {
@@ -60,18 +60,24 @@ class RemoteAuthoritiesImpl {
private readonly _ports: { [authority: string]: number; };
private readonly _connectionTokens: { [authority: string]: string; };
private _preferredWebSchema: 'http' | 'https';
private _delegate: ((uri: URI) => UriComponents) | null;
constructor() {
this._hosts = Object.create(null);
this._ports = Object.create(null);
this._connectionTokens = Object.create(null);
this._preferredWebSchema = 'http';
this._delegate = null;
}
public setPreferredWebSchema(schema: 'http' | 'https') {
this._preferredWebSchema = schema;
}
public setDelegate(delegate: (uri: URI) => UriComponents): void {
this._delegate = delegate;
}
public set(authority: string, host: string, port: number): void {
this._hosts[authority] = host;
this._ports[authority] = port;
@@ -81,7 +87,12 @@ class RemoteAuthoritiesImpl {
this._connectionTokens[authority] = connectionToken;
}
public rewrite(authority: string, path: string): URI {
public rewrite(uri: URI): URI {
if (this._delegate) {
const result = this._delegate(uri);
return URI.revive(result);
}
const authority = uri.authority;
const host = this._hosts[authority];
const port = this._ports[authority];
const connectionToken = this._connectionTokens[authority];
@@ -89,7 +100,7 @@ class RemoteAuthoritiesImpl {
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: `${host}:${port}`,
path: `/vscode-remote-resource`,
query: `path=${encodeURIComponent(path)}&tkn=${encodeURIComponent(connectionToken)}`
query: `path=${encodeURIComponent(uri.path)}&tkn=${encodeURIComponent(connectionToken)}`
});
}
}
+7 -3
View File
@@ -12,8 +12,12 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
} else if (matches[0].toLowerCase() === matches[0]) {
return pattern.toLowerCase();
} else if (strings.containsUppercaseCharacter(matches[0][0])) {
if (validateSpecificSpecialCharacter(matches, pattern, '-')) {
const containsHyphens = validateSpecificSpecialCharacter(matches, pattern, '-');
const containsUnderscores = validateSpecificSpecialCharacter(matches, pattern, '_');
if (containsHyphens && !containsUnderscores) {
return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '-');
} else if (!containsHyphens && containsUnderscores) {
return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '_');
} else {
return pattern[0].toUpperCase() + pattern.substr(1);
}
@@ -27,8 +31,8 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
}
function validateSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): boolean {
const doesConatinSpecialCharacter = matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1;
return doesConatinSpecialCharacter && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length;
const doesContainSpecialCharacter = matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1;
return doesContainSpecialCharacter && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length;
}
function buildReplaceStringForSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): string {
@@ -3,11 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Menu, MenuItem, BrowserWindow, Event, ipcMain } from 'electron';
import { Menu, MenuItem, BrowserWindow, ipcMain } from 'electron';
import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu';
export function registerContextMenuListener(): void {
ipcMain.on(CONTEXT_MENU_CHANNEL, (event: Event, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => {
ipcMain.on(CONTEXT_MENU_CHANNEL, (event: Electron.IpcMainEvent, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => {
const menu = createMenu(event, onClickChannel, items);
menu.popup({
@@ -27,7 +27,7 @@ export function registerContextMenuListener(): void {
});
}
function createMenu(event: Event, onClickChannel: string, items: ISerializableContextMenuItem[]): Menu {
function createMenu(event: Electron.IpcMainEvent, onClickChannel: string, items: ISerializableContextMenuItem[]): Menu {
const menu = new Menu();
items.forEach(item => {
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { Iterator } from 'vs/base/common/iterator';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
@@ -305,7 +305,7 @@ suite('CompressedObjectTree', function () {
test('ctor', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
assert(model);
assert.equal(list.length, 0);
assert.equal(model.size, 0);
@@ -313,7 +313,7 @@ suite('CompressedObjectTree', function () {
test('flat', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
model.setChildren(null, Iterator.fromArray([
{ element: 0 },
@@ -340,7 +340,7 @@ suite('CompressedObjectTree', function () {
test('nested', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
model.setChildren(null, Iterator.fromArray([
{
@@ -376,7 +376,7 @@ suite('CompressedObjectTree', function () {
test('compressed', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedTreeModel<number>('test', toSpliceable(list));
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
model.setChildren(null, Iterator.fromArray([
{
@@ -7,7 +7,7 @@ import * as assert from 'assert';
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
import { Iterator } from 'vs/base/common/iterator';
import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
import { IndexTreeModel, IIndexTreeNode } from 'vs/base/browser/ui/tree/indexTreeModel';
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
return {
@@ -637,7 +637,7 @@ suite('IndexTreeModel', function () {
suite('getNodeLocation', function () {
test('simple', function () {
const list: ITreeNode<number>[] = [];
const list: IIndexTreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
model.splice([0], 0, Iterator.fromArray([
@@ -661,7 +661,7 @@ suite('IndexTreeModel', function () {
});
test('with filter', function () {
const list: ITreeNode<number>[] = [];
const list: IIndexTreeNode<number>[] = [];
const filter = new class implements ITreeFilter<number> {
filter(element: number): TreeVisibility {
return element % 2 === 0 ? TreeVisibility.Visible : TreeVisibility.Hidden;
@@ -6,8 +6,9 @@
import * as assert from 'assert';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ObjectTree, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { Iterator } from 'vs/base/common/iterator';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
suite('ObjectTree', function () {
suite('TreeNavigator', function () {
@@ -81,8 +82,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.parent(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
@@ -112,7 +111,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), 0);
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
@@ -147,8 +145,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.parent(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
@@ -180,8 +176,6 @@ suite('ObjectTree', function () {
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.parent(), 0);
assert.equal(navigator.parent(), null);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
@@ -225,3 +219,160 @@ suite('ObjectTree', function () {
assert.deepStrictEqual(tree.getFocus(), [101]);
});
});
function toArray(list: NodeList): Node[] {
const result: Node[] = [];
list.forEach(node => result.push(node));
return result;
}
suite('CompressibleObjectTree', function () {
class Delegate implements IListVirtualDelegate<number> {
getHeight() { return 20; }
getTemplateId(): string { return 'default'; }
}
class Renderer implements ICompressibleTreeRenderer<number, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(node: ITreeNode<number, void>, _: number, templateData: HTMLElement): void {
templateData.textContent = `${node.element}`;
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<number>, void>, _: number, templateData: HTMLElement): void {
templateData.textContent = `${node.element.elements.join('/')}`;
}
disposeTemplate(): void { }
}
test('empty', function () {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
const rows = toArray(container.querySelectorAll('.monaco-tl-contents'));
assert.equal(rows.length, 0);
});
test('simple', function () {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
tree.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
const rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['0', '10', '11', '12', '1', '2']);
});
test('compressed', () => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
tree.setChildren(null, Iterator.fromArray([
{
element: 1, children: Iterator.fromArray([{
element: 11, children: Iterator.fromArray([{
element: 111, children: Iterator.fromArray([
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
])
}])
}])
}
]));
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
tree.setChildren(11, Iterator.fromArray([
{ element: 111 },
{ element: 112 },
{ element: 113 },
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113']);
tree.setChildren(113, Iterator.fromArray([
{ element: 1131 }
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131']);
tree.setChildren(1131, Iterator.fromArray([
{ element: 1132 }
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131/1132']);
tree.setChildren(1131, Iterator.fromArray([
{ element: 1132 },
{ element: 1133 },
]));
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131', '1132', '1133']);
});
test('enableCompression', () => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
assert.equal(tree.isCompressionEnabled(), true);
tree.setChildren(null, Iterator.fromArray([
{
element: 1, children: Iterator.fromArray([{
element: 11, children: Iterator.fromArray([{
element: 111, children: Iterator.fromArray([
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
])
}])
}])
}
]));
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
tree.setCompressionEnabled(false);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1', '11', '111', '1111', '1112', '1113']);
tree.setCompressionEnabled(true);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
});
});
@@ -14,9 +14,9 @@
default-src 'self';
img-src 'self' https: data: blob:;
media-src 'none';
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'sha256-4DqvCTjCHj2KW4QxC/Yt6uBwMRyYiEg7kOoykSEkonQ=' 'sha256-meDZW3XhN5JmdjFUrWGhTouRKBiWYtXHltaKnqn/WMo=';
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'sha256-AMRGFXNZ7mBnD/6F4lTV00XAjE5CBSM7ZeIv3DIp5YM=' 'sha256-meDZW3XhN5JmdjFUrWGhTouRKBiWYtXHltaKnqn/WMo=';
child-src 'self';
frame-src 'self' {{WEBVIEW_ENDPOINT}} https://*.vscode-webview-test.com;
frame-src 'self' https://*.vscode-webview-test.com;
worker-src 'self';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws: wss: https:;
@@ -44,13 +44,13 @@
self.require = {
baseUrl: `${window.location.origin}/static/out`,
paths: {
'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`,
'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`,
'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
'@microsoft/applicationinsights-web': `${window.location.origin}/static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`,
'onigasm-umd': `${window.location.origin}/static/remote/web/node_modules/onigasm-umd/release/main`,
'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
'@microsoft/applicationinsights-web': `${window.location.origin}/static/remote/web/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
}
};
</script>
+1 -1
View File
@@ -16,7 +16,7 @@
media-src 'none';
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'sha256-4DqvCTjCHj2KW4QxC/Yt6uBwMRyYiEg7kOoykSEkonQ=';
child-src 'self';
frame-src 'self' {{WEBVIEW_ENDPOINT}} https://*.vscode-webview-test.com;
frame-src 'self' https://*.vscode-webview-test.com;
worker-src 'self';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws: wss: https:;
+3 -3
View File
@@ -117,8 +117,8 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi
FRAGMENT: 'vscode-fragment'
};
private readonly _onCallback: Emitter<URI> = this._register(new Emitter<URI>());
readonly onCallback: Event<URI> = this._onCallback.event;
private readonly _onCallback: Emitter<UriComponents> = this._register(new Emitter<UriComponents>());
readonly onCallback: Event<UriComponents> = this._onCallback.event;
create(options?: Partial<UriComponents>): URI {
const queryValues: Map<string, string> = new Map();
@@ -168,7 +168,7 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi
const content = await streamToBuffer(result.stream);
if (content.byteLength > 0) {
try {
this._onCallback.fire(URI.revive(JSON.parse(content.toString())));
this._onCallback.fire(JSON.parse(content.toString()));
} catch (error) {
console.error(error);
}
+5 -5
View File
@@ -12,7 +12,7 @@ import { WindowsService } from 'vs/platform/windows/electron-main/windowsService
import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { getShellEnvironment } from 'vs/code/node/shellEnv';
import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/node/updateIpc';
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
@@ -258,7 +258,7 @@ export class CodeApplication extends Disposable {
this.lifecycleService.kill(code);
});
ipc.on('vscode:fetchShellEnv', async (event: Event) => {
ipc.on('vscode:fetchShellEnv', async (event: Electron.IpcMainEvent) => {
const webContents = event.sender;
try {
@@ -275,10 +275,10 @@ export class CodeApplication extends Disposable {
}
});
ipc.on('vscode:toggleDevTools', (event: Event) => event.sender.toggleDevTools());
ipc.on('vscode:openDevTools', (event: Event) => event.sender.openDevTools());
ipc.on('vscode:toggleDevTools', (event: Electron.IpcMainEvent) => event.sender.toggleDevTools());
ipc.on('vscode:openDevTools', (event: Electron.IpcMainEvent) => event.sender.openDevTools());
ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload());
ipc.on('vscode:reloadWindow', (event: Electron.IpcMainEvent) => event.sender.reload());
// Some listeners after window opened
(async () => {
+1 -1
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/platform/update/node/update.config.contribution';
import 'vs/platform/update/common/update.config.contribution';
import { app, dialog } from 'electron';
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
+1 -2
View File
@@ -36,9 +36,8 @@ export class SharedProcess implements ISharedProcess {
backgroundColor: this.themeMainService.getBackgroundColor(),
webPreferences: {
images: false,
webaudio: false,
webgl: false,
nodeIntegration: true,
webgl: false,
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
}
});
+33 -46
View File
@@ -1955,24 +1955,21 @@ class Dialogs {
}
showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
return this.getDialogQueue(window).queue(() => {
return new Promise(resolve => {
const callback = (response: number, checkboxChecked: boolean) => {
resolve({ button: response, checkboxChecked });
};
return this.getDialogQueue(window).queue(async () => {
let result: Electron.MessageBoxReturnValue;
if (window) {
result = await dialog.showMessageBox(window.win, options);
} else {
result = await dialog.showMessageBox(options);
}
if (window) {
dialog.showMessageBox(window.win, options, callback);
} else {
dialog.showMessageBox(options, callback);
}
});
return { button: result.response, checkboxChecked: result.checkboxChecked };
});
}
showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Promise<string> {
function normalizePath(path: string): string {
function normalizePath(path: string | undefined): string | undefined {
if (path && isMacintosh) {
path = normalizeNFC(path); // normalize paths returned from the OS
}
@@ -1980,24 +1977,21 @@ class Dialogs {
return path;
}
return this.getDialogQueue(window).queue(() => {
return new Promise(resolve => {
const callback = (path: string) => {
resolve(normalizePath(path));
};
return this.getDialogQueue(window).queue(async () => {
let result: Electron.SaveDialogReturnValue;
if (window) {
result = await dialog.showSaveDialog(window.win, options);
} else {
result = await dialog.showSaveDialog(options);
}
if (window) {
dialog.showSaveDialog(window.win, options, callback);
} else {
dialog.showSaveDialog(options, callback);
}
});
return normalizePath(result.filePath);
});
}
showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Promise<string[]> {
function normalizePaths(paths: string[]): string[] {
function normalizePaths(paths: string[] | undefined): string[] | undefined {
if (paths && paths.length > 0 && isMacintosh) {
paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS
}
@@ -2005,32 +1999,25 @@ class Dialogs {
return paths;
}
return this.getDialogQueue(window).queue(() => {
return new Promise(resolve => {
return this.getDialogQueue(window).queue(async () => {
// Ensure the path exists (if provided)
let validatePathPromise: Promise<void> = Promise.resolve();
if (options.defaultPath) {
validatePathPromise = exists(options.defaultPath).then(exists => {
if (!exists) {
options.defaultPath = undefined;
}
});
// Ensure the path exists (if provided)
if (options.defaultPath) {
const pathExists = await exists(options.defaultPath);
if (!pathExists) {
options.defaultPath = undefined;
}
}
// Show dialog and wrap as promise
validatePathPromise.then(() => {
const callback = (paths: string[]) => {
resolve(normalizePaths(paths));
};
// Show dialog
let result: Electron.OpenDialogReturnValue;
if (window) {
result = await dialog.showOpenDialog(window.win, options);
} else {
result = await dialog.showOpenDialog(options);
}
if (window) {
dialog.showOpenDialog(window.win, options, callback);
} else {
dialog.showOpenDialog(options, callback);
}
});
});
return normalizePaths(result.filePaths);
});
}
}
@@ -36,6 +36,7 @@ export class EditorScrollbar extends ViewPart {
const fastScrollSensitivity = options.get(EditorOption.fastScrollSensitivity);
const scrollbarOptions: ScrollableElementCreationOptions = {
alwaysConsumeMouseWheel: true,
listenOnDomNode: viewDomNode.domNode,
className: 'editor-scrollable' + ' ' + getThemeTypeSelector(context.theme.type),
useShadows: false,
@@ -23,10 +23,12 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v
import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLineData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, minimapSelection } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel';
import { Selection } from 'vs/editor/common/core/selection';
import { Color } from 'vs/base/common/color';
function getMinimapLineHeight(renderMinimap: RenderMinimap): number {
if (renderMinimap === RenderMinimap.Large) {
@@ -452,7 +454,8 @@ export class Minimap extends ViewPart {
private _options: MinimapOptions;
private _lastRenderData: RenderData | null;
private _lastDecorations: ViewModelDecoration[] | undefined;
private _selections: Selection[] = [];
private _selectionColor: Color | undefined;
private _renderDecorations: boolean = false;
private _buffers: MinimapBuffers | null;
@@ -462,6 +465,7 @@ export class Minimap extends ViewPart {
this._options = new MinimapOptions(this._context.configuration);
this._lastRenderData = null;
this._buffers = null;
this._selectionColor = this._context.theme.getColor(minimapSelection);
this._domNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this._domNode, PartFingerprint.Minimap);
@@ -631,6 +635,11 @@ export class Minimap extends ViewPart {
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
return this._onOptionsMaybeChanged();
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
this._selections = e.selections;
this._renderDecorations = true;
return true;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
this._lastRenderData = null;
return true;
@@ -680,9 +689,9 @@ export class Minimap extends ViewPart {
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
this._context.model.invalidateMinimapColorCache();
// Only bother calling render if decorations are currently shown
this._renderDecorations = !!this._lastDecorations;
return !!this._lastDecorations;
this._selectionColor = this._context.theme.getColor(minimapSelection);
this._renderDecorations = true;
return true;
}
// --- end event handlers
@@ -744,8 +753,16 @@ export class Minimap extends ViewPart {
canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight);
// Loop over decorations, ignoring those that don't have the minimap property set and rendering rectangles for each line the decoration spans
const lineOffsetMap = new Map<number, number[]>();
for (let i = 0; i < this._selections.length; i++) {
const selection = this._selections[i];
for (let line = selection.startLineNumber; line <= selection.endLineNumber; line++) {
this.renderDecorationOnLine(canvasContext, lineOffsetMap, selection, this._selectionColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth);
}
}
// Loop over decorations, ignoring those that don't have the minimap property set and rendering rectangles for each line the decoration spans
for (let i = 0; i < decorations.length; i++) {
const decoration = decorations[i];
@@ -754,17 +771,17 @@ export class Minimap extends ViewPart {
}
for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) {
this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration, layout, line, lineHeight, lineHeight, tabSize, characterWidth);
const decorationColor = (<ModelDecorationMinimapOptions>decoration.options.minimap).getColor(this._context.theme);
this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth);
}
}
this._lastDecorations = decorations;
}
}
private renderDecorationOnLine(canvasContext: CanvasRenderingContext2D,
lineOffsetMap: Map<number, number[]>,
decoration: ViewModelDecoration,
decorationRange: Range,
decorationColor: Color | undefined,
layout: MinimapLayout,
lineNumber: number,
height: number,
@@ -793,7 +810,7 @@ export class Minimap extends ViewPart {
lineOffsetMap.set(lineNumber, lineIndexToXOffset);
}
const { startColumn, endColumn, startLineNumber, endLineNumber } = decoration.range;
const { startColumn, endColumn, startLineNumber, endLineNumber } = decorationRange;
const x = startLineNumber === lineNumber ? lineIndexToXOffset[startColumn - 1] : 0;
const endColumnForLine = endLineNumber > lineNumber ? lineIndexToXOffset.length - 1 : endColumn - 1;
@@ -802,24 +819,21 @@ export class Minimap extends ViewPart {
// If the decoration starts at the last character of the column and spans over it, ensure it has a width
const width = lineIndexToXOffset[endColumnForLine] - x || 2;
this.renderDecoration(canvasContext, <ModelDecorationMinimapOptions>decoration.options.minimap, x, y, width, height);
this.renderDecoration(canvasContext, decorationColor, x, y, width, height);
}
if (isFirstDecorationForLine) {
this.renderLineHighlight(canvasContext, <ModelDecorationMinimapOptions>decoration.options.minimap, y, height);
this.renderLineHighlight(canvasContext, decorationColor, y, height);
}
}
private renderLineHighlight(canvasContext: CanvasRenderingContext2D, minimapOptions: ModelDecorationMinimapOptions, y: number, height: number): void {
const decorationColor = minimapOptions.getColor(this._context.theme);
private renderLineHighlight(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, y: number, height: number): void {
canvasContext.fillStyle = decorationColor && decorationColor.transparent(0.5).toString() || '';
canvasContext.fillRect(0, y, canvasContext.canvas.width, height);
}
private renderDecoration(canvasContext: CanvasRenderingContext2D, minimapOptions: ModelDecorationMinimapOptions, x: number, y: number, width: number, height: number) {
const decorationColor = minimapOptions.getColor(this._context.theme);
private renderDecoration(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, x: number, y: number, width: number, height: number) {
canvasContext.fillStyle = decorationColor && decorationColor.toString() || '';
canvasContext.fillRect(x, y, width, height);
}
@@ -1172,7 +1172,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
}
public hasWidgetFocus(): boolean {
return this._focusTracker && this._focusTracker.hasFocus();
return (this._editorWidgetFocus.getValue() === BooleanEventValue.True);
}
public addContentWidget(widget: editorBrowser.IContentWidget): void {
@@ -1548,6 +1548,10 @@ export class BooleanEventEmitter extends Disposable {
this._value = BooleanEventValue.NotSet;
}
public getValue(): BooleanEventValue {
return this._value;
}
public setValue(_value: boolean) {
const value = (_value ? BooleanEventValue.True : BooleanEventValue.False);
if (this._value === value) {
@@ -179,7 +179,7 @@ export class ShiftCommand implements ICommand {
}
this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), desiredIndent);
if (lineNumber === startLine) {
if (lineNumber === startLine && !this._selection.isEmpty()) {
// Force the startColumn to stay put because we're inserting after it
this._selectionStartColumnStaysPut = (this._selection.startColumn <= indentationEndIndex + 1);
}
@@ -226,7 +226,7 @@ export class ShiftCommand implements ICommand {
this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), '');
} else {
this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, 1), oneIndent);
if (lineNumber === startLine) {
if (lineNumber === startLine && !this._selection.isEmpty()) {
// Force the startColumn to stay put because we're inserting after it
this._selectionStartColumnStaysPut = (this._selection.startColumn === 1);
}
+9
View File
@@ -1402,6 +1402,15 @@ export interface IWebviewPanelOptions {
readonly retainContextWhenHidden?: boolean;
}
/**
* @internal
*/
export const enum WebviewEditorState {
Readonly = 1,
Unchanged = 2,
Dirty = 3,
}
export interface CodeLens {
range: IRange;
id?: string;
+6 -26
View File
@@ -778,19 +778,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _onFindInputKeyDown(e: IKeyboardEvent): void {
if (e.equals(ctrlKeyMod | KeyCode.Enter)) {
const inputElement = this._findInput.inputBox.inputElement;
const start = inputElement.selectionStart;
const end = inputElement.selectionEnd;
const content = inputElement.value;
if (start !== null && end !== null) {
const value = content.substr(0, start) + '\n' + content.substr(end);
this._findInput.inputBox.value = value;
inputElement.setSelectionRange(start + 1, start + 1);
this._findInput.inputBox.layout();
e.preventDefault();
return;
}
this._findInput.inputBox.insertAtCursor('\n');
e.preventDefault();
return;
}
if (e.equals(KeyCode.Tab)) {
@@ -832,19 +822,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
const inputElement = this._replaceInput.inputBox.inputElement;
const start = inputElement.selectionStart;
const end = inputElement.selectionEnd;
const content = inputElement.value;
if (start !== null && end !== null) {
const value = content.substr(0, start) + '\n' + content.substr(end);
this._replaceInput.inputBox.value = value;
inputElement.setSelectionRange(start + 1, start + 1);
this._replaceInput.inputBox.layout();
e.preventDefault();
return;
}
this._replaceInput.inputBox.insertAtCursor('\n');
e.preventDefault();
return;
}
if (e.equals(KeyCode.Tab)) {
@@ -183,6 +183,15 @@ suite('Replace Pattern test', () => {
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar-newabc'), 'Newfoo-Newbar-Newabc');
actual = ['Foo-Bar-abc'];
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-newbar');
actual = ['Foo_Bar'];
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_Newbar');
actual = ['Foo_Bar_Abc'];
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar_newabc'), 'Newfoo_Newbar_Newabc');
actual = ['Foo_Bar_abc'];
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_newbar');
actual = ['Foo_Bar-abc'];
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar-abc'), 'Newfoo_newbar-abc');
});
test('preserve case', () => {
@@ -217,5 +226,21 @@ suite('Replace Pattern test', () => {
replacePattern = parseReplaceString('newfoo-newbar');
actual = replacePattern.buildReplaceString(['Foo-Bar-abc'], true);
assert.equal(actual, 'Newfoo-newbar');
replacePattern = parseReplaceString('newfoo_newbar');
actual = replacePattern.buildReplaceString(['Foo_Bar'], true);
assert.equal(actual, 'Newfoo_Newbar');
replacePattern = parseReplaceString('newfoo_newbar_newabc');
actual = replacePattern.buildReplaceString(['Foo_Bar_Abc'], true);
assert.equal(actual, 'Newfoo_Newbar_Newabc');
replacePattern = parseReplaceString('newfoo_newbar');
actual = replacePattern.buildReplaceString(['Foo_Bar_abc'], true);
assert.equal(actual, 'Newfoo_newbar');
replacePattern = parseReplaceString('newfoo_newbar-abc');
actual = replacePattern.buildReplaceString(['Foo_Bar-abc'], true);
assert.equal(actual, 'Newfoo_newbar-abc');
});
});
@@ -928,6 +928,28 @@ suite('Editor Contrib - Line Operations', () => {
model.dispose();
});
test('issue #80736: Indenting while the cursor is at the start of a line of text causes the added spaces or tab to be selected', () => {
const model = createTextModel(
[
'Some text'
].join('\n'),
{
insertSpaces: false,
}
);
withTestCodeEditor(null, { model: model }, (editor) => {
const indentLinesAction = new IndentLinesAction();
editor.setPosition(new Position(1, 1));
indentLinesAction.run(null!, editor);
assert.equal(model.getLineContent(1), '\tSome text');
assert.deepEqual(editor.getSelection(), new Selection(1, 2, 1, 2));
});
model.dispose();
});
test('issue #62112: Delete line does not work properly when multiple cursors are on line', () => {
const TEXT = [
'a',
@@ -68,7 +68,8 @@ suite('Multicursor selection', () => {
getNumber: (key: string) => undefined!,
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
remove: (key) => undefined,
logStorage: () => undefined
logStorage: () => undefined,
migrate: (toWorkspace) => Promise.resolve(undefined)
} as IStorageService);
test('issue #8817: Cursor position changes when you cancel multicursor', () => {
@@ -332,7 +332,12 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
private updateMaxHeight(): void {
const height = Math.max(this.editor.getLayoutInfo().height / 4, 250);
this.element.style.maxHeight = `${height}px`;
const maxHeight = `${height}px`;
this.element.style.maxHeight = maxHeight;
const wrapper = this.element.getElementsByClassName('wrapper') as HTMLCollectionOf<HTMLElement>;
if (wrapper.length) {
wrapper[0].style.maxHeight = maxHeight;
}
}
}

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