mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
Merge branch 'master' into joao/splitview
This commit is contained in:
commit
28aec6305b
2
.github/classifier.yml
vendored
2
.github/classifier.yml
vendored
|
@ -63,7 +63,7 @@
|
|||
assignLabel: false
|
||||
},
|
||||
file-explorer: {
|
||||
assignees: [ isidorn ],
|
||||
assignees: [ ],
|
||||
assignLabel: false
|
||||
},
|
||||
file-glob: [],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
test/**
|
||||
src/**
|
||||
tsconfig.json
|
||||
extension.webpack.config.js
|
||||
cgmanifest.json
|
||||
|
|
17
extensions/python/extension.webpack.config.js
Normal file
17
extensions/python/extension.webpack.config.js
Normal 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'
|
||||
}
|
||||
});
|
|
@ -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)
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
"module": "amd",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"target": "es5",
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitReturns": true,
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"lib": [
|
||||
"dom",
|
||||
"es5",
|
||||
"es2015.iterable"
|
||||
"es2015.iterable",
|
||||
"webworker"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
@ -19,4 +20,4 @@
|
|||
"exclude": [
|
||||
"./typings/require-monaco.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -43,7 +43,6 @@ bootstrapWindow.load([
|
|||
});
|
||||
|
||||
/**
|
||||
* // configuration: IWindowConfiguration
|
||||
* @param {{
|
||||
* partsSplashPath?: string,
|
||||
* highContrast?: boolean,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -783,7 +783,7 @@ export interface ITextModel {
|
|||
* Flush all tokenization state.
|
||||
* @internal
|
||||
*/
|
||||
flushTokens(): void;
|
||||
resetTokenization(): void;
|
||||
|
||||
/**
|
||||
* Force tokenization information for `lineNumber` to be accurate.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
334
src/vs/editor/common/model/tokensStore.ts
Normal file
334
src/vs/editor/common/model/tokensStore.ts
Normal 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
|
||||
}
|
|
@ -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[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ class CodeActionOracle extends Disposable {
|
|||
}
|
||||
}
|
||||
}
|
||||
return selection ? selection : undefined;
|
||||
return selection;
|
||||
}
|
||||
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): TriggeredCodeAction {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
2
src/vs/monaco.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
49
src/vs/platform/clipboard/browser/clipboardService.ts
Normal file
49
src/vs/platform/clipboard/browser/clipboardService.ts
Normal 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);
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 });
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
|
@ -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
|
|
@ -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';
|
|
@ -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';
|
|
@ -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[] {
|
|
@ -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;
|
|
@ -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();
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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;
|
|
@ -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();
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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();
|
||||
|
|
@ -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';
|
|
@ -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', () => {
|
||||
|
33
src/vs/platform/files/test/common/nullFileSystemProvider.ts
Normal file
33
src/vs/platform/files/test/common/nullFileSystemProvider.ts
Normal 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!); }
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue