Merge branch 'master' into joao/splitview

This commit is contained in:
Joao Moreno 2019-07-11 18:21:13 +02:00
commit 28aec6305b
244 changed files with 2892 additions and 4288 deletions

View file

@ -63,7 +63,7 @@
assignLabel: false
},
file-explorer: {
assignees: [ isidorn ],
assignees: [ ],
assignLabel: false
},
file-glob: [],

View file

@ -4,10 +4,26 @@ set -e
cd $BUILD_STAGINGDIRECTORY
git clone https://github.com/microsoft/vscode-telemetry-extractor.git
cd vscode-telemetry-extractor
git checkout f538e3157c84d1bd0b239dfc5ebccac226006d58
git checkout 4e64f3de30f8fccb58ebdc0d85c4861a135d46cf
npm i
npm run setup-extension-repos
node ./out/cli-extract.js --sourceDir $BUILD_SOURCESDIRECTORY --excludedDirPattern extensions --outputDir . --applyEndpoints --includeIsMeasurement --patchWebsiteEvents
npm run compile
cd src
mkdir telemetry-sources
cd telemetry-sources
git clone --depth 1 https://github.com/Microsoft/vscode-extension-telemetry.git
git clone --depth 1 https://github.com/Microsoft/vscode-chrome-debug-core.git
git clone --depth 1 https://github.com/Microsoft/vscode-chrome-debug.git
git clone --depth 1 https://github.com/Microsoft/vscode-node-debug2.git
git clone --depth 1 https://github.com/Microsoft/vscode-node-debug.git
git clone --depth 1 https://github.com/Microsoft/vscode-docker.git
git clone --depth 1 https://github.com/Microsoft/vscode-go.git
git clone --depth 1 https://github.com/Microsoft/vscode-azure-account.git
git clone --depth 1 https://github.com/Microsoft/vscode-html-languageservice.git
git clone --depth 1 https://github.com/Microsoft/vscode-json-languageservice.git
git clone --depth 1 https://github.com/Microsoft/vscode-mono-debug.git
git clone --depth 1 https://github.com/Microsoft/TypeScript.git
cd ../../
node ./out/cli-extract.js --sourceDir $BUILD_SOURCESDIRECTORY --excludedDirPattern extensions --outputDir . --applyEndpoints --includeIsMeasurement
node ./out/cli-extract-extensions.js --sourceDir ./src/telemetry-sources --outputDir . --applyEndpoints --includeIsMeasurement
mkdir -p $BUILD_SOURCESDIRECTORY/.build/telemetry
mv declarations-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-core.json

View file

@ -130,8 +130,8 @@ jobs:
- template: sync-mooncake.yml
schedules:
- cron: "0 6 * * Mon-Fri"
displayName: Mon-Fri at 6:00
- cron: "0 5 * * Mon-Fri"
displayName: Mon-Fri at 7:00
branches:
include:
- master

View file

@ -102,7 +102,6 @@ steps:
- script: |
set -e
yarn gulp compile-build
yarn gulp compile-extensions-build-legacy
yarn gulp compile-extensions-build
yarn gulp minify-vscode
yarn gulp minify-vscode-reh

View file

@ -77,8 +77,8 @@ const vscodeResources = [
'out-build/vs/**/markdown.css',
'out-build/vs/workbench/contrib/tasks/**/*.json',
'out-build/vs/workbench/contrib/welcome/walkThrough/**/*.md',
'out-build/vs/workbench/services/files/**/*.exe',
'out-build/vs/workbench/services/files/**/*.md',
'out-build/vs/platform/files/**/*.exe',
'out-build/vs/platform/files/**/*.md',
'out-build/vs/code/electron-browser/workbench/**',
'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js',
'out-build/vs/code/electron-browser/issue/issueReporter.js',
@ -475,6 +475,8 @@ const apiToken = process.env.TRANSIFEX_API_TOKEN;
gulp.task(task.define(
'vscode-translations-push',
task.series(
compileBuildTask,
compileExtensionsBuildTask,
optimizeVSCodeTask,
function () {
const pathToMetadata = './out-vscode/nls.metadata.json';
@ -494,6 +496,8 @@ gulp.task(task.define(
gulp.task(task.define(
'vscode-translations-export',
task.series(
compileBuildTask,
compileExtensionsBuildTask,
optimizeVSCodeTask,
function () {
const pathToMetadata = './out-vscode/nls.metadata.json';

View file

@ -27,14 +27,14 @@ suite('XLF Parser Tests', () => {
});
test('JSON file source path to Transifex resource match', () => {
const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench';
const platform = { name: 'vs/platform', project: editorProject }, editorContrib = { name: 'vs/editor/contrib', project: editorProject }, editor = { name: 'vs/editor', project: editorProject }, base = { name: 'vs/base', project: editorProject }, code = { name: 'vs/code', project: workbenchProject }, workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/files', project: workbenchProject }, workbench = { name: 'vs/workbench', project: workbenchProject };
const platform = { name: 'vs/platform', project: editorProject }, editorContrib = { name: 'vs/editor/contrib', project: editorProject }, editor = { name: 'vs/editor', project: editorProject }, base = { name: 'vs/base', project: editorProject }, code = { name: 'vs/code', project: workbenchProject }, workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/textfile', project: workbenchProject }, workbench = { name: 'vs/workbench', project: workbenchProject };
assert.deepEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform);
assert.deepEqual(i18n.getResource('vs/editor/contrib/clipboard/browser/clipboard'), editorContrib);
assert.deepEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor);
assert.deepEqual(i18n.getResource('vs/base/common/errorMessage'), base);
assert.deepEqual(i18n.getResource('vs/code/electron-main/window'), code);
assert.deepEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts);
assert.deepEqual(i18n.getResource('vs/workbench/services/files/node/fileService'), workbenchServices);
assert.deepEqual(i18n.getResource('vs/workbench/services/textfile/node/testFileService'), workbenchServices);
assert.deepEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench);
});
});

View file

@ -39,7 +39,7 @@ suite('XLF Parser Tests', () => {
base = { name: 'vs/base', project: editorProject },
code = { name: 'vs/code', project: workbenchProject },
workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject },
workbenchServices = { name: 'vs/workbench/services/files', project: workbenchProject },
workbenchServices = { name: 'vs/workbench/services/textfile', project: workbenchProject },
workbench = { name: 'vs/workbench', project: workbenchProject};
assert.deepEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform);
@ -48,7 +48,7 @@ suite('XLF Parser Tests', () => {
assert.deepEqual(i18n.getResource('vs/base/common/errorMessage'), base);
assert.deepEqual(i18n.getResource('vs/code/electron-main/window'), code);
assert.deepEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts);
assert.deepEqual(i18n.getResource('vs/workbench/services/files/node/fileService'), workbenchServices);
assert.deepEqual(i18n.getResource('vs/workbench/services/textfile/node/testFileService'), workbenchServices);
assert.deepEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench);
});
});

View file

@ -716,7 +716,7 @@
{
"begin": "(^|\\G)(\\s*)(.*)",
"while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
"contentName": "meta.embedded.block.cpp",
"contentName": "meta.embedded.block.cpp source.cpp",
"patterns": [
{
"include": "source.cpp"

View file

@ -1,4 +1,5 @@
test/**
src/**
tsconfig.json
extension.webpack.config.js
cgmanifest.json

View file

@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
entry: {
pythonMain: './src/pythonMain.ts'
}
});

View file

@ -9,11 +9,17 @@
'use strict';
const path = require('path');
const fs = require('fs');
const merge = require('merge-options');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { NLSBundlePlugin } = require('vscode-nls-dev/lib/webpack-bundler');
module.exports = function withDefaults(/**@type WebpackConfig*/extConfig) {
// Need to find the top-most `package.json` file
const folderName = path.relative(__dirname, extConfig.context).split(/[\\\/]/)[0];
const pkgPath = path.join(__dirname, folderName, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const id = `${pkg.publisher}.${pkg.name}`;
/** @type WebpackConfig */
let defaultConfig = {
@ -62,9 +68,11 @@ module.exports = function withDefaults(/**@type WebpackConfig*/extConfig) {
// yes, really source maps
devtool: 'source-map',
plugins: [
// @ts-ignore
new CopyWebpackPlugin([
{ from: './out/**/*', to: '.', ignore: ['*.js', '*.js.map'], flatten: true }
])
{ from: 'src', to: '.', ignore: ['**/test/**', '*.ts'] }
]),
new NLSBundlePlugin(id)
],
};

View file

@ -72,6 +72,12 @@ class TscTaskProvider implements vscode.TaskProvider {
}
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
const definition = <TypeScriptTaskDefinition>_task.definition;
const badTsconfig = '\\tsconfig.json';
if ((definition.tsconfig.length > badTsconfig.length) && (definition.tsconfig.substring(definition.tsconfig.length - badTsconfig.length, definition.tsconfig.length) === badTsconfig)) {
// Warn that the task has the wrong slash type
vscode.window.showWarningMessage(localize('badTsConfig', "Typescript Task in tasks.json contains \"\\\\\". Typescript tasks must use \"/\""));
}
return undefined;
}

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.37.0",
"distro": "82ca40ea3cbbe55b4ede43dd3c8ce6183115eac2",
"distro": "40f61fd7e131c62473197b6505a3168a6bce1bc6",
"author": {
"name": "Microsoft Corporation"
},
@ -137,7 +137,7 @@
"vinyl-fs": "^3.0.0",
"vsce": "1.48.0",
"vscode-debugprotocol": "1.35.0",
"vscode-nls-dev": "3.2.5",
"vscode-nls-dev": "^3.3.1",
"webpack": "^4.16.5",
"webpack-cli": "^3.1.0",
"webpack-stream": "^5.1.1"

View file

@ -3,7 +3,6 @@
"module": "amd",
"moduleResolution": "node",
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"experimentalDecorators": true,
"noImplicitReturns": true,

View file

@ -9,7 +9,8 @@
"lib": [
"dom",
"es5",
"es2015.iterable"
"es2015.iterable",
"webworker"
]
},
"include": [
@ -19,4 +20,4 @@
"exclude": [
"./typings/require-monaco.d.ts"
]
}
}

View file

@ -1199,7 +1199,7 @@ export function asDomUri(uri: URI): URI {
if (Schemas.vscodeRemote === uri.scheme) {
// rewrite vscode-remote-uris to uris of the window location
// so that they can be intercepted by the service worker
return _location.with({ path: '/vscode-resources/fetch', query: uri.toString() });
return _location.with({ path: '/vscode-resources/fetch', query: JSON.stringify({ u: uri.toJSON(), i: 1 }) });
}
return uri;
}

View file

@ -6,7 +6,7 @@
import * as DOM from 'vs/base/browser/dom';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { escape } from 'vs/base/common/strings';
import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent';
import { removeMarkdownEscapes, IMarkdownString, parseHrefAndDimensions } from 'vs/base/common/htmlContent';
import * as marked from 'vs/base/common/marked/marked';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { DisposableStore } from 'vs/base/common/lifecycle';
@ -100,29 +100,11 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
const renderer = new marked.Renderer();
renderer.image = (href: string, title: string, text: string) => {
href = _href(href, true);
let dimensions: string[] = [];
if (href) {
const splitted = href.split('|').map(s => s.trim());
href = splitted[0];
const parameters = splitted[1];
if (parameters) {
const heightFromParams = /height=(\d+)/.exec(parameters);
const widthFromParams = /width=(\d+)/.exec(parameters);
const height = heightFromParams ? heightFromParams[1] : '';
const width = widthFromParams ? widthFromParams[1] : '';
const widthIsFinite = isFinite(parseInt(width));
const heightIsFinite = isFinite(parseInt(height));
if (widthIsFinite) {
dimensions.push(`width="${width}"`);
}
if (heightIsFinite) {
dimensions.push(`height="${height}"`);
}
}
}
let attributes: string[] = [];
if (href) {
({ href, dimensions } = parseHrefAndDimensions(href));
href = _href(href, true);
attributes.push(`src="${href}"`);
}
if (text) {

View file

@ -57,7 +57,7 @@ export interface IBreadcrumbsItemEvent {
export class BreadcrumbsWidget {
private readonly _disposables = new Array<IDisposable>();
private readonly _disposables = new DisposableStore();
private readonly _domNode: HTMLDivElement;
private readonly _styleElement: HTMLStyleElement;
private readonly _scrollable: DomScrollableElement;
@ -94,26 +94,25 @@ export class BreadcrumbsWidget {
useShadows: false,
scrollYToX: true
});
this._disposables.push(this._scrollable);
this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e)));
this._disposables.add(this._scrollable);
this._disposables.add(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e)));
container.appendChild(this._scrollable.getDomNode());
this._styleElement = dom.createStyleSheet(this._domNode);
let focusTracker = dom.trackFocus(this._domNode);
this._disposables.push(focusTracker);
this._disposables.push(focusTracker.onDidBlur(_ => this._onDidChangeFocus.fire(false)));
this._disposables.push(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true)));
const focusTracker = dom.trackFocus(this._domNode);
this._disposables.add(focusTracker);
this._disposables.add(focusTracker.onDidBlur(_ => this._onDidChangeFocus.fire(false)));
this._disposables.add(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true)));
}
dispose(): void {
dispose(this._disposables);
this._disposables.dispose();
dispose(this._pendingLayout);
this._onDidSelectItem.dispose();
this._onDidFocusItem.dispose();
this._onDidChangeFocus.dispose();
this._domNode.remove();
this._disposables.length = 0;
this._nodes.length = 0;
this._freeNodes.length = 0;
}

View file

@ -7,7 +7,7 @@ import 'vs/css!./gridview';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { Disposable } from 'vs/base/common/lifecycle';
import { tail2 as tail, equals } from 'vs/base/common/arrays';
import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles } from './gridview';
import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize } from './gridview';
import { Event, Emitter } from 'vs/base/common/event';
import { $ } from 'vs/base/browser/dom';
import { LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
@ -117,10 +117,6 @@ function getDirectionOrientation(direction: Direction): Orientation {
return direction === Direction.Up || direction === Direction.Down ? Orientation.VERTICAL : Orientation.HORIZONTAL;
}
function getSize(dimensions: { width: number; height: number; }, orientation: Orientation) {
return orientation === Orientation.HORIZONTAL ? dimensions.width : dimensions.height;
}
export function getRelativeLocation(rootOrientation: Orientation, location: number[], direction: Direction): number[] {
const orientation = getLocationOrientation(rootOrientation, location);
const directionOrientation = getDirectionOrientation(direction);
@ -209,8 +205,6 @@ export class Grid<T extends IView> extends Disposable {
get element(): HTMLElement { return this.gridview.element; }
sashResetSizing: Sizing = Sizing.Distribute;
constructor(view: T, options: IGridOptions = {}) {
super();
this.gridview = new GridView(options);
@ -298,15 +292,14 @@ export class Grid<T extends IView> extends Disposable {
return this.gridview.swapViews(fromLocation, toLocation);
}
resizeView(view: T, size: number): void {
resizeView(view: T, size: IViewSize): void {
const location = this.getViewLocation(view);
return this.gridview.resizeView(location, size);
}
getViewSize(view: T): number {
getViewSize(view: T): IViewSize {
const location = this.getViewLocation(view);
const viewSize = this.gridview.getViewSize(location);
return getLocationOrientation(this.orientation, location) === Orientation.HORIZONTAL ? viewSize.width : viewSize.height;
return this.gridview.getViewSize(location);
}
// TODO@joao cleanup
@ -361,18 +354,8 @@ export class Grid<T extends IView> extends Disposable {
}
private doResetViewSize(location: number[]): void {
if (this.sashResetSizing === Sizing.Split) {
const orientation = getLocationOrientation(this.orientation, location);
const firstViewSize = getSize(this.gridview.getViewSize(location), orientation);
const [parentLocation, index] = tail(location);
const secondViewSize = getSize(this.gridview.getViewSize([...parentLocation, index + 1]), orientation);
const totalSize = firstViewSize + secondViewSize;
this.gridview.resizeView(location, Math.floor(totalSize / 2));
} else {
const [parentLocation,] = tail(location);
this.gridview.distributeViewSizes(parentLocation);
}
const [parentLocation,] = tail(location);
this.gridview.distributeViewSizes(parentLocation);
}
}
@ -563,8 +546,11 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
const childLocation = [...location, i];
if (i < node.children.length - 1) {
const size = orientation === Orientation.VERTICAL ? child.box.height : child.box.width;
this.gridview.resizeView(childLocation, Math.floor(size * scale));
const size = orientation === Orientation.VERTICAL
? { height: Math.floor(child.box.height * scale) }
: { width: Math.floor(child.box.width * scale) };
this.gridview.resizeView(childLocation, size);
}
this.restoreViewsSize(childLocation, child, orthogonal(orientation), widthScale, heightScale);

View file

@ -15,13 +15,18 @@ import { Color } from 'vs/base/common/color';
export { Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
export interface IViewSize {
readonly width: number;
readonly height: number;
}
export interface IView {
readonly element: HTMLElement;
readonly minimumWidth: number;
readonly maximumWidth: number;
readonly minimumHeight: number;
readonly maximumHeight: number;
readonly onDidChange: Event<{ width: number; height: number; } | undefined>;
readonly onDidChange: Event<IViewSize | undefined>;
readonly priority?: LayoutPriority;
readonly snapSize?: number;
layout(width: number, height: number, orientation: Orientation): void;
@ -573,7 +578,7 @@ export class GridView implements IDisposable {
get maximumWidth(): number { return this.root.maximumHeight; }
get maximumHeight(): number { return this.root.maximumHeight; }
private _onDidChange = new Relay<{ width: number; height: number; } | undefined>();
private _onDidChange = new Relay<IViewSize | undefined>();
readonly onDidChange = this._onDidChange.event;
constructor(options: IGridViewOptions = {}) {
@ -747,18 +752,33 @@ export class GridView implements IDisposable {
}
}
resizeView(location: number[], size: number): void {
resizeView(location: number[], { width, height }: Partial<IViewSize>): void {
const [rest, index] = tail(location);
const [, parent] = this.getNode(rest);
const [pathToParent, parent] = this.getNode(rest);
if (!(parent instanceof BranchNode)) {
throw new Error('Invalid location');
}
parent.resizeChild(index, size);
if (!width && !height) {
return;
}
const [parentSize, grandParentSize] = parent.orientation === Orientation.HORIZONTAL ? [width, height] : [height, width];
if (typeof grandParentSize === 'number' && pathToParent.length > 0) {
const [, grandParent] = tail(pathToParent);
const [, parentIndex] = tail(rest);
grandParent.resizeChild(parentIndex, grandParentSize);
}
if (typeof parentSize === 'number') {
parent.resizeChild(index, parentSize);
}
}
getViewSize(location: number[]): { width: number; height: number; } {
getViewSize(location: number[]): IViewSize {
const [, node] = this.getNode(location);
return { width: node.width, height: node.height };
}

View file

@ -326,6 +326,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get filterOnType(): boolean { return this.tree.filterOnType; }
get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
return this.tree.expandOnlyOnTwistieClick;
}
const fn = this.tree.expandOnlyOnTwistieClick;
return element => fn(this.nodes.get((element === this.root.element ? null : element) as T) || null);
}
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }

View file

@ -141,18 +141,13 @@ export interface VSBufferReadable {
read(): VSBuffer | null;
}
/**
* A buffer readable stream emits data to listeners. The stream
* will only start emitting when the first data listener has
* been added or the resume() method has been called.
*/
export interface VSBufferReadableStream {
export interface ReadableStream<T> {
/**
* The 'data' event is emitted whenever the stream is
* relinquishing ownership of a chunk of data to a consumer.
*/
on(event: 'data', callback: (chunk: VSBuffer) => void): void;
on(event: 'data', callback: (chunk: T) => void): void;
/**
* Emitted when any error occurs.
@ -182,6 +177,17 @@ export interface VSBufferReadableStream {
destroy(): void;
}
/**
* A readable stream that sends data via VSBuffer.
*/
export interface VSBufferReadableStream extends ReadableStream<VSBuffer> { }
export function isVSBufferReadableStream(obj: any): obj is VSBufferReadableStream {
const candidate: VSBufferReadableStream = obj;
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
}
/**
* Helper to fully read a VSBuffer readable into a single buffer.
*/
@ -239,6 +245,19 @@ export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream {
return stream;
}
/**
* Helper to create a VSBufferStream from a Uint8Array stream.
*/
export function toVSBufferReadableStream(stream: ReadableStream<Uint8Array>): VSBufferReadableStream {
const vsbufferStream = writeableBufferStream();
stream.on('data', data => vsbufferStream.write(VSBuffer.wrap(data)));
stream.on('end', () => vsbufferStream.end());
stream.on('error', error => vsbufferStream.error(error));
return vsbufferStream;
}
/**
* Helper to create a VSBufferStream that can be pushed
* buffers to. Will only start to emit data when a listener

View file

@ -495,7 +495,7 @@ function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
return word[pos] !== wordLow[pos];
}
function isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number): boolean {
export function isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number): boolean {
while (patternPos < patternLen && wordPos < wordLen) {
if (patternLow[patternPos] === wordLow[wordPos]) {
patternPos += 1;

View file

@ -92,3 +92,25 @@ export function removeMarkdownEscapes(text: string): string {
}
return text.replace(/\\([\\`*_{}[\]()#+\-.!])/g, '$1');
}
export function parseHrefAndDimensions(href: string): { href: string, dimensions: string[] } {
const dimensions: string[] = [];
const splitted = href.split('|').map(s => s.trim());
href = splitted[0];
const parameters = splitted[1];
if (parameters) {
const heightFromParams = /height=(\d+)/.exec(parameters);
const widthFromParams = /width=(\d+)/.exec(parameters);
const height = heightFromParams ? heightFromParams[1] : '';
const width = widthFromParams ? widthFromParams[1] : '';
const widthIsFinite = isFinite(parseInt(width));
const heightIsFinite = isFinite(parseInt(height));
if (widthIsFinite) {
dimensions.push(`width="${width}"`);
}
if (heightIsFinite) {
dimensions.push(`height="${height}"`);
}
}
return { href, dimensions };
}

View file

@ -81,7 +81,13 @@ export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
}
export function toDisposable(fn: () => void): IDisposable {
return trackDisposable({ dispose: fn });
const self = trackDisposable({
dispose: () => {
markTracked(self);
fn();
}
});
return self;
}
export class DisposableStore implements IDisposable {

View file

@ -170,9 +170,9 @@ export function getAllPropertyNames(obj: object): string[] {
}
export function getAllMethodNames(obj: object): string[] {
let methods: string[] = [];
const methods: string[] = [];
for (const prop of getAllPropertyNames(obj)) {
if (typeof obj[prop] === 'function') {
if (typeof (obj as any)[prop] === 'function') {
methods.push(prop);
}
}

View file

@ -212,12 +212,12 @@ export class SimpleWorkerClient<W extends object, H extends object> extends Disp
this._worker.postMessage(msg);
},
handleMessage: (method: string, args: any[]): Promise<any> => {
if (typeof host[method] !== 'function') {
if (typeof (host as any)[method] !== 'function') {
return Promise.reject(new Error('Missing method ' + method + ' on main thread host.'));
}
try {
return Promise.resolve(host[method].apply(host, args));
return Promise.resolve((host as any)[method].apply(host, args));
} catch (e) {
return Promise.reject(e);
}

View file

@ -138,6 +138,20 @@ export async function readdir(path: string): Promise<string[]> {
return handleDirectoryChildren(await promisify(fs.readdir)(path));
}
export async function readdirWithFileTypes(path: string): Promise<fs.Dirent[]> {
const children = await promisify(fs.readdir)(path, { withFileTypes: true });
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
if (platform.isMacintosh) {
for (const child of children) {
child.name = normalizeNFC(child.name);
}
}
return children;
}
export function readdirSync(path: string): string[] {
return handleDirectoryChildren(fs.readdirSync(path));
}

View file

@ -426,6 +426,19 @@ suite('URI', () => {
assert.equal(uri.toString(true), input);
});
test('Unable to open \'%A0.txt\': URI malformed #76506', function () {
let uri = URI.file('/foo/%A0.txt');
let uri2 = URI.parse(uri.toString());
assert.equal(uri.scheme, uri2.scheme);
assert.equal(uri.path, uri2.path);
uri = URI.file('/foo/%2e.txt');
uri2 = URI.parse(uri.toString());
assert.equal(uri.scheme, uri2.scheme);
assert.equal(uri.path, uri2.path);
});
test('URI - (de)serialize', function () {
const values = [

View file

@ -16,6 +16,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { canNormalize } from 'vs/base/common/normalization';
import { VSBuffer } from 'vs/base/common/buffer';
import { join } from 'path';
const chunkSize = 64 * 1024;
const readError = 'Error while reading';
@ -386,6 +387,31 @@ suite('PFS', () => {
}
});
test('readdirWithFileTypes', async () => {
if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) {
const id = uuid.generateUuid();
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
const testDir = join(parentDir, 'pfs', id);
const newDir = path.join(testDir, 'öäü');
await pfs.mkdirp(newDir, 493);
await pfs.writeFile(join(testDir, 'somefile.txt'), 'contents');
assert.ok(fs.existsSync(newDir));
const children = await pfs.readdirWithFileTypes(testDir);
assert.equal(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so
assert.equal(children.some(n => n.isDirectory()), true);
assert.equal(children.some(n => n.name === 'somefile.txt'), true);
assert.equal(children.some(n => n.isFile()), true);
await pfs.rimraf(parentDir);
}
});
test('writeFile (string)', async () => {
const smallData = 'Hello World';
const bigData = (new Array(100 * 1024)).join('Large String\n');

View file

@ -24,7 +24,7 @@
<meta id="vscode-remote-connection-token" data-settings="{{CONNECTION_AUTH_TOKEN}}">
</head>
<body class="vs-dark" aria-label="">
<body aria-label="">
</body>
<!-- Require our AMD loader -->

View file

@ -336,7 +336,7 @@ export class IssueReporter extends Disposable {
this.render();
});
['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'].forEach(elementId => {
(['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'] as const).forEach(elementId => {
this.addEventListener(elementId, 'click', (event: Event) => {
event.stopPropagation();
this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] });
@ -346,7 +346,7 @@ export class IssueReporter extends Disposable {
const showInfoElements = document.getElementsByClassName('showInfo');
for (let i = 0; i < showInfoElements.length; i++) {
const showInfo = showInfoElements.item(i);
showInfo!.addEventListener('click', (e) => {
showInfo!.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
const label = (<HTMLDivElement>e.target);
if (label) {

View file

@ -43,7 +43,6 @@ bootstrapWindow.load([
});
/**
* // configuration: IWindowConfiguration
* @param {{
* partsSplashPath?: string,
* highContrast?: boolean,

View file

@ -584,9 +584,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Config (combination of process.argv and window configuration)
const environment = parseArgs(process.argv);
const config = objects.assign(environment, windowConfiguration);
for (let key in config) {
if (config[key] === undefined || config[key] === null || config[key] === '' || config[key] === false) {
delete config[key]; // only send over properties that have a true value
for (const key in config) {
const configValue = (config as any)[key];
if (configValue === undefined || configValue === null || configValue === '' || configValue === false) {
delete (config as any)[key]; // only send over properties that have a true value
}
}

View file

@ -14,7 +14,6 @@ import { CommonEditorConfiguration, IEnvConfiguration } from 'vs/editor/common/c
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo';
import { IDimension } from 'vs/editor/common/editorCommon';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
class CSSBasedConfigurationCache {
@ -62,28 +61,17 @@ export function readFontInfo(bareFontInfo: BareFontInfo): FontInfo {
return CSSBasedConfiguration.INSTANCE.readConfiguration(bareFontInfo);
}
export function restoreFontInfo(storageService: IStorageService): void {
const strStoredFontInfo = storageService.get('editorFontInfo', StorageScope.GLOBAL);
if (typeof strStoredFontInfo !== 'string') {
return;
}
let storedFontInfo: ISerializedFontInfo[] | null = null;
try {
storedFontInfo = JSON.parse(strStoredFontInfo);
} catch (err) {
return;
}
if (!Array.isArray(storedFontInfo)) {
return;
}
CSSBasedConfiguration.INSTANCE.restoreFontInfo(storedFontInfo);
export function restoreFontInfo(fontInfo: ISerializedFontInfo[]): void {
CSSBasedConfiguration.INSTANCE.restoreFontInfo(fontInfo);
}
export function saveFontInfo(storageService: IStorageService): void {
const knownFontInfo = CSSBasedConfiguration.INSTANCE.saveFontInfo();
if (knownFontInfo.length > 0) {
storageService.store('editorFontInfo', JSON.stringify(knownFontInfo), StorageScope.GLOBAL);
export function serializeFontInfo(): ISerializedFontInfo[] | null {
const fontInfo = CSSBasedConfiguration.INSTANCE.saveFontInfo();
if (fontInfo.length > 0) {
return fontInfo;
}
return null;
}
export interface ISerializedFontInfo {

View file

@ -767,6 +767,7 @@ export class Minimap extends ViewPart {
// Cache line offset data so that it is only read once per line
let lineIndexToXOffset = lineOffsetMap.get(lineNumber);
const isFirstDecorationForLine = !lineIndexToXOffset;
if (!lineIndexToXOffset) {
const lineData = this._context.model.getLineContent(lineNumber);
lineIndexToXOffset = [0];
@ -796,12 +797,22 @@ export class Minimap extends ViewPart {
this.renderDecoration(canvasContext, <ModelDecorationMinimapOptions>decoration.options.minimap, x, y, width, height);
}
if (isFirstDecorationForLine) {
this.renderLineHighlight(canvasContext, <ModelDecorationMinimapOptions>decoration.options.minimap, y, height);
}
}
private renderLineHighlight(canvasContext: CanvasRenderingContext2D, minimapOptions: ModelDecorationMinimapOptions, y: number, height: number): void {
const decorationColor = minimapOptions.getColor(this._context.theme);
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);
canvasContext.fillStyle = decorationColor;
canvasContext.fillStyle = decorationColor && decorationColor.toString() || '';
canvasContext.fillRect(x, y, width, height);
}

View file

@ -783,7 +783,7 @@ export interface ITextModel {
* Flush all tokenization state.
* @internal
*/
flushTokens(): void;
resetTokenization(): void;
/**
* Force tokenization information for `lineNumber` to be accurate.

View file

@ -32,6 +32,8 @@ import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/comm
import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
import { TokensStore, MultilineTokens } from 'vs/editor/common/model/tokensStore';
import { Color } from 'vs/base/common/color';
function createTextBufferBuilder() {
return new PieceTreeTextBufferBuilder();
@ -288,7 +290,8 @@ export class TextModel extends Disposable implements model.ITextModel {
//#region Tokenization
private _languageIdentifier: LanguageIdentifier;
private readonly _languageRegistryListener: IDisposable;
_tokenization: TextModelTokenization;
private readonly _tokens: TokensStore;
private readonly _tokenization: TextModelTokenization;
//#endregion
constructor(source: string | model.ITextBufferFactory, creationOptions: model.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier | null, associatedResource: URI | null = null) {
@ -348,6 +351,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._isRedoing = false;
this._trimAutoWhitespaceLines = null;
this._tokens = new TokensStore();
this._tokenization = new TextModelTokenization(this);
}
@ -421,6 +425,9 @@ export class TextModel extends Disposable implements model.ITextModel {
this._buffer = textBuffer;
this._increaseVersionId();
// Flush all tokens
this._tokens.flush();
// Destroy all my decorations
this._decorations = Object.create(null);
this._decorationsTree = new DecorationsTrees();
@ -1271,7 +1278,8 @@ export class TextModel extends Disposable implements model.ITextModel {
let lineCount = oldLineCount;
for (let i = 0, len = contentChanges.length; i < len; i++) {
const change = contentChanges[i];
const [eolCount] = countEOL(change.text);
const [eolCount, firstLineLength] = countEOL(change.text);
this._tokens.applyEdits(change.range, eolCount, firstLineLength);
this._onDidChangeDecorations.fire();
this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers);
@ -1696,12 +1704,60 @@ export class TextModel extends Disposable implements model.ITextModel {
//#region Tokenization
public setLineTokens(lineNumber: number, tokens: Uint32Array): void {
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
throw new Error('Illegal value for lineNumber');
}
this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens);
}
public setTokens(tokens: MultilineTokens[]): void {
if (tokens.length === 0) {
return;
}
let ranges: { fromLineNumber: number; toLineNumber: number; }[] = [];
for (let i = 0, len = tokens.length; i < len; i++) {
const element = tokens[i];
ranges.push({ fromLineNumber: element.startLineNumber, toLineNumber: element.startLineNumber + element.tokens.length - 1 });
for (let j = 0, lenJ = element.tokens.length; j < lenJ; j++) {
this.setLineTokens(element.startLineNumber + j, element.tokens[j]);
}
}
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
ranges: ranges
});
}
public tokenizeViewport(startLineNumber: number, endLineNumber: number): void {
startLineNumber = Math.max(1, startLineNumber);
endLineNumber = Math.min(this._buffer.getLineCount(), endLineNumber);
this._tokenization.tokenizeViewport(startLineNumber, endLineNumber);
}
public flushTokens(): void {
this._tokenization.flushTokens();
public clearTokens(): void {
this._tokens.flush();
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: true,
ranges: [{
fromLineNumber: 1,
toLineNumber: this._buffer.getLineCount()
}]
});
}
private _emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void {
if (!this._isDisposing) {
this._onDidChangeTokens.fire(e);
}
}
public resetTokenization(): void {
this._tokenization.reset();
}
public forceTokenization(lineNumber: number): void {
@ -1731,7 +1787,8 @@ export class TextModel extends Disposable implements model.ITextModel {
}
private _getLineTokens(lineNumber: number): LineTokens {
return this._tokenization.getLineTokens(lineNumber);
const lineText = this.getLineContent(lineNumber);
return this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText);
}
public getLanguageIdentifier(): LanguageIdentifier {
@ -1761,13 +1818,8 @@ export class TextModel extends Disposable implements model.ITextModel {
public getLanguageIdAtPosition(lineNumber: number, column: number): LanguageId {
const position = this.validatePosition(new Position(lineNumber, column));
return this._tokenization.getLanguageIdAtPosition(position);
}
emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void {
if (!this._isDisposing) {
this._onDidChangeTokens.fire(e);
}
const lineTokens = this.getLineTokens(position.lineNumber);
return lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1));
}
// Having tokens allows implementing additional helper methods
@ -2584,12 +2636,22 @@ function cleanClassName(className: string): string {
class DecorationOptions implements model.IDecorationOptions {
readonly color: string | ThemeColor;
readonly darkColor: string | ThemeColor;
private _resolvedColor: string | null;
constructor(options: model.IDecorationOptions) {
this.color = options.color || strings.empty;
this.darkColor = options.darkColor || strings.empty;
}
}
export class ModelDecorationOverviewRulerOptions extends DecorationOptions {
readonly position: model.OverviewRulerLane;
private _resolvedColor: string | null;
constructor(options: model.IModelDecorationOverviewRulerOptions) {
super(options);
this._resolvedColor = null;
this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center);
}
public getColor(theme: ITheme): string {
@ -2619,22 +2681,34 @@ class DecorationOptions implements model.IDecorationOptions {
}
}
export class ModelDecorationOverviewRulerOptions extends DecorationOptions {
readonly position: model.OverviewRulerLane;
constructor(options: model.IModelDecorationOverviewRulerOptions) {
super(options);
this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center);
}
}
export class ModelDecorationMinimapOptions extends DecorationOptions {
readonly position: model.MinimapPosition;
private _resolvedColor: Color | undefined;
constructor(options: model.IModelDecorationMinimapOptions) {
super(options);
this.position = options.position;
}
public getColor(theme: ITheme): Color | undefined {
if (!this._resolvedColor) {
if (theme.type !== 'light' && this.darkColor) {
this._resolvedColor = this._resolveColor(this.darkColor, theme);
} else {
this._resolvedColor = this._resolveColor(this.color, theme);
}
}
return this._resolvedColor;
}
private _resolveColor(color: string | ThemeColor, theme: ITheme): Color | undefined {
if (typeof color === 'string') {
return Color.fromHex(color);
}
return theme.getColor(color.id);
}
}
export class ModelDecorationOptions implements model.IModelDecorationOptions {

View file

@ -9,13 +9,14 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { Position } from 'vs/editor/common/core/position';
import { IRange } from 'vs/editor/common/core/range';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { IModelTokensChangedEvent, RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import { ColorId, FontStyle, IState, ITokenizationSupport, LanguageId, LanguageIdentifier, MetadataConsts, StandardTokenType, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes';
import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import { IState, ITokenizationSupport, LanguageIdentifier, TokenizationRegistry } from 'vs/editor/common/modes';
import { nullTokenize2 } from 'vs/editor/common/modes/nullMode';
import { TextModel } from 'vs/editor/common/model/textModel';
import { Disposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { CharCode } from 'vs/base/common/charCode';
import { MultilineTokensBuilder } from 'vs/editor/common/model/tokensStore';
export function countEOL(text: string): [number, number] {
let eolCount = 0;
@ -47,158 +48,18 @@ export function countEOL(text: string): [number, number] {
return [eolCount, firstLineLength];
}
function getDefaultMetadata(topLevelLanguageId: LanguageId): number {
return (
(topLevelLanguageId << MetadataConsts.LANGUAGEID_OFFSET)
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
| (FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET)
| (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET)
| (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET)
) >>> 0;
}
const EMPTY_LINE_TOKENS = (new Uint32Array(0)).buffer;
const enum Constants {
CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048
}
class ModelLineTokens {
public static deleteBeginning(lineTokens: ArrayBuffer | null, toChIndex: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS) {
return lineTokens;
}
return this.delete(lineTokens, 0, toChIndex);
}
public static deleteEnding(lineTokens: ArrayBuffer | null, fromChIndex: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS) {
return lineTokens;
}
const tokens = new Uint32Array(lineTokens);
const lineTextLength = tokens[tokens.length - 2];
return this.delete(lineTokens, fromChIndex, lineTextLength);
}
public static delete(lineTokens: ArrayBuffer | null, fromChIndex: number, toChIndex: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS || fromChIndex === toChIndex) {
return lineTokens;
}
const tokens = new Uint32Array(lineTokens);
const tokensCount = (tokens.length >>> 1);
// special case: deleting everything
if (fromChIndex === 0 && tokens[tokens.length - 2] === toChIndex) {
return EMPTY_LINE_TOKENS;
}
const fromTokenIndex = LineTokens.findIndexInTokensArray(tokens, fromChIndex);
const fromTokenStartOffset = (fromTokenIndex > 0 ? tokens[(fromTokenIndex - 1) << 1] : 0);
const fromTokenEndOffset = tokens[fromTokenIndex << 1];
if (toChIndex < fromTokenEndOffset) {
// the delete range is inside a single token
const delta = (toChIndex - fromChIndex);
for (let i = fromTokenIndex; i < tokensCount; i++) {
tokens[i << 1] -= delta;
}
return lineTokens;
}
let dest: number;
let lastEnd: number;
if (fromTokenStartOffset !== fromChIndex) {
tokens[fromTokenIndex << 1] = fromChIndex;
dest = ((fromTokenIndex + 1) << 1);
lastEnd = fromChIndex;
} else {
dest = (fromTokenIndex << 1);
lastEnd = fromTokenStartOffset;
}
const delta = (toChIndex - fromChIndex);
for (let tokenIndex = fromTokenIndex + 1; tokenIndex < tokensCount; tokenIndex++) {
const tokenEndOffset = tokens[tokenIndex << 1] - delta;
if (tokenEndOffset > lastEnd) {
tokens[dest++] = tokenEndOffset;
tokens[dest++] = tokens[(tokenIndex << 1) + 1];
lastEnd = tokenEndOffset;
}
}
if (dest === tokens.length) {
// nothing to trim
return lineTokens;
}
let tmp = new Uint32Array(dest);
tmp.set(tokens.subarray(0, dest), 0);
return tmp.buffer;
}
public static append(lineTokens: ArrayBuffer | null, _otherTokens: ArrayBuffer | null): ArrayBuffer | null {
if (_otherTokens === EMPTY_LINE_TOKENS) {
return lineTokens;
}
if (lineTokens === EMPTY_LINE_TOKENS) {
return _otherTokens;
}
if (lineTokens === null) {
return lineTokens;
}
if (_otherTokens === null) {
// cannot determine combined line length...
return null;
}
const myTokens = new Uint32Array(lineTokens);
const otherTokens = new Uint32Array(_otherTokens);
const otherTokensCount = (otherTokens.length >>> 1);
let result = new Uint32Array(myTokens.length + otherTokens.length);
result.set(myTokens, 0);
let dest = myTokens.length;
const delta = myTokens[myTokens.length - 2];
for (let i = 0; i < otherTokensCount; i++) {
result[dest++] = otherTokens[(i << 1)] + delta;
result[dest++] = otherTokens[(i << 1) + 1];
}
return result.buffer;
}
public static insert(lineTokens: ArrayBuffer | null, chIndex: number, textLength: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS) {
// nothing to do
return lineTokens;
}
const tokens = new Uint32Array(lineTokens);
const tokensCount = (tokens.length >>> 1);
let fromTokenIndex = LineTokens.findIndexInTokensArray(tokens, chIndex);
if (fromTokenIndex > 0) {
const fromTokenStartOffset = tokens[(fromTokenIndex - 1) << 1];
if (fromTokenStartOffset === chIndex) {
fromTokenIndex--;
}
}
for (let tokenIndex = fromTokenIndex; tokenIndex < tokensCount; tokenIndex++) {
tokens[tokenIndex << 1] += textLength;
}
return lineTokens;
}
}
export class TokenizationStateStore {
private _beginState: (IState | null)[];
private _valid: boolean[];
private _len: number;
private _invalidLineStartIndex: number;
constructor(initialState: IState | null) {
this._reset(initialState);
constructor() {
this._reset(null);
}
private _reset(initialState: IState | null): void {
@ -212,6 +73,10 @@ export class TokenizationStateStore {
}
}
public flush(initialState: IState | null): void {
this._reset(initialState);
}
public get invalidLineStartIndex() {
return this._invalidLineStartIndex;
}
@ -282,7 +147,7 @@ export class TokenizationStateStore {
this._beginState[lineIndex] = beginState;
}
public setGoodTokens(linesLength: number, lineIndex: number, endState: IState): void {
public setEndState(linesLength: number, lineIndex: number, endState: IState): void {
this._setValid(lineIndex, true);
this._invalidLineStartIndex = lineIndex + 1;
@ -310,28 +175,23 @@ export class TokenizationStateStore {
this._invalidLineStartIndex = i;
}
setFakeTokens(lineIndex: number): void {
public setFakeTokens(lineIndex: number): void {
this._setValid(lineIndex, false);
}
//#region Editing
public applyEdits(range: IRange, eolCount: number): void {
try {
const deletingLinesCnt = range.endLineNumber - range.startLineNumber;
const insertingLinesCnt = eolCount;
const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);
const deletingLinesCnt = range.endLineNumber - range.startLineNumber;
const insertingLinesCnt = eolCount;
const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);
for (let j = editingLinesCnt; j >= 0; j--) {
this._invalidateLine(range.startLineNumber + j - 1);
}
this._acceptDeleteRange(range);
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount);
} catch (err) {
// emergency recovery => reset tokens
this._reset(this.getBeginState(0));
for (let j = editingLinesCnt; j >= 0; j--) {
this._invalidateLine(range.startLineNumber + j - 1);
}
this._acceptDeleteRange(range);
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount);
}
private _acceptDeleteRange(range: IRange): void {
@ -357,305 +217,20 @@ export class TokenizationStateStore {
//#endregion
}
export class TokensStore {
private _lineTokens: (ArrayBuffer | null)[];
private _len: number;
constructor() {
this._reset();
}
private _reset(): void {
this._lineTokens = [];
this._len = 0;
}
public getTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineText: string): LineTokens {
let rawLineTokens: ArrayBuffer | null = null;
if (lineIndex < this._len) {
rawLineTokens = this._lineTokens[lineIndex];
}
if (rawLineTokens !== null && rawLineTokens !== EMPTY_LINE_TOKENS) {
return new LineTokens(new Uint32Array(rawLineTokens), lineText);
}
let lineTokens = new Uint32Array(2);
lineTokens[0] = lineText.length;
lineTokens[1] = getDefaultMetadata(topLevelLanguageId);
return new LineTokens(lineTokens, lineText);
}
private static _massageTokens(topLevelLanguageId: LanguageId, lineTextLength: number, tokens: Uint32Array): ArrayBuffer {
if (lineTextLength === 0) {
let hasDifferentLanguageId = false;
if (tokens && tokens.length > 1) {
hasDifferentLanguageId = (TokenMetadata.getLanguageId(tokens[1]) !== topLevelLanguageId);
}
if (!hasDifferentLanguageId) {
return EMPTY_LINE_TOKENS;
}
}
if (!tokens || tokens.length === 0) {
tokens = new Uint32Array(2);
tokens[0] = 0;
tokens[1] = getDefaultMetadata(topLevelLanguageId);
}
LineTokens.convertToEndOffset(tokens, lineTextLength);
return tokens.buffer;
}
private _ensureLine(lineIndex: number): void {
while (lineIndex >= this._len) {
this._lineTokens[this._len] = null;
this._len++;
}
}
private _deleteLines(start: number, deleteCount: number): void {
if (deleteCount === 0) {
return;
}
this._lineTokens.splice(start, deleteCount);
this._len -= deleteCount;
}
private _insertLines(insertIndex: number, insertCount: number): void {
if (insertCount === 0) {
return;
}
let lineTokens: (ArrayBuffer | null)[] = [];
for (let i = 0; i < insertCount; i++) {
lineTokens[i] = null;
}
this._lineTokens = arrays.arrayInsert(this._lineTokens, insertIndex, lineTokens);
this._len += insertCount;
}
private _setTokens(lineIndex: number, tokens: ArrayBuffer | null): void {
this._ensureLine(lineIndex);
this._lineTokens[lineIndex] = tokens;
}
public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array): void {
const tokens = TokensStore._massageTokens(topLevelLanguageId, lineTextLength, _tokens);
this._setTokens(lineIndex, tokens);
}
//#region Editing
public applyEdits(range: IRange, eolCount: number, firstLineLength: number): void {
try {
this._acceptDeleteRange(range);
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength);
} catch (err) {
// emergency recovery => reset tokens
this._reset();
}
}
private _acceptDeleteRange(range: IRange): void {
const firstLineIndex = range.startLineNumber - 1;
if (firstLineIndex >= this._len) {
return;
}
if (range.startLineNumber === range.endLineNumber) {
if (range.startColumn === range.endColumn) {
// Nothing to delete
return;
}
this._lineTokens[firstLineIndex] = ModelLineTokens.delete(this._lineTokens[firstLineIndex], range.startColumn - 1, range.endColumn - 1);
return;
}
this._lineTokens[firstLineIndex] = ModelLineTokens.deleteEnding(this._lineTokens[firstLineIndex], range.startColumn - 1);
const lastLineIndex = range.endLineNumber - 1;
let lastLineTokens: ArrayBuffer | null = null;
if (lastLineIndex < this._len) {
lastLineTokens = ModelLineTokens.deleteBeginning(this._lineTokens[lastLineIndex], range.endColumn - 1);
}
// Take remaining text on last line and append it to remaining text on first line
this._lineTokens[firstLineIndex] = ModelLineTokens.append(this._lineTokens[firstLineIndex], lastLineTokens);
// Delete middle lines
this._deleteLines(range.startLineNumber, range.endLineNumber - range.startLineNumber);
}
private _acceptInsertText(position: Position, eolCount: number, firstLineLength: number): void {
if (eolCount === 0 && firstLineLength === 0) {
// Nothing to insert
return;
}
const lineIndex = position.lineNumber - 1;
if (lineIndex >= this._len) {
return;
}
if (eolCount === 0) {
// Inserting text on one line
this._lineTokens[lineIndex] = ModelLineTokens.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
return;
}
this._lineTokens[lineIndex] = ModelLineTokens.deleteEnding(this._lineTokens[lineIndex], position.column - 1);
this._lineTokens[lineIndex] = ModelLineTokens.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
this._insertLines(position.lineNumber, eolCount);
}
//#endregion
}
export class ModelLinesTokens {
private readonly _languageIdentifier: LanguageIdentifier;
public readonly tokenizationSupport: ITokenizationSupport | null;
constructor(languageIdentifier: LanguageIdentifier, tokenizationSupport: ITokenizationSupport | null) {
this._languageIdentifier = languageIdentifier;
this.tokenizationSupport = tokenizationSupport;
}
public isCheapToTokenize(tokenizationStateStore: TokenizationStateStore, buffer: TextModel, lineNumber: number): boolean {
if (!this.tokenizationSupport) {
return true;
}
const firstInvalidLineNumber = tokenizationStateStore.invalidLineStartIndex + 1;
if (lineNumber > firstInvalidLineNumber) {
return false;
}
if (lineNumber < firstInvalidLineNumber) {
return true;
}
if (buffer.getLineLength(lineNumber) < Constants.CHEAP_TOKENIZATION_LENGTH_LIMIT) {
return true;
}
return false;
}
public hasLinesToTokenize(tokenizationStateStore: TokenizationStateStore, buffer: TextModel): boolean {
if (!this.tokenizationSupport) {
return false;
}
return (tokenizationStateStore.invalidLineStartIndex < buffer.getLineCount());
}
public tokenizeOneInvalidLine(tokensStore: TokensStore, tokenizationStateStore: TokenizationStateStore, buffer: TextModel, eventBuilder: ModelTokensChangedEventBuilder): number {
if (!this.hasLinesToTokenize(tokenizationStateStore, buffer)) {
return buffer.getLineCount() + 1;
}
const lineNumber = tokenizationStateStore.invalidLineStartIndex + 1;
this.updateTokensUntilLine(tokensStore, tokenizationStateStore, buffer, eventBuilder, lineNumber);
return lineNumber;
}
public updateTokensUntilLine(tokensStore: TokensStore, tokenizationStateStore: TokenizationStateStore, buffer: TextModel, eventBuilder: ModelTokensChangedEventBuilder, lineNumber: number): void {
if (!this.tokenizationSupport) {
return;
}
const linesLength = buffer.getLineCount();
const endLineIndex = lineNumber - 1;
// Validate all states up to and including endLineIndex
for (let lineIndex = tokenizationStateStore.invalidLineStartIndex; lineIndex <= endLineIndex; lineIndex++) {
const text = buffer.getLineContent(lineIndex + 1);
const lineStartState = tokenizationStateStore.getBeginState(lineIndex);
const r = safeTokenize(this._languageIdentifier, this.tokenizationSupport, text, lineStartState!);
tokensStore.setTokens(this._languageIdentifier.id, lineIndex, text.length, r.tokens);
tokenizationStateStore.setGoodTokens(linesLength, lineIndex, r.endState);
eventBuilder.registerChangedTokens(lineIndex + 1);
lineIndex = tokenizationStateStore.invalidLineStartIndex - 1; // -1 because the outer loop increments it
}
}
public tokenizeViewport(tokensStore: TokensStore, tokenizationStateStore: TokenizationStateStore, buffer: TextModel, eventBuilder: ModelTokensChangedEventBuilder, startLineNumber: number, endLineNumber: number): void {
if (!this.tokenizationSupport) {
// nothing to do
return;
}
if (endLineNumber <= tokenizationStateStore.invalidLineStartIndex) {
// nothing to do
return;
}
if (startLineNumber <= tokenizationStateStore.invalidLineStartIndex) {
// tokenization has reached the viewport start...
this.updateTokensUntilLine(tokensStore, tokenizationStateStore, buffer, eventBuilder, endLineNumber);
return;
}
let nonWhitespaceColumn = buffer.getLineFirstNonWhitespaceColumn(startLineNumber);
let fakeLines: string[] = [];
let initialState: IState | null = null;
for (let i = startLineNumber - 1; nonWhitespaceColumn > 0 && i >= 1; i--) {
let newNonWhitespaceIndex = buffer.getLineFirstNonWhitespaceColumn(i);
if (newNonWhitespaceIndex === 0) {
continue;
}
if (newNonWhitespaceIndex < nonWhitespaceColumn) {
initialState = tokenizationStateStore.getBeginState(i - 1);
if (initialState) {
break;
}
fakeLines.push(buffer.getLineContent(i));
nonWhitespaceColumn = newNonWhitespaceIndex;
}
}
if (!initialState) {
initialState = this.tokenizationSupport.getInitialState();
}
let state = initialState;
for (let i = fakeLines.length - 1; i >= 0; i--) {
let r = safeTokenize(this._languageIdentifier, this.tokenizationSupport, fakeLines[i], state);
state = r.endState;
}
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
let text = buffer.getLineContent(lineNumber);
let r = safeTokenize(this._languageIdentifier, this.tokenizationSupport, text, state);
tokensStore.setTokens(this._languageIdentifier.id, lineNumber - 1, text.length, r.tokens);
tokenizationStateStore.setFakeTokens(lineNumber - 1);
state = r.endState;
eventBuilder.registerChangedTokens(lineNumber);
}
}
}
export class TextModelTokenization extends Disposable {
private readonly _textModel: TextModel;
private readonly _tokenizationStateStore: TokenizationStateStore;
private _revalidateTokensTimeout: any;
private _tokenization: ModelLinesTokens;
_tokensStore: TokensStore;
_tokenizationStateStore: TokenizationStateStore;
private _tokenizationSupport: ITokenizationSupport | null;
constructor(textModel: TextModel) {
super();
this._textModel = textModel;
this._tokenizationStateStore = new TokenizationStateStore();
this._revalidateTokensTimeout = -1;
this._tokenizationSupport = null;
this._register(TokenizationRegistry.onDidChange((e) => {
const languageIdentifier = this._textModel.getLanguageIdentifier();
if (e.changedLanguages.indexOf(languageIdentifier.language) === -1) {
@ -663,44 +238,35 @@ export class TextModelTokenization extends Disposable {
}
this._resetTokenizationState();
this._textModel.emitModelTokensChangedEvent({
tokenizationSupportChanged: true,
ranges: [{
fromLineNumber: 1,
toLineNumber: this._textModel.getLineCount()
}]
});
this._textModel.clearTokens();
}));
this._register(this._textModel.onDidChangeRawContentFast((e) => {
if (e.containsEvent(RawContentChangedType.Flush)) {
this._resetTokenizationState();
return;
}
}));
this._register(this._textModel.onDidChangeContentFast((e) => {
for (let i = 0, len = e.changes.length; i < len; i++) {
const change = e.changes[i];
const [eolCount, firstLineLength] = countEOL(change.text);
this._tokensStore.applyEdits(change.range, eolCount, firstLineLength);
const [eolCount] = countEOL(change.text);
this._tokenizationStateStore.applyEdits(change.range, eolCount);
}
this._beginBackgroundTokenization();
}));
this._register(this._textModel.onDidChangeAttached(() => {
this._beginBackgroundTokenization();
}));
this._register(this._textModel.onDidChangeLanguage(() => {
this._resetTokenizationState();
this._textModel.emitModelTokensChangedEvent({
tokenizationSupportChanged: true,
ranges: [{
fromLineNumber: 1,
toLineNumber: this._textModel.getLineCount()
}]
});
this._textModel.clearTokens();
}));
this._resetTokenizationState();
}
@ -718,29 +284,14 @@ export class TextModelTokenization extends Disposable {
private _resetTokenizationState(): void {
this._clearTimers();
const languageIdentifier = this._textModel.getLanguageIdentifier();
let tokenizationSupport = (
this._textModel.isTooLargeForTokenization()
? null
: TokenizationRegistry.get(languageIdentifier.language)
);
let initialState: IState | null = null;
if (tokenizationSupport) {
try {
initialState = tokenizationSupport.getInitialState();
} catch (e) {
onUnexpectedError(e);
tokenizationSupport = null;
}
}
this._tokenization = new ModelLinesTokens(languageIdentifier, tokenizationSupport);
this._tokensStore = new TokensStore();
this._tokenizationStateStore = new TokenizationStateStore(initialState);
const [tokenizationSupport, initialState] = initializeTokenization(this._textModel);
this._tokenizationSupport = tokenizationSupport;
this._tokenizationStateStore.flush(initialState);
this._beginBackgroundTokenization();
}
private _beginBackgroundTokenization(): void {
if (this._textModel.isAttachedToEditor() && this._tokenization.hasLinesToTokenize(this._tokenizationStateStore, this._textModel) && this._revalidateTokensTimeout === -1) {
if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize() && this._revalidateTokensTimeout === -1) {
this._revalidateTokensTimeout = setTimeout(() => {
this._revalidateTokensTimeout = -1;
this._revalidateTokensNow();
@ -750,16 +301,16 @@ export class TextModelTokenization extends Disposable {
private _revalidateTokensNow(toLineNumber: number = this._textModel.getLineCount()): void {
const MAX_ALLOWED_TIME = 20;
const eventBuilder = new ModelTokensChangedEventBuilder();
const builder = new MultilineTokensBuilder();
const sw = StopWatch.create(false);
while (this._tokenization.hasLinesToTokenize(this._tokenizationStateStore, this._textModel)) {
while (this._hasLinesToTokenize()) {
if (sw.elapsed() > MAX_ALLOWED_TIME) {
// Stop if MAX_ALLOWED_TIME is reached
break;
}
const tokenizedLineNumber = this._tokenization.tokenizeOneInvalidLine(this._tokensStore, this._tokenizationStateStore, this._textModel, eventBuilder);
const tokenizedLineNumber = this._tokenizeOneInvalidLine(builder);
if (tokenizedLineNumber >= toLineNumber) {
break;
@ -767,66 +318,158 @@ export class TextModelTokenization extends Disposable {
}
this._beginBackgroundTokenization();
const e = eventBuilder.build();
if (e) {
this._textModel.emitModelTokensChangedEvent(e);
}
this._textModel.setTokens(builder.tokens);
}
public tokenizeViewport(startLineNumber: number, endLineNumber: number): void {
startLineNumber = Math.max(1, startLineNumber);
endLineNumber = Math.min(this._textModel.getLineCount(), endLineNumber);
const eventBuilder = new ModelTokensChangedEventBuilder();
this._tokenization.tokenizeViewport(this._tokensStore, this._tokenizationStateStore, this._textModel, eventBuilder, startLineNumber, endLineNumber);
const e = eventBuilder.build();
if (e) {
this._textModel.emitModelTokensChangedEvent(e);
}
const builder = new MultilineTokensBuilder();
this._tokenizeViewport(builder, startLineNumber, endLineNumber);
this._textModel.setTokens(builder.tokens);
}
public flushTokens(): void {
public reset(): void {
this._resetTokenizationState();
this._textModel.emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
ranges: [{
fromLineNumber: 1,
toLineNumber: this._textModel.getLineCount()
}]
});
this._textModel.clearTokens();
}
public forceTokenization(lineNumber: number): void {
const eventBuilder = new ModelTokensChangedEventBuilder();
this._tokenization.updateTokensUntilLine(this._tokensStore, this._tokenizationStateStore, this._textModel, eventBuilder, lineNumber);
const e = eventBuilder.build();
if (e) {
this._textModel.emitModelTokensChangedEvent(e);
}
const builder = new MultilineTokensBuilder();
this._updateTokensUntilLine(builder, lineNumber);
this._textModel.setTokens(builder.tokens);
}
public isCheapToTokenize(lineNumber: number): boolean {
return this._tokenization.isCheapToTokenize(this._tokenizationStateStore, this._textModel, lineNumber);
}
public getLineTokens(lineNumber: number): LineTokens {
const lineText = this._textModel.getLineContent(lineNumber);
const languageIdentifier = this._textModel.getLanguageIdentifier();
return this._tokensStore.getTokens(languageIdentifier.id, lineNumber - 1, lineText);
}
public getLanguageIdAtPosition(position: Position): LanguageId {
if (!this._tokenization.tokenizationSupport) {
return this._textModel.getLanguageIdentifier().id;
if (!this._tokenizationSupport) {
return true;
}
let lineTokens = this.getLineTokens(position.lineNumber);
return lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1));
const firstInvalidLineNumber = this._tokenizationStateStore.invalidLineStartIndex + 1;
if (lineNumber > firstInvalidLineNumber) {
return false;
}
if (lineNumber < firstInvalidLineNumber) {
return true;
}
if (this._textModel.getLineLength(lineNumber) < Constants.CHEAP_TOKENIZATION_LENGTH_LIMIT) {
return true;
}
return false;
}
private _hasLinesToTokenize(): boolean {
if (!this._tokenizationSupport) {
return false;
}
return (this._tokenizationStateStore.invalidLineStartIndex < this._textModel.getLineCount());
}
private _tokenizeOneInvalidLine(builder: MultilineTokensBuilder): number {
if (!this._hasLinesToTokenize()) {
return this._textModel.getLineCount() + 1;
}
const lineNumber = this._tokenizationStateStore.invalidLineStartIndex + 1;
this._updateTokensUntilLine(builder, lineNumber);
return lineNumber;
}
private _updateTokensUntilLine(builder: MultilineTokensBuilder, lineNumber: number): void {
if (!this._tokenizationSupport) {
return;
}
const languageIdentifier = this._textModel.getLanguageIdentifier();
const linesLength = this._textModel.getLineCount();
const endLineIndex = lineNumber - 1;
// Validate all states up to and including endLineIndex
for (let lineIndex = this._tokenizationStateStore.invalidLineStartIndex; lineIndex <= endLineIndex; lineIndex++) {
const text = this._textModel.getLineContent(lineIndex + 1);
const lineStartState = this._tokenizationStateStore.getBeginState(lineIndex);
const r = safeTokenize(languageIdentifier, this._tokenizationSupport, text, lineStartState!);
builder.add(lineIndex + 1, r.tokens);
this._tokenizationStateStore.setEndState(linesLength, lineIndex, r.endState);
lineIndex = this._tokenizationStateStore.invalidLineStartIndex - 1; // -1 because the outer loop increments it
}
}
private _tokenizeViewport(builder: MultilineTokensBuilder, startLineNumber: number, endLineNumber: number): void {
if (!this._tokenizationSupport) {
// nothing to do
return;
}
if (endLineNumber <= this._tokenizationStateStore.invalidLineStartIndex) {
// nothing to do
return;
}
if (startLineNumber <= this._tokenizationStateStore.invalidLineStartIndex) {
// tokenization has reached the viewport start...
this._updateTokensUntilLine(builder, endLineNumber);
return;
}
let nonWhitespaceColumn = this._textModel.getLineFirstNonWhitespaceColumn(startLineNumber);
let fakeLines: string[] = [];
let initialState: IState | null = null;
for (let i = startLineNumber - 1; nonWhitespaceColumn > 0 && i >= 1; i--) {
let newNonWhitespaceIndex = this._textModel.getLineFirstNonWhitespaceColumn(i);
if (newNonWhitespaceIndex === 0) {
continue;
}
if (newNonWhitespaceIndex < nonWhitespaceColumn) {
initialState = this._tokenizationStateStore.getBeginState(i - 1);
if (initialState) {
break;
}
fakeLines.push(this._textModel.getLineContent(i));
nonWhitespaceColumn = newNonWhitespaceIndex;
}
}
if (!initialState) {
initialState = this._tokenizationSupport.getInitialState();
}
const languageIdentifier = this._textModel.getLanguageIdentifier();
let state = initialState;
for (let i = fakeLines.length - 1; i >= 0; i--) {
let r = safeTokenize(languageIdentifier, this._tokenizationSupport, fakeLines[i], state);
state = r.endState;
}
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
let text = this._textModel.getLineContent(lineNumber);
let r = safeTokenize(languageIdentifier, this._tokenizationSupport, text, state);
builder.add(lineNumber, r.tokens);
this._tokenizationStateStore.setFakeTokens(lineNumber - 1);
state = r.endState;
}
}
}
function initializeTokenization(textModel: TextModel): [ITokenizationSupport | null, IState | null] {
const languageIdentifier = textModel.getLanguageIdentifier();
let tokenizationSupport = (
textModel.isTooLargeForTokenization()
? null
: TokenizationRegistry.get(languageIdentifier.language)
);
let initialState: IState | null = null;
if (tokenizationSupport) {
try {
initialState = tokenizationSupport.getInitialState();
} catch (e) {
onUnexpectedError(e);
tokenizationSupport = null;
}
}
return [tokenizationSupport, initialState];
}
function safeTokenize(languageIdentifier: LanguageIdentifier, tokenizationSupport: ITokenizationSupport | null, text: string, state: IState): TokenizationResult2 {
@ -843,41 +486,7 @@ function safeTokenize(languageIdentifier: LanguageIdentifier, tokenizationSuppor
if (!r) {
r = nullTokenize2(languageIdentifier.id, text, state, 0);
}
LineTokens.convertToEndOffset(r.tokens, text.length);
return r;
}
export class ModelTokensChangedEventBuilder {
private readonly _ranges: { fromLineNumber: number; toLineNumber: number; }[];
constructor() {
this._ranges = [];
}
public registerChangedTokens(lineNumber: number): void {
const ranges = this._ranges;
const rangesLength = ranges.length;
const previousRange = rangesLength > 0 ? ranges[rangesLength - 1] : null;
if (previousRange && previousRange.toLineNumber === lineNumber - 1) {
// extend previous range
previousRange.toLineNumber++;
} else {
// insert new range
ranges[rangesLength] = {
fromLineNumber: lineNumber,
toLineNumber: lineNumber
};
}
}
public build(): IModelTokensChangedEvent | null {
if (this._ranges.length === 0) {
return null;
}
return {
tokenizationSupportChanged: false,
ranges: this._ranges
};
}
}

View file

@ -0,0 +1,334 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from 'vs/base/common/arrays';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { Position } from 'vs/editor/common/core/position';
import { IRange } from 'vs/editor/common/core/range';
import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType, TokenMetadata } from 'vs/editor/common/modes';
function getDefaultMetadata(topLevelLanguageId: LanguageId): number {
return (
(topLevelLanguageId << MetadataConsts.LANGUAGEID_OFFSET)
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
| (FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET)
| (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET)
| (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET)
) >>> 0;
}
const EMPTY_LINE_TOKENS = (new Uint32Array(0)).buffer;
export class MultilineTokensBuilder {
public readonly tokens: MultilineTokens[];
constructor() {
this.tokens = [];
}
public add(lineNumber: number, lineTokens: Uint32Array): void {
if (this.tokens.length > 0) {
const last = this.tokens[this.tokens.length - 1];
const lastLineNumber = last.startLineNumber + last.tokens.length - 1;
if (lastLineNumber + 1 === lineNumber) {
// append
last.tokens.push(lineTokens);
return;
}
}
this.tokens.push(new MultilineTokens(lineNumber, lineTokens));
}
}
export class MultilineTokens {
public readonly startLineNumber: number;
public readonly tokens: Uint32Array[];
constructor(lineNumber: number, tokens: Uint32Array) {
this.startLineNumber = lineNumber;
this.tokens = [tokens];
}
}
export class TokensStore {
private _lineTokens: (ArrayBuffer | null)[];
private _len: number;
constructor() {
this._lineTokens = [];
this._len = 0;
}
public flush(): void {
this._lineTokens = [];
this._len = 0;
}
public getTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineText: string): LineTokens {
let rawLineTokens: ArrayBuffer | null = null;
if (lineIndex < this._len) {
rawLineTokens = this._lineTokens[lineIndex];
}
if (rawLineTokens !== null && rawLineTokens !== EMPTY_LINE_TOKENS) {
return new LineTokens(new Uint32Array(rawLineTokens), lineText);
}
let lineTokens = new Uint32Array(2);
lineTokens[0] = lineText.length;
lineTokens[1] = getDefaultMetadata(topLevelLanguageId);
return new LineTokens(lineTokens, lineText);
}
private static _massageTokens(topLevelLanguageId: LanguageId, lineTextLength: number, tokens: Uint32Array): ArrayBuffer {
if (lineTextLength === 0) {
let hasDifferentLanguageId = false;
if (tokens && tokens.length > 1) {
hasDifferentLanguageId = (TokenMetadata.getLanguageId(tokens[1]) !== topLevelLanguageId);
}
if (!hasDifferentLanguageId) {
return EMPTY_LINE_TOKENS;
}
}
if (!tokens || tokens.length === 0) {
tokens = new Uint32Array(2);
tokens[0] = lineTextLength;
tokens[1] = getDefaultMetadata(topLevelLanguageId);
}
return tokens.buffer;
}
private _ensureLine(lineIndex: number): void {
while (lineIndex >= this._len) {
this._lineTokens[this._len] = null;
this._len++;
}
}
private _deleteLines(start: number, deleteCount: number): void {
if (deleteCount === 0) {
return;
}
this._lineTokens.splice(start, deleteCount);
this._len -= deleteCount;
}
private _insertLines(insertIndex: number, insertCount: number): void {
if (insertCount === 0) {
return;
}
let lineTokens: (ArrayBuffer | null)[] = [];
for (let i = 0; i < insertCount; i++) {
lineTokens[i] = null;
}
this._lineTokens = arrays.arrayInsert(this._lineTokens, insertIndex, lineTokens);
this._len += insertCount;
}
private _setTokens(lineIndex: number, tokens: ArrayBuffer | null): void {
this._ensureLine(lineIndex);
this._lineTokens[lineIndex] = tokens;
}
public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array): void {
const tokens = TokensStore._massageTokens(topLevelLanguageId, lineTextLength, _tokens);
this._setTokens(lineIndex, tokens);
}
//#region Editing
public applyEdits(range: IRange, eolCount: number, firstLineLength: number): void {
this._acceptDeleteRange(range);
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength);
}
private _acceptDeleteRange(range: IRange): void {
const firstLineIndex = range.startLineNumber - 1;
if (firstLineIndex >= this._len) {
return;
}
if (range.startLineNumber === range.endLineNumber) {
if (range.startColumn === range.endColumn) {
// Nothing to delete
return;
}
this._lineTokens[firstLineIndex] = TokensStore._delete(this._lineTokens[firstLineIndex], range.startColumn - 1, range.endColumn - 1);
return;
}
this._lineTokens[firstLineIndex] = TokensStore._deleteEnding(this._lineTokens[firstLineIndex], range.startColumn - 1);
const lastLineIndex = range.endLineNumber - 1;
let lastLineTokens: ArrayBuffer | null = null;
if (lastLineIndex < this._len) {
lastLineTokens = TokensStore._deleteBeginning(this._lineTokens[lastLineIndex], range.endColumn - 1);
}
// Take remaining text on last line and append it to remaining text on first line
this._lineTokens[firstLineIndex] = TokensStore._append(this._lineTokens[firstLineIndex], lastLineTokens);
// Delete middle lines
this._deleteLines(range.startLineNumber, range.endLineNumber - range.startLineNumber);
}
private _acceptInsertText(position: Position, eolCount: number, firstLineLength: number): void {
if (eolCount === 0 && firstLineLength === 0) {
// Nothing to insert
return;
}
const lineIndex = position.lineNumber - 1;
if (lineIndex >= this._len) {
return;
}
if (eolCount === 0) {
// Inserting text on one line
this._lineTokens[lineIndex] = TokensStore._insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
return;
}
this._lineTokens[lineIndex] = TokensStore._deleteEnding(this._lineTokens[lineIndex], position.column - 1);
this._lineTokens[lineIndex] = TokensStore._insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
this._insertLines(position.lineNumber, eolCount);
}
private static _deleteBeginning(lineTokens: ArrayBuffer | null, toChIndex: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS) {
return lineTokens;
}
return TokensStore._delete(lineTokens, 0, toChIndex);
}
private static _deleteEnding(lineTokens: ArrayBuffer | null, fromChIndex: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS) {
return lineTokens;
}
const tokens = new Uint32Array(lineTokens);
const lineTextLength = tokens[tokens.length - 2];
return TokensStore._delete(lineTokens, fromChIndex, lineTextLength);
}
private static _delete(lineTokens: ArrayBuffer | null, fromChIndex: number, toChIndex: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS || fromChIndex === toChIndex) {
return lineTokens;
}
const tokens = new Uint32Array(lineTokens);
const tokensCount = (tokens.length >>> 1);
// special case: deleting everything
if (fromChIndex === 0 && tokens[tokens.length - 2] === toChIndex) {
return EMPTY_LINE_TOKENS;
}
const fromTokenIndex = LineTokens.findIndexInTokensArray(tokens, fromChIndex);
const fromTokenStartOffset = (fromTokenIndex > 0 ? tokens[(fromTokenIndex - 1) << 1] : 0);
const fromTokenEndOffset = tokens[fromTokenIndex << 1];
if (toChIndex < fromTokenEndOffset) {
// the delete range is inside a single token
const delta = (toChIndex - fromChIndex);
for (let i = fromTokenIndex; i < tokensCount; i++) {
tokens[i << 1] -= delta;
}
return lineTokens;
}
let dest: number;
let lastEnd: number;
if (fromTokenStartOffset !== fromChIndex) {
tokens[fromTokenIndex << 1] = fromChIndex;
dest = ((fromTokenIndex + 1) << 1);
lastEnd = fromChIndex;
} else {
dest = (fromTokenIndex << 1);
lastEnd = fromTokenStartOffset;
}
const delta = (toChIndex - fromChIndex);
for (let tokenIndex = fromTokenIndex + 1; tokenIndex < tokensCount; tokenIndex++) {
const tokenEndOffset = tokens[tokenIndex << 1] - delta;
if (tokenEndOffset > lastEnd) {
tokens[dest++] = tokenEndOffset;
tokens[dest++] = tokens[(tokenIndex << 1) + 1];
lastEnd = tokenEndOffset;
}
}
if (dest === tokens.length) {
// nothing to trim
return lineTokens;
}
let tmp = new Uint32Array(dest);
tmp.set(tokens.subarray(0, dest), 0);
return tmp.buffer;
}
private static _append(lineTokens: ArrayBuffer | null, _otherTokens: ArrayBuffer | null): ArrayBuffer | null {
if (_otherTokens === EMPTY_LINE_TOKENS) {
return lineTokens;
}
if (lineTokens === EMPTY_LINE_TOKENS) {
return _otherTokens;
}
if (lineTokens === null) {
return lineTokens;
}
if (_otherTokens === null) {
// cannot determine combined line length...
return null;
}
const myTokens = new Uint32Array(lineTokens);
const otherTokens = new Uint32Array(_otherTokens);
const otherTokensCount = (otherTokens.length >>> 1);
let result = new Uint32Array(myTokens.length + otherTokens.length);
result.set(myTokens, 0);
let dest = myTokens.length;
const delta = myTokens[myTokens.length - 2];
for (let i = 0; i < otherTokensCount; i++) {
result[dest++] = otherTokens[(i << 1)] + delta;
result[dest++] = otherTokens[(i << 1) + 1];
}
return result.buffer;
}
private static _insert(lineTokens: ArrayBuffer | null, chIndex: number, textLength: number): ArrayBuffer | null {
if (lineTokens === null || lineTokens === EMPTY_LINE_TOKENS) {
// nothing to do
return lineTokens;
}
const tokens = new Uint32Array(lineTokens);
const tokensCount = (tokens.length >>> 1);
let fromTokenIndex = LineTokens.findIndexInTokensArray(tokens, chIndex);
if (fromTokenIndex > 0) {
const fromTokenStartOffset = tokens[(fromTokenIndex - 1) << 1];
if (fromTokenStartOffset === chIndex) {
fromTokenIndex--;
}
}
for (let tokenIndex = fromTokenIndex; tokenIndex < tokensCount; tokenIndex++) {
tokens[tokenIndex << 1] += textLength;
}
return lineTokens;
}
//#endregion
}

View file

@ -17,8 +17,8 @@ import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/t
import * as model from 'vs/editor/common/model';
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IMarkerData } from 'vs/platform/markers/common/markers';
/**
* Open ended enum at runtime
@ -1241,19 +1241,7 @@ export interface CommentThreadTemplate {
export interface CommentInfo {
extensionId?: string;
threads: CommentThread[];
commentingRanges?: (IRange[] | CommentingRanges);
reply?: Command;
draftMode?: DraftMode;
template?: CommentThreadTemplate;
}
/**
* @internal
*/
export enum DraftMode {
NotSupported,
InDraft,
NotInDraft
commentingRanges: CommentingRanges;
}
/**
@ -1293,7 +1281,7 @@ export interface CommentInput {
/**
* @internal
*/
export interface CommentThread2 {
export interface CommentThread {
commentThreadHandle: number;
controllerHandle: number;
extensionId?: string;
@ -1307,11 +1295,6 @@ export interface CommentThread2 {
collapsibleState?: CommentThreadCollapsibleState;
input?: CommentInput;
onDidChangeInput: Event<CommentInput | undefined>;
acceptInputCommand?: Command;
additionalCommands?: Command[];
deleteCommand?: Command;
onDidChangeAcceptInputCommand: Event<Command | undefined>;
onDidChangeAdditionalCommands: Event<Command[] | undefined>;
onDidChangeRange: Event<IRange>;
onDidChangeLabel: Event<string>;
onDidChangeCollasibleState: Event<CommentThreadCollapsibleState | undefined>;
@ -1325,30 +1308,6 @@ export interface CommentThread2 {
export interface CommentingRanges {
readonly resource: URI;
ranges: IRange[];
newCommentThreadCallback?: (uri: UriComponents, range: IRange) => Promise<CommentThread | undefined>;
}
/**
* @internal
*/
export interface CommentThread {
extensionId?: string;
threadId: string | null;
resource: string | null;
range: IRange;
comments: Comment[] | undefined;
collapsibleState?: CommentThreadCollapsibleState;
reply?: Command;
isDisposed?: boolean;
contextValue?: string;
}
/**
* @internal
*/
export interface NewCommentAction {
ranges: IRange[];
actions: Command[];
}
/**
@ -1380,12 +1339,6 @@ export interface Comment {
readonly userName: string;
readonly userIconPath?: string;
readonly contextValue?: string;
readonly canEdit?: boolean;
readonly canDelete?: boolean;
readonly selectCommand?: Command;
readonly editCommand?: Command;
readonly deleteCommand?: Command;
readonly isDraft?: boolean;
readonly commentReactions?: CommentReaction[];
readonly label?: string;
readonly mode?: CommentMode;
@ -1398,54 +1351,17 @@ export interface CommentThreadChangedEvent {
/**
* Added comment threads.
*/
readonly added: (CommentThread | CommentThread2)[];
readonly added: CommentThread[];
/**
* Removed comment threads.
*/
readonly removed: (CommentThread | CommentThread2)[];
readonly removed: CommentThread[];
/**
* Changed comment threads.
*/
readonly changed: (CommentThread | CommentThread2)[];
/**
* changed draft mode.
*/
readonly draftMode?: DraftMode;
}
/**
* @internal
*/
export interface DocumentCommentProvider {
provideDocumentComments(resource: URI, token: CancellationToken): Promise<CommentInfo | null>;
createNewCommentThread(resource: URI, range: Range, text: string, token: CancellationToken): Promise<CommentThread | null>;
replyToCommentThread(resource: URI, range: Range, thread: CommentThread, text: string, token: CancellationToken): Promise<CommentThread | null>;
editComment(resource: URI, comment: Comment, text: string, token: CancellationToken): Promise<void>;
deleteComment(resource: URI, comment: Comment, token: CancellationToken): Promise<void>;
startDraft?(resource: URI, token: CancellationToken): Promise<void>;
deleteDraft?(resource: URI, token: CancellationToken): Promise<void>;
finishDraft?(resource: URI, token: CancellationToken): Promise<void>;
startDraftLabel?: string;
deleteDraftLabel?: string;
finishDraftLabel?: string;
addReaction?(resource: URI, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise<void>;
deleteReaction?(resource: URI, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise<void>;
reactionGroup?: CommentReaction[];
onDidChangeCommentThreads?(): Event<CommentThreadChangedEvent>;
}
/**
* @internal
*/
export interface WorkspaceCommentProvider {
provideWorkspaceComments(token: CancellationToken): Promise<CommentThread[]>;
onDidChangeCommentThreads(): Event<CommentThreadChangedEvent>;
readonly changed: CommentThread[];
}
/**

View file

@ -52,13 +52,13 @@ export interface IWebWorkerOptions {
/**
* An object that can be used by the web worker to make calls back to the main thread.
*/
host?: object;
host?: any;
}
class MonacoWebWorkerImpl<T> extends EditorWorkerClient implements MonacoWebWorker<T> {
private readonly _foreignModuleId: string;
private readonly _foreignModuleHost: object | null;
private readonly _foreignModuleHost: { [method: string]: Function } | null;
private _foreignModuleCreateData: any | null;
private _foreignProxy: Promise<T> | null;

View file

@ -24,6 +24,9 @@ import { IMarkerService } from 'vs/platform/markers/common/markers';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger';
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { IPosition } from 'vs/editor/common/core/position';
function contextKeyForSupportedActions(kind: CodeActionKind) {
return ContextKeyExpr.regex(
@ -58,7 +61,7 @@ export class QuickFixController extends Disposable implements IEditorContributio
this._editor = editor;
this._model = this._register(new CodeActionModel(this._editor, markerService, contextKeyService, progressService));
this._register(this._model.onDidChangeState((newState) => this._onDidChangeCodeActionsState(newState)));
this._register(this._model.onDidChangeState((newState) => this.update(newState)));
this._ui = this._register(new CodeActionUi(editor, QuickFixAction.Id, {
applyCodeAction: async (action, retrigger) => {
@ -73,10 +76,14 @@ export class QuickFixController extends Disposable implements IEditorContributio
}, contextMenuService, keybindingService));
}
private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void {
private update(newState: CodeActionsState.State): void {
this._ui.update(newState);
}
public showCodeActions(actions: Promise<CodeActionSet>, at: IAnchor | IPosition) {
return this._ui.showCodeActionList(actions, at);
}
public getId(): string {
return QuickFixController.ID;
}

View file

@ -107,7 +107,7 @@ class CodeActionOracle extends Disposable {
}
}
}
return selection ? selection : undefined;
return selection;
}
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): TriggeredCodeAction {

View file

@ -15,6 +15,8 @@ import { CodeActionsState } from './codeActionModel';
import { CodeActionAutoApply } from './codeActionTrigger';
import { CodeActionWidget } from './codeActionWidget';
import { LightBulbWidget } from './lightBulbWidget';
import { IPosition } from 'vs/editor/common/core/position';
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
export class CodeActionUi extends Disposable {
@ -93,6 +95,18 @@ export class CodeActionUi extends Disposable {
}
}
public async showCodeActionList(codeActions: Promise<CodeActionSet>, at?: IAnchor | IPosition): Promise<void> {
let actions: CodeActionSet;
try {
actions = await codeActions;
} catch (e) {
onUnexpectedError(e);
return;
}
this._codeActionWidget.show(actions, at);
}
private _handleLightBulbSelect(e: { x: number, y: number, actions: CodeActionSet }): void {
this._codeActionWidget.show(e.actions, e);
}

View file

@ -7,12 +7,13 @@ import { getDomNodePagePosition } from 'vs/base/browser/dom';
import { Action } from 'vs/base/common/actions';
import { canceled } from 'vs/base/common/errors';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { CodeAction } from 'vs/editor/common/modes';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
interface CodeActionWidgetDelegate {
onSelectCodeAction: (action: CodeAction) => Promise<any>;
@ -31,7 +32,7 @@ export class CodeActionWidget extends Disposable {
super();
}
public async show(codeActions: CodeActionSet, at?: { x: number; y: number } | Position): Promise<void> {
public async show(codeActions: CodeActionSet, at?: IAnchor | IPosition): Promise<void> {
if (!codeActions.actions.length) {
this._visible = false;
return;
@ -72,7 +73,7 @@ export class CodeActionWidget extends Disposable {
return this._visible;
}
private _toCoords(position: Position): { x: number, y: number } {
private _toCoords(position: IPosition): { x: number, y: number } {
if (!this._editor.hasModel()) {
return { x: 0, y: 0 };
}

View file

@ -9,7 +9,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { FindMatch, IModelDecorationsChangeAccessor, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, MinimapPosition } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry';
import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
export class FindDecorations implements IDisposable {
@ -271,7 +271,7 @@ export class FindDecorations implements IDisposable {
position: OverviewRulerLane.Center
},
minimap: {
color: themeColorFromId(overviewRulerFindMatchForeground),
color: themeColorFromId(minimapFindMatch),
position: MinimapPosition.Inline
}
});
@ -285,7 +285,7 @@ export class FindDecorations implements IDisposable {
position: OverviewRulerLane.Center
},
minimap: {
color: themeColorFromId(overviewRulerFindMatchForeground),
color: themeColorFromId(minimapFindMatch),
position: MinimapPosition.Inline
}
});

View file

@ -31,7 +31,9 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { InitializingRangeProvider, ID_INIT_PROVIDER } from 'vs/editor/contrib/folding/intializingRangeProvider';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { onUnexpectedError } from 'vs/base/common/errors';
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
const CONTEXT_FOLDING_ENABLED = new RawContextKey<boolean>('foldingEnabled', false);
export const ID = 'editor.contrib.folding';
export interface RangeProvider {
@ -73,12 +75,15 @@ export class FoldingController extends Disposable implements IEditorContribution
private foldingModelPromise: Promise<FoldingModel | null> | null;
private updateScheduler: Delayer<FoldingModel | null> | null;
private foldingEnabled: IContextKey<boolean>;
private cursorChangedScheduler: RunOnceScheduler | null;
private readonly localToDispose = this._register(new DisposableStore());
constructor(editor: ICodeEditor) {
constructor(
editor: ICodeEditor,
@IContextKeyService private readonly contextKeyService: IContextKeyService
) {
super();
this.editor = editor;
this._isEnabled = this.editor.getConfiguration().contribInfo.folding;
@ -88,6 +93,8 @@ export class FoldingController extends Disposable implements IEditorContribution
this.foldingDecorationProvider = new FoldingDecorationProvider(editor);
this.foldingDecorationProvider.autoHideFoldingControls = this._autoHideFoldingControls;
this.foldingEnabled = CONTEXT_FOLDING_ENABLED.bindTo(this.contextKeyService);
this.foldingEnabled.set(this._isEnabled);
this._register(this.editor.onDidChangeModel(() => this.onModelChanged()));
@ -95,6 +102,7 @@ export class FoldingController extends Disposable implements IEditorContribution
if (e.contribInfo) {
let oldIsEnabled = this._isEnabled;
this._isEnabled = this.editor.getConfiguration().contribInfo.folding;
this.foldingEnabled.set(this._isEnabled);
if (oldIsEnabled !== this._isEnabled) {
this.onModelChanged();
}
@ -496,7 +504,7 @@ class UnfoldAction extends FoldingAction<FoldingArguments> {
id: 'editor.unfold',
label: nls.localize('unfoldAction.label', "Unfold"),
alias: 'Unfold',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET,
@ -560,7 +568,7 @@ class UnFoldRecursivelyAction extends FoldingAction<void> {
id: 'editor.unfoldRecursively',
label: nls.localize('unFoldRecursivelyAction.label', "Unfold Recursively"),
alias: 'Unfold Recursively',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_CLOSE_SQUARE_BRACKET),
@ -581,7 +589,7 @@ class FoldAction extends FoldingAction<FoldingArguments> {
id: 'editor.fold',
label: nls.localize('foldAction.label', "Fold"),
alias: 'Fold',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET,
@ -645,7 +653,7 @@ class FoldRecursivelyAction extends FoldingAction<void> {
id: 'editor.foldRecursively',
label: nls.localize('foldRecursivelyAction.label', "Fold Recursively"),
alias: 'Fold Recursively',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_OPEN_SQUARE_BRACKET),
@ -667,7 +675,7 @@ class FoldAllBlockCommentsAction extends FoldingAction<void> {
id: 'editor.foldAllBlockComments',
label: nls.localize('foldAllBlockComments.label', "Fold All Block Comments"),
alias: 'Fold All Block Comments',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_SLASH),
@ -700,7 +708,7 @@ class FoldAllRegionsAction extends FoldingAction<void> {
id: 'editor.foldAllMarkerRegions',
label: nls.localize('foldAllMarkerRegions.label', "Fold All Regions"),
alias: 'Fold All Regions',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_8),
@ -733,7 +741,7 @@ class UnfoldAllRegionsAction extends FoldingAction<void> {
id: 'editor.unfoldAllMarkerRegions',
label: nls.localize('unfoldAllMarkerRegions.label', "Unfold All Regions"),
alias: 'Unfold All Regions',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_9),
@ -766,7 +774,7 @@ class FoldAllAction extends FoldingAction<void> {
id: 'editor.foldAll',
label: nls.localize('foldAllAction.label', "Fold All"),
alias: 'Fold All',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_0),
@ -787,7 +795,7 @@ class UnfoldAllAction extends FoldingAction<void> {
id: 'editor.unfoldAll',
label: nls.localize('unfoldAllAction.label', "Unfold All"),
alias: 'Unfold All',
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_J),
@ -831,7 +839,7 @@ for (let i = 1; i <= 7; i++) {
id: FoldLevelAction.ID(i),
label: nls.localize('foldLevelAction.label', "Fold Level {0}", i),
alias: `Fold Level ${i}`,
precondition: undefined,
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | (KeyCode.KEY_0 + i)),

View file

@ -25,9 +25,6 @@ import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCod
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
export class ModesHoverController implements IEditorContribution {
@ -68,9 +65,6 @@ export class ModesHoverController implements IEditorContribution {
@IModeService private readonly _modeService: IModeService,
@IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
@ICommandService private readonly _commandService: ICommandService,
@IThemeService private readonly _themeService: IThemeService
) {
this._isMouseDown = false;
@ -211,7 +205,7 @@ export class ModesHoverController implements IEditorContribution {
}
private _createHoverWidget() {
this._contentWidget = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._contextMenuService, this._bulkEditService, this._commandService, this._modeService, this._openerService);
this._contentWidget = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService);
this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService);
}

View file

@ -13,7 +13,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes';
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor, CodeAction } from 'vs/editor/common/modes';
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
@ -31,13 +31,9 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { applyCodeAction, QuickFixAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { Action } from 'vs/base/common/actions';
import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
@ -187,10 +183,6 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
}
}
interface ActionSet extends IDisposable {
readonly actions: Action[];
}
export class ModesContentHoverWidget extends ContentHoverWidget {
static readonly ID = 'editor.contrib.modesContentHoverWidget';
@ -211,9 +203,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
markerDecorationsService: IMarkerDecorationsService,
private readonly _themeService: IThemeService,
private readonly _keybindingService: IKeybindingService,
private readonly _contextMenuService: IContextMenuService,
private readonly _bulkEditService: IBulkEditService,
private readonly _commandService: ICommandService,
private readonly _modeService: IModeService,
private readonly _openerService: IOpenerService | null = NullOpenerService,
) {
@ -346,7 +335,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
let isEmptyHoverContent = true;
let containColorPicker = false;
let markdownDisposeables: IDisposable[] = [];
const markdownDisposeables = new DisposableStore();
const markerMessages: MarkerHover[] = [];
messages.forEach((msg) => {
if (!msg.range) {
@ -437,7 +426,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
this.updateContents(fragment);
this._colorPicker.layout();
this.renderDisposable.value = combinedDisposable(colorListener, colorChangeListener, widget, ...markdownDisposeables);
this.renderDisposable.value = combinedDisposable(colorListener, colorChangeListener, widget, markdownDisposeables);
});
} else {
if (msg instanceof MarkerHover) {
@ -449,15 +438,14 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
.forEach(contents => {
const markdownHoverElement = $('div.hover-row.markdown-hover');
const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService);
markdownDisposeables.push(renderer.onDidRenderCodeBlock(() => {
const renderer = markdownDisposeables.add(new MarkdownRenderer(this._editor, this._modeService, this._openerService));
markdownDisposeables.add(renderer.onDidRenderCodeBlock(() => {
hoverContentsElement.className = 'hover-contents code-hover-contents';
this.onContentsChange();
}));
const renderedContents = renderer.render(contents);
const renderedContents = markdownDisposeables.add(renderer.render(contents));
hoverContentsElement.appendChild(renderedContents.element);
fragment.appendChild(markdownHoverElement);
markdownDisposeables.push(renderedContents);
isEmptyHoverContent = false;
});
}
@ -535,12 +523,12 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
run: async (target) => {
const codeActionsPromise = this.getCodeActions(markerHover.marker);
disposables.add(toDisposable(() => codeActionsPromise.cancel()));
const actions = await codeActionsPromise;
disposables.add(actions);
const controller = QuickFixController.get(this._editor);
const elementPosition = dom.getDomNodePagePosition(target);
this._contextMenuService.showContextMenu({
getAnchor: () => ({ x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }),
getActions: () => actions.actions
controller.showCodeActions(codeActionsPromise, {
x: elementPosition.left + 6,
y: elementPosition.top + elementPosition.height + 6
});
}
}));
@ -559,30 +547,22 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
return hoverElement;
}
private getCodeActions(marker: IMarker): CancelablePromise<ActionSet> {
return createCancelablePromise(async cancellationToken => {
const codeActions = await getCodeActions(this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken);
if (codeActions.actions.length) {
const actions: Action[] = [];
for (const codeAction of codeActions.actions) {
actions.push(new Action(
codeAction.command ? codeAction.command.id : codeAction.title,
codeAction.title,
undefined,
true,
() => applyCodeAction(codeAction, this._bulkEditService, this._commandService)));
}
return {
actions: actions,
dispose: () => codeActions.dispose()
};
}
private getCodeActions(marker: IMarker): CancelablePromise<CodeActionSet> {
const noAction: CodeAction = {
title: nls.localize('editor.action.quickFix.noneMessage', "No code actions available"),
kind: CodeActionKind.QuickFix.value,
};
return createCancelablePromise(async (cancellationToken): Promise<CodeActionSet> => {
const result = await getCodeActions(
this._editor.getModel()!,
new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn),
{ type: 'manual', filter: { kind: CodeActionKind.QuickFix } },
cancellationToken);
return {
actions: [
new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available"))
],
dispose() { }
actions: result.actions.length ? result.actions : [noAction],
hasAutoFix: result.hasAutoFix,
dispose: () => result.dispose(),
};
});
}

View file

@ -5,7 +5,7 @@
import { $ } from 'vs/base/browser/dom';
import { IMarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation';
import { GlyphHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets';
@ -91,7 +91,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget {
private readonly _markdownRenderer: MarkdownRenderer;
private readonly _computer: MarginComputer;
private readonly _hoverOperation: HoverOperation<IHoverMessage[]>;
private _renderDisposeables: IDisposable[];
private readonly _renderDisposeables = this._register(new DisposableStore());
constructor(
editor: ICodeEditor,
@ -102,7 +102,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget {
this._lastLineNumber = -1;
this._markdownRenderer = new MarkdownRenderer(this._editor, modeService, openerService);
this._markdownRenderer = this._register(new MarkdownRenderer(this._editor, modeService, openerService));
this._computer = new MarginComputer(this._editor);
this._hoverOperation = new HoverOperation(
@ -116,7 +116,6 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget {
}
public dispose(): void {
this._renderDisposeables = dispose(this._renderDisposeables);
this._hoverOperation.cancel();
super.dispose();
}
@ -163,16 +162,15 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget {
}
private _renderMessages(lineNumber: number, messages: IHoverMessage[]): void {
dispose(this._renderDisposeables);
this._renderDisposeables = [];
this._renderDisposeables.clear();
const fragment = document.createDocumentFragment();
messages.forEach((msg) => {
for (const msg of messages) {
const renderedContents = this._markdownRenderer.render(msg.value);
this._renderDisposeables.push(renderedContents);
this._renderDisposeables.add(renderedContents);
fragment.appendChild($('div.hover-row', undefined, renderedContents.element));
});
}
this.updateContents(fragment);
this.showAt(lineNumber);

View file

@ -26,6 +26,7 @@ export interface ISnippetInsertOptions {
adjustWhitespace: boolean;
undoStopBefore: boolean;
undoStopAfter: boolean;
clipboardText: string | undefined;
}
const _defaultOptions: ISnippetInsertOptions = {
@ -34,6 +35,7 @@ const _defaultOptions: ISnippetInsertOptions = {
undoStopBefore: true,
undoStopAfter: true,
adjustWhitespace: true,
clipboardText: undefined
};
export class SnippetController2 implements IEditorContribution {

View file

@ -320,12 +320,14 @@ export interface ISnippetSessionInsertOptions {
overwriteBefore: number;
overwriteAfter: number;
adjustWhitespace: boolean;
clipboardText: string | undefined;
}
const _defaultOptions: ISnippetSessionInsertOptions = {
overwriteBefore: 0,
overwriteAfter: 0,
adjustWhitespace: true
adjustWhitespace: true,
clipboardText: undefined
};
export class SnippetSession {
@ -376,7 +378,7 @@ export class SnippetSession {
return selection;
}
static createEditsAndSnippets(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean): { edits: IIdentifiedSingleEditOperation[], snippets: OneSnippet[] } {
static createEditsAndSnippets(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean, clipboardText: string | undefined): { edits: IIdentifiedSingleEditOperation[], snippets: OneSnippet[] } {
const edits: IIdentifiedSingleEditOperation[] = [];
const snippets: OneSnippet[] = [];
@ -385,10 +387,12 @@ export class SnippetSession {
}
const model = editor.getModel();
const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional));
const workspaceService = editor.invokeWithinContext(accessor => accessor.get(IWorkspaceContextService, optional));
const modelBasedVariableResolver = editor.invokeWithinContext(accessor => new ModelBasedVariableResolver(accessor.get(ILabelService, optional), model));
const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional));
clipboardText = clipboardText || clipboardService && clipboardService.readTextSync();
let delta = 0;
// know what text the overwrite[Before|After] extensions
@ -440,7 +444,7 @@ export class SnippetSession {
snippet.resolveVariables(new CompositeSnippetVariableResolver([
modelBasedVariableResolver,
new ClipboardBasedVariableResolver(clipboardService, idx, indexedSelections.length),
new ClipboardBasedVariableResolver(clipboardText, idx, indexedSelections.length),
new SelectionBasedVariableResolver(model, selection),
new CommentBasedVariableResolver(model),
new TimeBasedVariableResolver,
@ -488,7 +492,7 @@ export class SnippetSession {
const model = this._editor.getModel();
// make insert edit and start with first selections
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace);
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText);
this._snippets = snippets;
const selections = model.pushEditOperations(this._editor.getSelections(), edits, undoEdits => {
@ -507,7 +511,7 @@ export class SnippetSession {
return;
}
this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]);
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace);
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText);
this._editor.setSelections(this._editor.getModel().pushEditOperations(this._editor.getSelections(), edits, undoEdits => {

View file

@ -11,7 +11,6 @@ import { Selection } from 'vs/editor/common/core/selection';
import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snippetParser';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, pad, endsWith } from 'vs/base/common/strings';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService } from 'vs/platform/label/common/label';
@ -128,7 +127,7 @@ export class SelectionBasedVariableResolver implements VariableResolver {
export class ModelBasedVariableResolver implements VariableResolver {
constructor(
private readonly _labelService: ILabelService,
private readonly _labelService: ILabelService | undefined,
private readonly _model: ITextModel
) {
//
@ -150,13 +149,13 @@ export class ModelBasedVariableResolver implements VariableResolver {
return name.slice(0, idx);
}
} else if (name === 'TM_DIRECTORY') {
} else if (name === 'TM_DIRECTORY' && this._labelService) {
if (path.dirname(this._model.uri.fsPath) === '.') {
return '';
}
return this._labelService.getUriLabel(dirname(this._model.uri));
} else if (name === 'TM_FILEPATH') {
} else if (name === 'TM_FILEPATH' && this._labelService) {
return this._labelService.getUriLabel(this._model.uri);
}
@ -167,7 +166,7 @@ export class ModelBasedVariableResolver implements VariableResolver {
export class ClipboardBasedVariableResolver implements VariableResolver {
constructor(
private readonly _clipboardService: IClipboardService,
private readonly _clipboardText: string | undefined,
private readonly _selectionIdx: number,
private readonly _selectionCount: number
) {
@ -175,20 +174,19 @@ export class ClipboardBasedVariableResolver implements VariableResolver {
}
resolve(variable: Variable): string | undefined {
if (variable.name !== 'CLIPBOARD' || !this._clipboardService) {
if (variable.name !== 'CLIPBOARD') {
return undefined;
}
const text = this._clipboardService.readText();
if (!text) {
if (!this._clipboardText) {
return undefined;
}
const lines = text.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s));
const lines = this._clipboardText.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s));
if (lines.length === this._selectionCount) {
return lines[this._selectionIdx];
} else {
return text;
return this._clipboardText;
}
}
}
@ -255,7 +253,7 @@ export class TimeBasedVariableResolver implements VariableResolver {
export class WorkspaceBasedVariableResolver implements VariableResolver {
constructor(
private readonly _workspaceService: IWorkspaceContextService,
private readonly _workspaceService: IWorkspaceContextService | undefined,
) {
//
}

View file

@ -126,7 +126,7 @@ suite('SnippetSession', function () {
test('snippets, newline NO whitespace adjust', () => {
editor.setSelection(new Selection(2, 5, 2, 5));
const session = new SnippetSession(editor, 'abc\n foo\n bar\n$0', { overwriteBefore: 0, overwriteAfter: 0, adjustWhitespace: false });
const session = new SnippetSession(editor, 'abc\n foo\n bar\n$0', { overwriteBefore: 0, overwriteAfter: 0, adjustWhitespace: false, clipboardText: undefined });
session.insert();
assert.equal(editor.getModel()!.getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}');
});
@ -648,7 +648,7 @@ suite('SnippetSession', function () {
assert.ok(actual.equalsSelection(new Selection(1, 9, 1, 12)));
editor.setSelections([new Selection(1, 9, 1, 12)]);
new SnippetSession(editor, 'far', { overwriteBefore: 3, overwriteAfter: 0, adjustWhitespace: true }).insert();
new SnippetSession(editor, 'far', { overwriteBefore: 3, overwriteAfter: 0, adjustWhitespace: true, clipboardText: undefined }).insert();
assert.equal(model.getValue(), 'console.far');
});
});

View file

@ -9,7 +9,6 @@ import { Selection } from 'vs/editor/common/core/selection';
import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver, TimeBasedVariableResolver, WorkspaceBasedVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables';
import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/snippetParser';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ILabelService } from 'vs/platform/label/common/label';
import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test';
@ -237,63 +236,26 @@ suite('Snippet Variables Resolver', function () {
test('Add variable to insert value from clipboard to a snippet #40153', function () {
let readTextResult: string | null | undefined;
const clipboardService = new class implements IClipboardService {
_serviceBrand: any;
readText(): any { return readTextResult; }
_throw = () => { throw new Error(); };
writeText = this._throw;
readFindText = this._throw;
writeFindText = this._throw;
writeResources = this._throw;
readResources = this._throw;
hasResources = this._throw;
};
let resolver = new ClipboardBasedVariableResolver(clipboardService, 1, 0);
assertVariableResolve(new ClipboardBasedVariableResolver(undefined, 1, 0), 'CLIPBOARD', undefined);
readTextResult = undefined;
assertVariableResolve(resolver, 'CLIPBOARD', undefined);
assertVariableResolve(new ClipboardBasedVariableResolver(null!, 1, 0), 'CLIPBOARD', undefined);
readTextResult = null;
assertVariableResolve(resolver, 'CLIPBOARD', undefined);
assertVariableResolve(new ClipboardBasedVariableResolver('', 1, 0), 'CLIPBOARD', undefined);
readTextResult = '';
assertVariableResolve(resolver, 'CLIPBOARD', undefined);
assertVariableResolve(new ClipboardBasedVariableResolver('foo', 1, 0), 'CLIPBOARD', 'foo');
readTextResult = 'foo';
assertVariableResolve(resolver, 'CLIPBOARD', 'foo');
assertVariableResolve(resolver, 'foo', undefined);
assertVariableResolve(resolver, 'cLIPBOARD', undefined);
assertVariableResolve(new ClipboardBasedVariableResolver('foo', 1, 0), 'foo', undefined);
assertVariableResolve(new ClipboardBasedVariableResolver('foo', 1, 0), 'cLIPBOARD', undefined);
});
test('Add variable to insert value from clipboard to a snippet #40153', function () {
let readTextResult: string;
let resolver: VariableResolver;
const clipboardService = new class implements IClipboardService {
_serviceBrand: any;
readText(): string { return readTextResult; }
_throw = () => { throw new Error(); };
writeText = this._throw;
readFindText = this._throw;
writeFindText = this._throw;
writeResources = this._throw;
readResources = this._throw;
hasResources = this._throw;
};
assertVariableResolve(new ClipboardBasedVariableResolver('line1', 1, 2), 'CLIPBOARD', 'line1');
assertVariableResolve(new ClipboardBasedVariableResolver('line1\nline2\nline3', 1, 2), 'CLIPBOARD', 'line1\nline2\nline3');
resolver = new ClipboardBasedVariableResolver(clipboardService, 1, 2);
readTextResult = 'line1';
assertVariableResolve(resolver, 'CLIPBOARD', 'line1');
readTextResult = 'line1\nline2\nline3';
assertVariableResolve(resolver, 'CLIPBOARD', 'line1\nline2\nline3');
readTextResult = 'line1\nline2';
assertVariableResolve(resolver, 'CLIPBOARD', 'line2');
readTextResult = 'line1\nline2';
resolver = new ClipboardBasedVariableResolver(clipboardService, 0, 2);
assertVariableResolve(resolver, 'CLIPBOARD', 'line1');
assertVariableResolve(new ClipboardBasedVariableResolver('line1\nline2', 1, 2), 'CLIPBOARD', 'line2');
resolver = new ClipboardBasedVariableResolver('line1\nline2', 0, 2);
assertVariableResolve(new ClipboardBasedVariableResolver('line1\nline2', 0, 2), 'CLIPBOARD', 'line1');
});

View file

@ -492,7 +492,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
) {
const kb = keybindingService.lookupKeybinding('editor.action.triggerSuggest');
const triggerKeybindingLabel = !kb ? '' : ` (${kb.getLabel()})`;
const markdownRenderer = new MarkdownRenderer(editor, modeService, openerService);
const markdownRenderer = this.toDispose.add(new MarkdownRenderer(editor, modeService, openerService));
this.isAuto = false;
this.focusedItem = null;

View file

@ -7,6 +7,8 @@ import * as nls from 'vs/nls';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { StopWatch } from 'vs/base/common/stopwatch';
import { StandardTokenType } from 'vs/editor/common/modes';
import { ITextModel } from 'vs/editor/common/model';
class ForceRetokenizeAction extends EditorAction {
constructor() {
@ -23,11 +25,57 @@ class ForceRetokenizeAction extends EditorAction {
return;
}
const model = editor.getModel();
model.flushTokens();
model.resetTokenization();
const sw = new StopWatch(true);
model.forceTokenization(model.getLineCount());
sw.stop();
console.log(`tokenization took ${sw.elapsed()}`);
if (!true) {
extractTokenTypes(model);
}
}
}
function extractTokenTypes(model: ITextModel): void {
const eolLength = model.getEOL().length;
let result: number[] = [];
let resultLen: number = 0;
let lastTokenType: StandardTokenType = StandardTokenType.Other;
let lastEndOffset: number = 0;
let offset = 0;
for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) {
const lineTokens = model.getLineTokens(lineNumber);
for (let i = 0, len = lineTokens.getCount(); i < len; i++) {
const tokenType = lineTokens.getStandardTokenType(i);
if (tokenType === StandardTokenType.Other) {
continue;
}
const startOffset = offset + lineTokens.getStartOffset(i);
const endOffset = offset + lineTokens.getEndOffset(i);
const length = endOffset - startOffset;
if (length === 0) {
continue;
}
if (lastTokenType === tokenType && lastEndOffset === startOffset) {
result[resultLen - 2] += length;
lastEndOffset += length;
continue;
}
result[resultLen++] = startOffset; // - lastEndOffset
result[resultLen++] = length;
result[resultLen++] = tokenType;
lastTokenType = tokenType;
lastEndOffset = endOffset;
}
offset += lineTokens.getLineContent().length + eolLength;
}
}

View file

@ -290,14 +290,11 @@ export interface EncodedTokensProvider {
}
function isEncodedTokensProvider(provider: TokensProvider | EncodedTokensProvider): provider is EncodedTokensProvider {
return provider['tokenizeEncoded'];
return 'tokenizeEncoded' in provider;
}
function isThenable<T>(obj: any): obj is Thenable<T> {
if (typeof obj.then === 'function') {
return true;
}
return false;
return obj && typeof obj.then === 'function';
}
/**

View file

@ -47,7 +47,7 @@ import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecora
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { getServices } from 'vs/platform/instantiation/common/extensions';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
export interface IEditorOverrideServices {
[index: string]: any;
@ -100,7 +100,7 @@ export module StaticServices {
let result = new ServiceCollection();
// make sure to add all services that use `registerSingleton`
for (const { id, descriptor } of getServices()) {
for (const [id, descriptor] of getSingletonServiceDescriptors()) {
result.set(id, descriptor);
}

View file

@ -2052,7 +2052,7 @@ suite('Editor Controller - Regression tests', () => {
getInitialState: () => NULL_STATE,
tokenize: undefined!,
tokenize2: (line: string, state: IState): TokenizationResult2 => {
return new TokenizationResult2(null!, state);
return new TokenizationResult2(new Uint32Array(0), state);
}
};

View file

@ -110,7 +110,9 @@ suite('ModelLinesTokens', () => {
for (let lineIndex = 0; lineIndex < initial.length; lineIndex++) {
const lineTokens = initial[lineIndex].tokens;
const lineTextLength = model.getLineMaxColumn(lineIndex + 1) - 1;
model._tokenization._tokensStore.setTokens(0, lineIndex, lineTextLength, TestToken.toTokens(lineTokens));
const tokens = TestToken.toTokens(lineTokens);
LineTokens.convertToEndOffset(tokens, lineTextLength);
model.setLineTokens(lineIndex + 1, tokens);
}
model.applyEdits(edits.map((ed) => ({
@ -441,14 +443,16 @@ suite('ModelLinesTokens', () => {
test('insertion on empty line', () => {
const model = new TextModel('some text', TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0));
model._tokenization._tokensStore.setTokens(0, 0, model.getLineMaxColumn(1) - 1, TestToken.toTokens([new TestToken(0, 1)]));
const tokens = TestToken.toTokens([new TestToken(0, 1)]);
LineTokens.convertToEndOffset(tokens, model.getLineMaxColumn(1) - 1);
model.setLineTokens(1, tokens);
model.applyEdits([{
range: new Range(1, 1, 1, 10),
text: ''
}]);
model._tokenization._tokensStore.setTokens(0, 0, model.getLineMaxColumn(1) - 1, new Uint32Array(0));
model.setLineTokens(1, new Uint32Array(0));
model.applyEdits([{
range: new Range(1, 1, 1, 1),
@ -660,7 +664,7 @@ suite('ModelLinesTokens', () => {
test('updates tokens on insertion 10', () => {
testLineEditTokens(
'',
null!,
[],
[{
startColumn: 1,
endColumn: 1,

View file

@ -29,7 +29,7 @@ suite('Editor Model - Model Modes 1', () => {
tokenize: undefined!,
tokenize2: (line: string, state: modes.IState): TokenizationResult2 => {
calledFor.push(line.charAt(0));
return new TokenizationResult2(null!, state);
return new TokenizationResult2(new Uint32Array(0), state);
}
};
@ -183,7 +183,7 @@ suite('Editor Model - Model Modes 2', () => {
tokenize2: (line: string, state: modes.IState): TokenizationResult2 => {
calledFor.push(line);
(<ModelState2>state).prevLineContent = line;
return new TokenizationResult2(null!, state);
return new TokenizationResult2(new Uint32Array(0), state);
}
};

2
src/vs/monaco.d.ts vendored
View file

@ -989,7 +989,7 @@ declare namespace monaco.editor {
/**
* An object that can be used by the web worker to make calls back to the main thread.
*/
host?: object;
host?: any;
}
/**

View file

@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { URI } from 'vs/base/common/uri';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
export class BrowserClipboardService implements IClipboardService {
_serviceBrand: ServiceIdentifier<IClipboardService>;
private _internalResourcesClipboard: URI[] | undefined;
async writeText(text: string, type?: string): Promise<void> {
return navigator.clipboard.writeText(text);
}
async readText(type?: string): Promise<string> {
return navigator.clipboard.readText();
}
readTextSync(): string | undefined {
return undefined;
}
readFindText(): string {
// @ts-ignore
return undefined;
}
writeFindText(text: string): void { }
writeResources(resources: URI[]): void {
this._internalResourcesClipboard = resources;
}
readResources(): URI[] {
return this._internalResourcesClipboard || [];
}
hasResources(): boolean {
return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0;
}
}
registerSingleton(IClipboardService, BrowserClipboardService, true);

View file

@ -15,12 +15,14 @@ export interface IClipboardService {
/**
* Writes text to the system clipboard.
*/
writeText(text: string, type?: string): void;
writeText(text: string, type?: string): Promise<void>;
/**
* Reads the content of the clipboard in plain text
*/
readText(type?: string): string;
readText(type?: string): Promise<string>;
readTextSync(): string | undefined;
/**
* Reads text from the system find pasteboard.

View file

@ -14,14 +14,18 @@ export class ClipboardService implements IClipboardService {
_serviceBrand: any;
writeText(text: string, type?: 'selection' | 'clipboard'): void {
async writeText(text: string, type?: 'selection' | 'clipboard'): Promise<void> {
clipboard.writeText(text, type);
}
readText(type?: 'selection' | 'clipboard'): string {
async readText(type?: 'selection' | 'clipboard'): Promise<string> {
return clipboard.readText(type);
}
readTextSync(): string {
return clipboard.readText();
}
readFindText(): string {
if (isMacintosh) {
return clipboard.readFindText();

View file

@ -14,7 +14,7 @@ import { TernarySearchTree } from 'vs/base/common/map';
import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays';
import { getBaseLabel } from 'vs/base/common/labels';
import { ILogService } from 'vs/platform/log/common/log';
import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream, writeableBufferStream, VSBufferWriteableStream } from 'vs/base/common/buffer';
import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream, writeableBufferStream, VSBufferWriteableStream, isVSBufferReadableStream } from 'vs/base/common/buffer';
import { Queue } from 'vs/base/common/async';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
@ -264,7 +264,7 @@ export class FileService extends Disposable implements IFileService {
//#region File Reading/Writing
async createFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
async createFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
// validate overwrite
const overwrite = !!(options && options.overwrite);
@ -273,7 +273,7 @@ export class FileService extends Disposable implements IFileService {
}
// do write into file (this will create it too)
const fileStat = await this.writeFile(resource, bufferOrReadable);
const fileStat = await this.writeFile(resource, bufferOrReadableOrStream);
// events
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
@ -281,7 +281,7 @@ export class FileService extends Disposable implements IFileService {
return fileStat;
}
async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
async writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
const provider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(resource));
try {
@ -296,12 +296,12 @@ export class FileService extends Disposable implements IFileService {
// write file: buffered
if (hasOpenReadWriteCloseCapability(provider)) {
await this.doWriteBuffered(provider, resource, bufferOrReadable instanceof VSBuffer ? bufferToReadable(bufferOrReadable) : bufferOrReadable);
await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream);
}
// write file: unbuffered
else {
await this.doWriteUnbuffered(provider, resource, bufferOrReadable);
await this.doWriteUnbuffered(provider, resource, bufferOrReadableOrStream);
}
} catch (error) {
throw new FileOperationError(localize('err.write', "Unable to write file ({0})", this.ensureError(error).toString()), toFileOperationResult(error), options);
@ -857,29 +857,66 @@ export class FileService extends Disposable implements IFileService {
return isPathCaseSensitive ? resource.toString() : resource.toString().toLowerCase();
}
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, readable: VSBufferReadable): Promise<void> {
return this.ensureWriteQueue(provider, resource).queue(() => this.doWriteBufferedQueued(provider, resource, readable));
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, readableOrStream: VSBufferReadable | VSBufferReadableStream): Promise<void> {
return this.ensureWriteQueue(provider, resource).queue(async () => {
// open handle
const handle = await provider.open(resource, { create: true });
// write into handle until all bytes from buffer have been written
try {
if (isVSBufferReadableStream(readableOrStream)) {
await this.doWriteStreamBufferedQueued(provider, handle, readableOrStream);
} else {
await this.doWriteReadableBufferedQueued(provider, handle, readableOrStream);
}
} catch (error) {
throw this.ensureError(error);
} finally {
// close handle always
await provider.close(handle);
}
});
}
private async doWriteBufferedQueued(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, readable: VSBufferReadable): Promise<void> {
// open handle
const handle = await provider.open(resource, { create: true });
// write into handle until all bytes from buffer have been written
try {
private doWriteStreamBufferedQueued(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, handle: number, stream: VSBufferReadableStream): Promise<void> {
return new Promise((resolve, reject) => {
let posInFile = 0;
let chunk: VSBuffer | null;
while (chunk = readable.read()) {
await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0);
stream.on('data', async chunk => {
// pause stream to perform async write operation
stream.pause();
try {
await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0);
} catch (error) {
return reject(error);
}
posInFile += chunk.byteLength;
}
} catch (error) {
throw this.ensureError(error);
} finally {
await provider.close(handle);
// resume stream now that we have successfully written
// run this on the next tick to prevent increasing the
// execution stack because resume() may call the event
// handler again before finishing.
setTimeout(() => stream.resume());
});
stream.on('error', error => reject(error));
stream.on('end', () => resolve());
});
}
private async doWriteReadableBufferedQueued(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, handle: number, readable: VSBufferReadable): Promise<void> {
let posInFile = 0;
let chunk: VSBuffer | null;
while (chunk = readable.read()) {
await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0);
posInFile += chunk.byteLength;
}
}
@ -891,16 +928,18 @@ export class FileService extends Disposable implements IFileService {
}
}
private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable): Promise<void> {
return this.ensureWriteQueue(provider, resource).queue(() => this.doWriteUnbufferedQueued(provider, resource, bufferOrReadable));
private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise<void> {
return this.ensureWriteQueue(provider, resource).queue(() => this.doWriteUnbufferedQueued(provider, resource, bufferOrReadableOrStream));
}
private async doWriteUnbufferedQueued(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable): Promise<void> {
private async doWriteUnbufferedQueued(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise<void> {
let buffer: VSBuffer;
if (bufferOrReadable instanceof VSBuffer) {
buffer = bufferOrReadable;
if (bufferOrReadableOrStream instanceof VSBuffer) {
buffer = bufferOrReadableOrStream;
} else if (isVSBufferReadableStream(bufferOrReadableOrStream)) {
buffer = await streamToBuffer(bufferOrReadableOrStream);
} else {
buffer = readableToBuffer(bufferOrReadable);
buffer = readableToBuffer(bufferOrReadableOrStream);
}
return provider.writeFile(resource, buffer.buffer, { create: true, overwrite: true });

View file

@ -6,7 +6,6 @@
import { sep } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import * as glob from 'vs/base/common/glob';
import { isLinux } from 'vs/base/common/platform';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { startsWithIgnoreCase } from 'vs/base/common/strings';
@ -105,7 +104,7 @@ export interface IFileService {
/**
* Updates the content replacing its previous value.
*/
writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise<IFileStatWithMetadata>;
writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata>;
/**
* Moves the file/folder to a new path identified by the resource.
@ -127,7 +126,7 @@ export interface IFileService {
*
* The optional parameter content can be used as value to fill into the new file.
*/
createFile(resource: URI, bufferOrReadable?: VSBuffer | VSBufferReadable, options?: ICreateFileOptions): Promise<IFileStatWithMetadata>;
createFile(resource: URI, bufferOrReadableOrStream?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: ICreateFileOptions): Promise<IFileStatWithMetadata>;
/**
* Creates a new folder with the given path. The returned promise
@ -429,10 +428,10 @@ export class FileChangesEvent {
// For deleted also return true when deleted folder is parent of target path
if (change.type === FileChangeType.DELETED) {
return isEqualOrParent(resource, change.resource, !isLinux /* ignorecase */);
return isEqualOrParent(resource, change.resource);
}
return isEqual(resource, change.resource, !isLinux /* ignorecase */);
return isEqual(resource, change.resource);
});
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { shell } from 'electron';
import { DiskFileSystemProvider as NodeDiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
import { DiskFileSystemProvider as NodeDiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';

View file

@ -3,25 +3,25 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { mkdir, open, close, read, write, fdatasync } from 'fs';
import { mkdir, open, close, read, write, fdatasync, Dirent, Stats } from 'fs';
import { promisify } from 'util';
import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { statLink, readdir, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists } from 'vs/base/node/pfs';
import { statLink, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists, readdirWithFileTypes } from 'vs/base/node/pfs';
import { normalize, basename, dirname } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/extpath';
import { retry, ThrottledDelayer } from 'vs/base/common/async';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { localize } from 'vs/nls';
import { IDiskFileChange, toFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { FileWatcher as UnixWatcherService } from 'vs/workbench/services/files/node/watcher/unix/watcherService';
import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files/node/watcher/win32/watcherService';
import { FileWatcher as NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/watcherService';
import { FileWatcher as NodeJSWatcherService } from 'vs/workbench/services/files/node/watcher/nodejs/watcherService';
import { IDiskFileChange, toFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { FileWatcher as UnixWatcherService } from 'vs/platform/files/node/watcher/unix/watcherService';
import { FileWatcher as WindowsWatcherService } from 'vs/platform/files/node/watcher/win32/watcherService';
import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService';
import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService';
export interface IWatcherOptions {
pollingInterval?: number;
@ -62,15 +62,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
try {
const { stat, isSymbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly
let type: number;
if (isSymbolicLink) {
type = FileType.SymbolicLink | (stat.isDirectory() ? FileType.Directory : FileType.File);
} else {
type = stat.isFile() ? FileType.File : stat.isDirectory() ? FileType.Directory : FileType.Unknown;
}
return {
type,
type: this.toType(stat, isSymbolicLink),
ctime: stat.ctime.getTime(),
mtime: stat.mtime.getTime(),
size: stat.size
@ -82,13 +75,19 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
async readdir(resource: URI): Promise<[string, FileType][]> {
try {
const children = await readdir(this.toFilePath(resource));
const children = await readdirWithFileTypes(this.toFilePath(resource));
const result: [string, FileType][] = [];
await Promise.all(children.map(async child => {
try {
const stat = await this.stat(joinPath(resource, child));
result.push([child, stat.type]);
let type: FileType;
if (child.isSymbolicLink()) {
type = (await this.stat(joinPath(resource, child.name))).type; // always resolve target the link points to if any
} else {
type = this.toType(child);
}
result.push([child.name, type]);
} catch (error) {
this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied
}
@ -100,6 +99,14 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
}
}
private toType(entry: Stats | Dirent, isSymbolicLink = entry.isSymbolicLink()): FileType {
if (isSymbolicLink) {
return FileType.SymbolicLink | (entry.isDirectory() ? FileType.Directory : FileType.File);
}
return entry.isFile() ? FileType.File : entry.isDirectory() ? FileType.Directory : FileType.Unknown;
}
//#endregion
//#region File Reading/Writing

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { Disposable } from 'vs/base/common/lifecycle';
import { statLink } from 'vs/base/node/pfs';
import { realpath } from 'vs/base/node/extpath';

View file

@ -7,9 +7,9 @@ import * as glob from 'vs/base/common/glob';
import * as extpath from 'vs/base/common/extpath';
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import * as nsfw from 'nsfw';
import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/nsfw/watcher';
import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/platform/files/node/watcher/nsfw/watcher';
import { ThrottledDelayer } from 'vs/base/common/async';
import { FileChangeType } from 'vs/platform/files/common/files';
import { normalizeNFC } from 'vs/base/common/normalization';

View file

@ -6,8 +6,8 @@
import * as assert from 'assert';
import * as platform from 'vs/base/common/platform';
import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService';
import { IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher';
import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatcherService';
import { IWatcherRequest } from 'vs/platform/files/node/watcher/nsfw/watcher';
class TestNsfwWatcherService extends NsfwWatcherService {
public normalizeRoots(roots: string[]): string[] {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
export interface IWatcherRequest {
path: string;

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { WatcherChannel } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc';
import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService';
import { WatcherChannel } from 'vs/platform/files/node/watcher/nsfw/watcherIpc';
import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatcherService';
const server = new Server('watcher');
const service = new NsfwWatcherService();

View file

@ -6,7 +6,7 @@
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher';
import { Event } from 'vs/base/common/event';
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
export class WatcherChannel implements IServerChannel {

View file

@ -5,10 +5,10 @@
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { WatcherChannelClient } from 'vs/platform/files/node/watcher/nsfw/watcherIpc';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher';
import { IWatcherRequest } from 'vs/platform/files/node/watcher/nsfw/watcher';
import { getPathFromAmdModule } from 'vs/base/common/amd';
export class FileWatcher extends Disposable {
@ -39,7 +39,7 @@ export class FileWatcher extends Disposable {
serverName: 'File Watcher (nsfw)',
args: ['--type=watcherService'],
env: {
AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/nsfw/watcherApp',
AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/nsfw/watcherApp',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: 'true' // transmit console logs from server to client
}

View file

@ -14,8 +14,8 @@ import { ThrottledDelayer } from 'vs/base/common/async';
import { normalizeNFC } from 'vs/base/common/normalization';
import { realcaseSync } from 'vs/base/node/extpath';
import { isMacintosh, isLinux } from 'vs/base/common/platform';
import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher';
import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/platform/files/node/watcher/unix/watcher';
import { Emitter, Event } from 'vs/base/common/event';
interface IWatcher {

View file

@ -11,7 +11,7 @@ import { normalizeRoots, ChokidarWatcherService } from '../chokidarWatcherServic
import { IWatcherRequest } from '../watcher';
import * as platform from 'vs/base/common/platform';
import { Delayer } from 'vs/base/common/async';
import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange } from 'vs/platform/files/node/watcher/watcher';
import { FileChangeType } from 'vs/platform/files/common/files';
function newRequest(basePath: string, ignored: string[] = []): IWatcherRequest {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
export interface IWatcherRequest {
path: string;

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { WatcherChannel } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc';
import { ChokidarWatcherService } from 'vs/workbench/services/files/node/watcher/unix/chokidarWatcherService';
import { WatcherChannel } from 'vs/platform/files/node/watcher/unix/watcherIpc';
import { ChokidarWatcherService } from 'vs/platform/files/node/watcher/unix/chokidarWatcherService';
const server = new Server('watcher');
const service = new ChokidarWatcherService();

View file

@ -6,7 +6,7 @@
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher';
import { Event } from 'vs/base/common/event';
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
export class WatcherChannel implements IServerChannel {

View file

@ -5,10 +5,10 @@
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { WatcherChannelClient } from 'vs/platform/files/node/watcher/unix/watcherIpc';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher';
import { IWatcherRequest, IWatcherOptions } from 'vs/platform/files/node/watcher/unix/watcher';
import { getPathFromAmdModule } from 'vs/base/common/amd';
export class FileWatcher extends Disposable {
@ -40,7 +40,7 @@ export class FileWatcher extends Disposable {
serverName: 'File Watcher (chokidar)',
args: ['--type=watcherService'],
env: {
AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/unix/watcherApp',
AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/unix/watcherApp',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: 'true' // transmit console logs from server to client
}

View file

@ -7,7 +7,7 @@ import * as cp from 'child_process';
import { FileChangeType } from 'vs/platform/files/common/files';
import * as decoder from 'vs/base/node/decoder';
import * as glob from 'vs/base/common/glob';
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { getPathFromAmdModule } from 'vs/base/common/amd';
export class OutOfProcessWin32FolderWatcher {
@ -50,7 +50,7 @@ export class OutOfProcessWin32FolderWatcher {
args.push('-verbose');
}
this.handle = cp.spawn(getPathFromAmdModule(require, 'vs/workbench/services/files/node/watcher/win32/CodeHelper.exe'), args);
this.handle = cp.spawn(getPathFromAmdModule(require, 'vs/platform/files/node/watcher/win32/CodeHelper.exe'), args);
const stdoutLineDecoder = new decoder.LineDecoder();

View file

@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher';
import { OutOfProcessWin32FolderWatcher } from 'vs/workbench/services/files/node/watcher/win32/csharpWatcherService';
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { OutOfProcessWin32FolderWatcher } from 'vs/platform/files/node/watcher/win32/csharpWatcherService';
import { posix } from 'vs/base/common/path';
import { rtrim, endsWith } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';

View file

@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { FileService } from 'vs/workbench/services/files/common/fileService';
import { FileService } from 'vs/platform/files/common/fileService';
import { URI } from 'vs/base/common/uri';
import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { NullFileSystemProvider } from 'vs/workbench/test/workbenchTestServices';
import { NullLogService } from 'vs/platform/log/common/log';
import { timeout } from 'vs/base/common/async';
import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider';
suite('File Service', () => {

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { FileSystemProviderCapabilities, IFileSystemProvider, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileChange } from 'vs/platform/files/common/files';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
export class NullFileSystemProvider implements IFileSystemProvider {
capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly;
onDidChangeCapabilities: Event<void> = Event.None;
onDidChangeFile: Event<IFileChange[]> = Event.None;
constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { }
watch(resource: URI, opts: IWatchOptions): IDisposable { return this.disposableFactory(); }
stat(resource: URI): Promise<IStat> { return Promise.resolve(undefined!); }
mkdir(resource: URI): Promise<void> { return Promise.resolve(undefined!); }
readdir(resource: URI): Promise<[string, FileType][]> { return Promise.resolve(undefined!); }
delete(resource: URI, opts: FileDeleteOptions): Promise<void> { return Promise.resolve(undefined!); }
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return Promise.resolve(undefined!); }
copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return Promise.resolve(undefined!); }
readFile?(resource: URI): Promise<Uint8Array> { return Promise.resolve(undefined!); }
writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> { return Promise.resolve(undefined!); }
open?(resource: URI, opts: FileOpenOptions): Promise<number> { return Promise.resolve(undefined!); }
close?(fd: number): Promise<void> { return Promise.resolve(undefined!); }
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return Promise.resolve(undefined!); }
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return Promise.resolve(undefined!); }
}

View file

@ -5,22 +5,22 @@
import * as assert from 'assert';
import { tmpdir } from 'os';
import { FileService } from 'vs/workbench/services/files/common/fileService';
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { generateUuid } from 'vs/base/common/uuid';
import { join, basename, dirname, posix } from 'vs/base/common/path';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { copy, rimraf, symlink, RimRafMode, rimrafSync } from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'fs';
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream } from 'fs';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { NullLogService } from 'vs/platform/log/common/log';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isEqual } from 'vs/base/common/resources';
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
import { VSBuffer, VSBufferReadable, toVSBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream } from 'vs/base/common/buffer';
function getByName(root: IFileStat, name: string): IFileStat | null {
if (root.children === undefined) {
@ -1326,12 +1326,24 @@ suite('Disk File Service', () => {
});
test('createFile', async () => {
assertCreateFile(contents => VSBuffer.fromString(contents));
});
test('createFile (readable)', async () => {
assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents)));
});
test('createFile (stream)', async () => {
assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents)));
});
async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise<void> {
let event: FileOperationEvent;
disposables.add(service.onAfterOperation(e => event = e));
const contents = 'Hello World';
const resource = URI.file(join(testDir, 'test.txt'));
const fileStat = await service.createFile(resource, VSBuffer.fromString(contents));
const fileStat = await service.createFile(resource, converter(contents));
assert.equal(fileStat.name, 'test.txt');
assert.equal(existsSync(fileStat.resource.fsPath), true);
assert.equal(readFileSync(fileStat.resource.fsPath), contents);
@ -1340,7 +1352,7 @@ suite('Disk File Service', () => {
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.CREATE);
assert.equal(event!.target!.resource.fsPath, resource.fsPath);
});
}
test('createFile (does not overwrite by default)', async () => {
const contents = 'Hello World';
@ -1545,6 +1557,54 @@ suite('Disk File Service', () => {
assert.equal(readFileSync(resource.fsPath), newContent);
});
test('writeFile (stream) - buffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
const source = URI.file(join(testDir, 'small.txt'));
const target = URI.file(join(testDir, 'small-copy.txt'));
const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath)));
assert.equal(fileStat.name, 'small-copy.txt');
assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString());
});
test('writeFile (large file - stream) - buffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
const source = URI.file(join(testDir, 'lorem.txt'));
const target = URI.file(join(testDir, 'lorem-copy.txt'));
const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath)));
assert.equal(fileStat.name, 'lorem-copy.txt');
assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString());
});
test('writeFile (stream) - unbuffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
const source = URI.file(join(testDir, 'small.txt'));
const target = URI.file(join(testDir, 'small-copy.txt'));
const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath)));
assert.equal(fileStat.name, 'small-copy.txt');
assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString());
});
test('writeFile (large file - stream) - unbuffered', async () => {
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
const source = URI.file(join(testDir, 'lorem.txt'));
const target = URI.file(join(testDir, 'lorem-copy.txt'));
const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath)));
assert.equal(fileStat.name, 'lorem-copy.txt');
assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString());
});
test('writeFile (file is created including parents)', async () => {
const resource = URI.file(join(testDir, 'other', 'newfile.txt'));

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