Merge remote-tracking branch 'origin/master' into alex/semantic-exploration

This commit is contained in:
Alexandru Dima 2019-11-18 08:59:14 +01:00
commit 41be9a6142
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
240 changed files with 4369 additions and 3069 deletions

View file

@ -6,7 +6,7 @@ steps:
inputs:
keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules'
vstsFeed: '$(ArtifactFeed)'
vstsFeed: 'vscode-build-cache'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: "1.x"
@ -18,7 +18,7 @@ steps:
inputs:
keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules'
vstsFeed: '$(ArtifactFeed)'
vstsFeed: 'vscode-build-cache'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
yarn electron x64

View file

@ -14,7 +14,7 @@ steps:
inputs:
keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules'
vstsFeed: '$(ArtifactFeed)'
vstsFeed: 'vscode-build-cache'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: "1.x"
@ -26,7 +26,7 @@ steps:
inputs:
keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules'
vstsFeed: '$(ArtifactFeed)'
vstsFeed: 'vscode-build-cache'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
yarn electron x64

View file

@ -13,7 +13,7 @@ steps:
inputs:
keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules'
vstsFeed: '$(ArtifactFeed)'
vstsFeed: 'vscode-build-cache'
- powershell: |
yarn --frozen-lockfile
env:
@ -24,7 +24,7 @@ steps:
inputs:
keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules'
vstsFeed: '$(ArtifactFeed)'
vstsFeed: 'vscode-build-cache'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- powershell: |
yarn electron

View file

@ -303,7 +303,7 @@
document.body.classList.remove('loading');
});
image.src = decodeURI(settings.src);
image.src = settings.src;
window.addEventListener('message', e => {
switch (e.data.type) {

View file

@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Disposable } from './dispose';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
class BinarySize {
static readonly KB = 1024;
static readonly MB = BinarySize.KB * BinarySize.KB;
static readonly GB = BinarySize.MB * BinarySize.KB;
static readonly TB = BinarySize.GB * BinarySize.KB;
static formatSize(size: number): string {
if (size < BinarySize.KB) {
return localize('sizeB', "{0}B", size);
}
if (size < BinarySize.MB) {
return localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2));
}
if (size < BinarySize.GB) {
return localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2));
}
if (size < BinarySize.TB) {
return localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2));
}
return localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2));
}
}
export class BinarySizeStatusBarEntry extends Disposable {
private readonly _entry: vscode.StatusBarItem;
private _showingOwner: string | undefined;
constructor() {
super();
this._entry = this._register(vscode.window.createStatusBarItem({
id: 'imagePreview.binarySize',
name: localize('sizeStatusBar.name', "Image Binary Size"),
alignment: vscode.StatusBarAlignment.Right,
priority: 100,
}));
}
public show(owner: string, size: number | undefined) {
this._showingOwner = owner;
if (typeof size === 'number') {
this._entry.text = BinarySize.formatSize(size);
this._entry.show();
} else {
this.hide(owner);
}
}
public hide(owner: string) {
if (owner === this._showingOwner) {
this._entry.hide();
this._showingOwner = undefined;
}
}
}

View file

@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import { PreviewManager } from './preview';
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
import { ZoomStatusBarEntry } from './zoomStatusBarEntry';
export function activate(context: vscode.ExtensionContext) {
@ -14,10 +15,13 @@ export function activate(context: vscode.ExtensionContext) {
const sizeStatusBarEntry = new SizeStatusBarEntry();
context.subscriptions.push(sizeStatusBarEntry);
const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry();
context.subscriptions.push(binarySizeStatusBarEntry);
const zoomStatusBarEntry = new ZoomStatusBarEntry();
context.subscriptions.push(zoomStatusBarEntry);
const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, zoomStatusBarEntry);
const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
context.subscriptions.push(vscode.window.registerWebviewEditorProvider(
PreviewManager.viewType,

View file

@ -8,6 +8,7 @@ import * as nls from 'vscode-nls';
import { Disposable } from './dispose';
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry';
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
const localize = nls.loadMessageBundle();
@ -22,6 +23,7 @@ export class PreviewManager {
constructor(
private readonly extensionRoot: vscode.Uri,
private readonly sizeStatusBarEntry: SizeStatusBarEntry,
private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
) { }
@ -29,7 +31,7 @@ export class PreviewManager {
resource: vscode.Uri,
webviewEditor: vscode.WebviewPanel,
): vscode.WebviewEditorCapabilities {
const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.zoomStatusBarEntry);
const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry);
this._previews.add(preview);
this.setActivePreview(preview);
@ -72,6 +74,7 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
private _previewState = PreviewState.Visible;
private _imageSize: string | undefined;
private _imageBinarySize: number | undefined;
private _imageZoom: Scale | undefined;
constructor(
@ -79,6 +82,7 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
private readonly resource: vscode.Uri,
private readonly webviewEditor: vscode.WebviewPanel,
private readonly sizeStatusBarEntry: SizeStatusBarEntry,
private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
) {
super();
@ -125,6 +129,7 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
this._register(webviewEditor.onDidDispose(() => {
if (this._previewState === PreviewState.Active) {
this.sizeStatusBarEntry.hide(this.id);
this.binarySizeStatusBarEntry.hide(this.id);
this.zoomStatusBarEntry.hide(this.id);
}
this._previewState = PreviewState.Disposed;
@ -142,6 +147,11 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
}
}));
vscode.workspace.fs.stat(resource).then(({ size }) => {
this._imageBinarySize = size;
this.update();
});
this.render();
this.update();
this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
@ -173,10 +183,12 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
if (this.webviewEditor.active) {
this._previewState = PreviewState.Active;
this.sizeStatusBarEntry.show(this.id, this._imageSize || '');
this.binarySizeStatusBarEntry.show(this.id, this._imageBinarySize);
this.zoomStatusBarEntry.show(this.id, this._imageZoom || 'fit');
} else {
if (this._previewState === PreviewState.Active) {
this.sizeStatusBarEntry.hide(this.id);
this.binarySizeStatusBarEntry.hide(this.id);
this.zoomStatusBarEntry.hide(this.id);
}
this._previewState = PreviewState.Visible;
@ -219,18 +231,18 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
private getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string) {
switch (resource.scheme) {
case 'data':
return encodeURI(resource.toString(true));
return resource.toString(true);
case 'git':
// Show blank image
return encodeURI('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg==');
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg==';
default:
// Avoid adding cache busting if there is already a query string
if (resource.query) {
return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true));
return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString());
}
return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true) + `?version=${version}`);
return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString() + `?version=${version}`);
}
}
@ -248,10 +260,10 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
async hotExit() { }
async applyEdits(_edits: any[]) { }
async undoEdits(edits: any[]) { console.log('undo', edits); }
async applyEdits(edits: any[]) { console.log('apply', edits); }
//#endregion
public test_makeEdit() {

View file

@ -36,27 +36,31 @@ function mode2ScriptKind(mode: string): 'TS' | 'TSX' | 'JS' | 'JSX' | undefined
return undefined;
}
const enum BufferOperationType { Close, Open, Change }
class CloseOperation {
readonly type = 'close';
readonly type = BufferOperationType.Close;
constructor(
public readonly args: string
) { }
}
class OpenOperation {
readonly type = 'open';
readonly type = BufferOperationType.Open;
constructor(
public readonly args: Proto.OpenRequestArgs
) { }
}
class ChangeOperation {
readonly type = 'change';
readonly type = BufferOperationType.Change;
constructor(
public readonly args: Proto.FileCodeEdits
) { }
}
type BufferOperation = CloseOperation | OpenOperation | ChangeOperation;
/**
* Manages synchronization of buffers with the TS server.
*
@ -64,7 +68,7 @@ class ChangeOperation {
*/
class BufferSynchronizer {
private readonly _pending = new ResourceMap<CloseOperation | OpenOperation | ChangeOperation>();
private readonly _pending = new ResourceMap<BufferOperation>();
constructor(
private readonly client: ITypeScriptServiceClient
@ -72,9 +76,7 @@ class BufferSynchronizer {
public open(resource: vscode.Uri, args: Proto.OpenRequestArgs) {
if (this.supportsBatching) {
this.updatePending(resource, pending => {
pending.set(resource, new OpenOperation(args));
});
this.updatePending(resource, new OpenOperation(args));
} else {
this.client.executeWithoutWaitingForResponse('open', args);
}
@ -82,9 +84,7 @@ class BufferSynchronizer {
public close(resource: vscode.Uri, filepath: string) {
if (this.supportsBatching) {
this.updatePending(resource, pending => {
pending.set(resource, new CloseOperation(filepath));
});
this.updatePending(resource, new CloseOperation(filepath));
} else {
const args: Proto.FileRequestArgs = { file: filepath };
this.client.executeWithoutWaitingForResponse('close', args);
@ -97,16 +97,14 @@ class BufferSynchronizer {
}
if (this.supportsBatching) {
this.updatePending(resource, pending => {
pending.set(resource, new ChangeOperation({
fileName: filepath,
textChanges: events.map((change): Proto.CodeEdit => ({
newText: change.text,
start: typeConverters.Position.toLocation(change.range.start),
end: typeConverters.Position.toLocation(change.range.end),
})).reverse(), // Send the edits end-of-document to start-of-document order
}));
});
this.updatePending(resource, new ChangeOperation({
fileName: filepath,
textChanges: events.map((change): Proto.CodeEdit => ({
newText: change.text,
start: typeConverters.Position.toLocation(change.range.start),
end: typeConverters.Position.toLocation(change.range.end),
})).reverse(), // Send the edits end-of-document to start-of-document order
}));
} else {
for (const { range, text } of events) {
const args: Proto.ChangeRequestArgs = {
@ -143,9 +141,9 @@ class BufferSynchronizer {
const changedFiles: Proto.FileCodeEdits[] = [];
for (const change of this._pending.values) {
switch (change.type) {
case 'change': changedFiles.push(change.args); break;
case 'open': openFiles.push(change.args); break;
case 'close': closedFiles.push(change.args); break;
case BufferOperationType.Change: changedFiles.push(change.args); break;
case BufferOperationType.Open: openFiles.push(change.args); break;
case BufferOperationType.Close: closedFiles.push(change.args); break;
}
}
this.client.execute('updateOpen', { changedFiles, closedFiles, openFiles }, nulToken, { nonRecoverable: true });
@ -157,12 +155,23 @@ class BufferSynchronizer {
return this.client.apiVersion.gte(API.v340);
}
private updatePending(resource: vscode.Uri, f: (pending: ResourceMap<CloseOperation | OpenOperation | ChangeOperation>) => void): void {
private updatePending(resource: vscode.Uri, op: BufferOperation): void {
switch (op.type) {
case BufferOperationType.Close:
const existing = this._pending.get(resource);
switch (existing?.type) {
case BufferOperationType.Open:
this._pending.delete(resource);
return; // Open then close. No need to do anything
}
break;
}
if (this._pending.has(resource)) {
// we saw this file before, make sure we flush before working with it again
this.flush();
}
f(this._pending);
this._pending.set(resource, op);
}
}

View file

@ -56,6 +56,7 @@
"xterm": "4.3.0-beta17",
"xterm-addon-search": "0.4.0-beta4",
"xterm-addon-web-links": "0.2.1",
"xterm-addon-webgl": "0.4.0-beta6",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},

View file

@ -23,6 +23,7 @@
"xterm": "4.3.0-beta17",
"xterm-addon-search": "0.4.0-beta4",
"xterm-addon-web-links": "0.2.1",
"xterm-addon-webgl": "0.4.0-beta6",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},

View file

@ -7,6 +7,7 @@
"vscode-textmate": "^4.3.0",
"xterm": "4.3.0-beta17",
"xterm-addon-search": "0.4.0-beta4",
"xterm-addon-web-links": "0.2.1"
"xterm-addon-web-links": "0.2.1",
"xterm-addon-webgl": "0.4.0-beta6"
}
}

View file

@ -41,6 +41,11 @@ xterm-addon-web-links@0.2.1:
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2"
integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ==
xterm-addon-webgl@0.4.0-beta6:
version "0.4.0-beta6"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta6.tgz#23d152a0467d8b1f96ab3da7ac9a49bfa0b08c98"
integrity sha512-gtM8XtRyrNFCtxHBIU3pqTlBeAi5VOoymJAXKQQ7RsHEVJX79OTk1dQ9Q6Ow14+REGwQU/zFECV050jbLTfvrQ==
xterm@4.3.0-beta17:
version "4.3.0-beta17"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta17.tgz#c038cc00cb5be33d2a5f083255c329d9ed186565"

View file

@ -428,6 +428,11 @@ xterm-addon-web-links@0.2.1:
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2"
integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ==
xterm-addon-webgl@0.4.0-beta6:
version "0.4.0-beta6"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta6.tgz#23d152a0467d8b1f96ab3da7ac9a49bfa0b08c98"
integrity sha512-gtM8XtRyrNFCtxHBIU3pqTlBeAi5VOoymJAXKQQ7RsHEVJX79OTk1dQ9Q6Ow14+REGwQU/zFECV050jbLTfvrQ==
xterm@4.3.0-beta17:
version "4.3.0-beta17"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta17.tgz#c038cc00cb5be33d2a5f083255c329d9ed186565"

View file

@ -56,5 +56,5 @@ export const BrowserFeatures = {
})(),
touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0,
pointerEvents: browser.isSafari && window.PointerEvent && ('ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0)
pointerEvents: window.PointerEvent && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0)
};

View file

@ -16,6 +16,7 @@ import * as platform from 'vs/base/common/platform';
import { coalesce } from 'vs/base/common/arrays';
import { URI } from 'vs/base/common/uri';
import { Schemas, RemoteAuthorities } from 'vs/base/common/network';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
export function clearNode(node: HTMLElement): void {
while (node.firstChild) {
@ -266,6 +267,13 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
return addDisposableListener(node, type, wrapHandler, useCapture);
};
export function addDisposableGenericMouseDownListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture);
}
export function addDisposableGenericMouseUpListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture);
}
export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
@ -443,7 +451,7 @@ export interface DOMEvent {
}
const MINIMUM_TIME_MS = 16;
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent, currentEvent: DOMEvent) {
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent | null, currentEvent: DOMEvent) {
return currentEvent;
};
@ -492,6 +500,11 @@ export function getClientArea(element: HTMLElement): Dimension {
return new Dimension(element.clientWidth, element.clientHeight);
}
// If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
if (platform.isIOS && (<any>window).visualViewport) {
return new Dimension((<any>window).visualViewport.width, (<any>window).visualViewport.height);
}
// Try innerWidth / innerHeight
if (window.innerWidth && window.innerHeight) {
return new Dimension(window.innerWidth, window.innerHeight);

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { IframeUtils } from 'vs/base/browser/iframe';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
@ -16,7 +17,7 @@ export interface IStandardMouseMoveEventData {
}
export interface IEventMerger<R> {
(lastEvent: R, currentEvent: MouseEvent): R;
(lastEvent: R | null, currentEvent: MouseEvent): R;
}
export interface IMouseMoveCallback<R> {
@ -27,7 +28,7 @@ export interface IOnStopCallback {
(): void;
}
export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData, currentEvent: MouseEvent): IStandardMouseMoveEventData {
export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent): IStandardMouseMoveEventData {
let ev = new StandardMouseEvent(currentEvent);
ev.preventDefault();
return {
@ -85,12 +86,12 @@ export class GlobalMouseMoveMonitor<R> implements IDisposable {
this.onStopCallback = onStopCallback;
let windowChain = IframeUtils.getSameOriginWindowChain();
const mouseMove = BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
const mouseUp = BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
for (const element of windowChain) {
this.hooks.add(dom.addDisposableThrottledListener(element.window.document, mouseMove,
(data: R) => this.mouseMoveCallback!(data),
(lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
(lastEvent: R | null, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
));
this.hooks.add(dom.addDisposableListener(element.window.document, mouseUp, (e: MouseEvent) => this.stopMonitoring(true)));
}

View file

@ -50,19 +50,19 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
const _href = function (href: string, isDomUri: boolean): string {
const data = markdown.uris && markdown.uris[href];
if (!data) {
return href;
return href; // no uri exists
}
let uri = URI.revive(data);
if (URI.parse(href).toString() === uri.toString()) {
return href; // no tranformation performed
}
if (isDomUri) {
uri = DOM.asDomUri(uri);
}
if (uri.query) {
uri = uri.with({ query: _uriMassage(uri.query) });
}
if (data) {
href = uri.toString(true);
}
return href;
return uri.toString();
};
// signal to code-block render that the

View file

@ -71,6 +71,7 @@ export class Gesture extends Disposable {
private dispatched = false;
private targets: HTMLElement[];
private ignoreTargets: HTMLElement[];
private handle: IDisposable | null;
private activeTouches: { [id: number]: TouchData; };
@ -81,6 +82,7 @@ export class Gesture extends Disposable {
this.activeTouches = {};
this.handle = null;
this.targets = [];
this.ignoreTargets = [];
this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e)));
this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e)));
this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e)));
@ -103,6 +105,23 @@ export class Gesture extends Disposable {
};
}
public static ignoreTarget(element: HTMLElement): IDisposable {
if (!Gesture.isTouchDevice()) {
return Disposable.None;
}
if (!Gesture.INSTANCE) {
Gesture.INSTANCE = new Gesture();
}
Gesture.INSTANCE.ignoreTargets.push(element);
return {
dispose: () => {
Gesture.INSTANCE.ignoreTargets = Gesture.INSTANCE.ignoreTargets.filter(t => t !== element);
}
};
}
@memoize
private static isTouchDevice(): boolean {
return 'ontouchstart' in window as any || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
@ -228,6 +247,12 @@ export class Gesture extends Disposable {
}
private dispatchEvent(event: GestureEvent): void {
for (let i = 0; i < this.ignoreTargets.length; i++) {
if (event.initialTarget instanceof Node && this.ignoreTargets[i].contains(event.initialTarget)) {
return;
}
}
this.targets.forEach(target => {
if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
target.dispatchEvent(event);

View file

@ -113,6 +113,8 @@ export class Checkbox extends Widget {
ev.preventDefault();
});
this.ignoreGesture(this.domNode);
this.onkeydown(this.domNode, (keyboardEvent) => {
if (keyboardEvent.keyCode === KeyCode.Space || keyboardEvent.keyCode === KeyCode.Enter) {
this.checked = !this._checked;

View file

@ -5,7 +5,7 @@
@font-face {
font-family: "codicon";
src: url("./codicon.ttf?72bd9e6bbf1e48287bcb9a9e4babeb28") format("truetype");
src: url("./codicon.ttf?b035097bd976825411d2c57142be0530") format("truetype");
}
.codicon[class*='codicon-'] {
@ -69,6 +69,9 @@
.codicon-eye-watch:before { content: "\ea70" }
.codicon-circle-filled:before { content: "\ea71" }
.codicon-primitive-dot:before { content: "\ea71" }
.codicon-debug-breakpoint:before { content: "\ea71" }
.codicon-debug-breakpoint-disabled:before { content: "\ea71" }
.codicon-debug-hint:before { content: "\ea71" }
.codicon-primitive-square:before { content: "\ea72" }
.codicon-edit:before { content: "\ea73" }
.codicon-pencil:before { content: "\ea73" }
@ -162,12 +165,15 @@
.codicon-bold:before { content: "\eaa3" }
.codicon-book:before { content: "\eaa4" }
.codicon-bookmark:before { content: "\eaa5" }
.codicon-breakpoint-conditional-unverified:before { content: "\eaa6" }
.codicon-breakpoint-conditional:before { content: "\eaa7" }
.codicon-breakpoint-data-unverified:before { content: "\eaa8" }
.codicon-breakpoint-data:before { content: "\eaa9" }
.codicon-breakpoint-log-unverified:before { content: "\eaaa" }
.codicon-breakpoint-log:before { content: "\eaab" }
.codicon-debug-breakpoint-conditional-unverified:before { content: "\eaa6" }
.codicon-debug-breakpoint-conditional:before { content: "\eaa7" }
.codicon-debug-breakpoint-conditional-disabled:before { content: "\eaa7" }
.codicon-debug-breakpoint-data-unverified:before { content: "\eaa8" }
.codicon-debug-breakpoint-data:before { content: "\eaa9" }
.codicon-debug-breakpoint-data-disabled:before { content: "\eaa9" }
.codicon-debug-breakpoint-log-unverified:before { content: "\eaaa" }
.codicon-debug-breakpoint-log:before { content: "\eaab" }
.codicon-debug-breakpoint-log-disabled:before { content: "\eaab" }
.codicon-briefcase:before { content: "\eaac" }
.codicon-broadcast:before { content: "\eaad" }
.codicon-browser:before { content: "\eaae" }
@ -185,6 +191,7 @@
.codicon-chrome-minimize:before { content: "\eaba" }
.codicon-chrome-restore:before { content: "\eabb" }
.codicon-circle-outline:before { content: "\eabc" }
.codicon-debug-breakpoint-unverified:before { content: "\eabc" }
.codicon-circle-slash:before { content: "\eabd" }
.codicon-circuit-board:before { content: "\eabe" }
.codicon-clear-all:before { content: "\eabf" }
@ -198,8 +205,6 @@
.codicon-comment-discussion:before { content: "\eac7" }
.codicon-compare-changes:before { content: "\eac8" }
.codicon-credit-card:before { content: "\eac9" }
.codicon-current-and-breakpoint:before { content: "\eaca" }
.codicon-current:before { content: "\eacb" }
.codicon-dash:before { content: "\eacc" }
.codicon-dashboard:before { content: "\eacd" }
.codicon-database:before { content: "\eace" }
@ -387,3 +392,12 @@
.codicon-list-selection:before { content: "\eb85" }
.codicon-selection:before { content: "\eb85" }
.codicon-list-tree:before { content: "\eb86" }
.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" }
.codicon-debug-breakpoint-function:before { content: "\eb88" }
.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" }
.codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" }
.codicon-debug-breakpoint-stackframe-dot:before { content: "\eb8a" }
.codicon-debug-breakpoint-stackframe:before { content: "\eb8b" }
.codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" }
.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" }
.codicon-debug-step-back:before { content: "\f101" }

View file

@ -5,6 +5,7 @@
import 'vs/css!./contextview';
import * as DOM from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Range } from 'vs/base/common/range';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
@ -179,7 +180,7 @@ export class ContextView extends Disposable {
return;
}
if (this.delegate!.canRelayout === false && !BrowserFeatures.pointerEvents) {
if (this.delegate!.canRelayout === false && !(platform.isIOS && BrowserFeatures.pointerEvents)) {
this.hide();
return;
}

View file

@ -8,6 +8,7 @@ import * as dom from 'vs/base/browser/dom';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IMatch } from 'vs/base/common/filters';
import { Disposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/base/common/range';
export interface IIconLabelCreationOptions {
supportHighlights?: boolean;
@ -24,6 +25,7 @@ export interface IIconLabelValueOptions {
matches?: IMatch[];
labelEscapeNewLines?: boolean;
descriptionMatches?: IMatch[];
readonly separator?: string;
}
class FastLabelNode {
@ -86,9 +88,10 @@ class FastLabelNode {
}
export class IconLabel extends Disposable {
private domNode: FastLabelNode;
private labelDescriptionContainer: FastLabelNode;
private labelNode: FastLabelNode | HighlightedLabel;
private descriptionContainer: FastLabelNode;
private nameNode: Label | LabelWithHighlights;
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
@ -97,18 +100,21 @@ export class IconLabel extends Disposable {
this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container'))));
const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
if (options?.supportHighlights) {
this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportCodicons);
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
} else {
this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name'))));
this.nameNode = new Label(nameContainer);
}
if (options?.supportDescriptionHighlights) {
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
} else {
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description'))));
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
}
}
@ -116,7 +122,7 @@ export class IconLabel extends Disposable {
return this.domNode.element;
}
setLabel(label: string, description?: string, options?: IIconLabelValueOptions): void {
setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void {
const classes = ['monaco-icon-label'];
if (options) {
if (options.extraClasses) {
@ -131,11 +137,7 @@ export class IconLabel extends Disposable {
this.domNode.className = classes.join(' ');
this.domNode.title = options?.title || '';
if (this.labelNode instanceof HighlightedLabel) {
this.labelNode.set(label || '', options?.matches, options?.title, options?.labelEscapeNewLines);
} else {
this.labelNode.textContent = label || '';
}
this.nameNode.setLabel(label, options);
if (description || this.descriptionNode) {
if (!this.descriptionNode) {
@ -157,3 +159,110 @@ export class IconLabel extends Disposable {
}
}
}
class Label {
private label: string | string[] | undefined = undefined;
private singleLabel: HTMLElement | undefined = undefined;
constructor(private container: HTMLElement) { }
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
if (this.label === label) {
return;
}
this.label = label;
if (typeof label === 'string') {
if (!this.singleLabel) {
this.container.innerHTML = '';
dom.removeClass(this.container, 'multiple');
this.singleLabel = dom.append(this.container, dom.$('a.label-name'));
}
this.singleLabel.textContent = label;
} else {
this.container.innerHTML = '';
dom.addClass(this.container, 'multiple');
this.singleLabel = undefined;
for (let i = 0; i < label.length; i++) {
const l = label[i];
dom.append(this.container, dom.$('a.label-name', { 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l));
if (i < label.length - 1) {
dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/'));
}
}
}
}
}
function splitMatches(labels: string[], separator: string, matches: IMatch[] | undefined): IMatch[][] | undefined {
if (!matches) {
return undefined;
}
let labelStart = 0;
return labels.map(label => {
const labelRange = { start: labelStart, end: labelStart + label.length };
const result = matches
.map(match => Range.intersect(labelRange, match))
.filter(range => !Range.isEmpty(range))
.map(({ start, end }) => ({ start: start - labelStart, end: end - labelStart }));
labelStart = labelRange.end + separator.length;
return result;
});
}
class LabelWithHighlights {
private label: string | string[] | undefined = undefined;
private singleLabel: HighlightedLabel | undefined = undefined;
constructor(private container: HTMLElement, private supportCodicons: boolean) { }
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
if (this.label === label) {
return;
}
this.label = label;
if (typeof label === 'string') {
if (!this.singleLabel) {
this.container.innerHTML = '';
dom.removeClass(this.container, 'multiple');
this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name')), this.supportCodicons);
}
this.singleLabel.set(label, options?.matches, options?.title, options?.labelEscapeNewLines);
} else {
this.container.innerHTML = '';
dom.addClass(this.container, 'multiple');
this.singleLabel = undefined;
const separator = options?.separator || '/';
const matches = splitMatches(label, separator, options?.matches);
for (let i = 0; i < label.length; i++) {
const l = label[i];
const m = matches ? matches[i] : undefined;
const name = dom.$('a.label-name', { 'data-icon-label-count': label.length, 'data-icon-label-index': i });
const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons);
highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines);
if (i < label.length - 1) {
dom.append(name, dom.$('span.label-separator', undefined, separator));
}
}
}
}
}

View file

@ -31,25 +31,32 @@
flex-shrink: 0; /* fix for https://github.com/Microsoft/vscode/issues/13787 */
}
.monaco-icon-label > .monaco-icon-label-description-container {
overflow: hidden; /* this causes the label/description to shrink first if decorations are enabled */
.monaco-icon-label > .monaco-icon-label-container {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.monaco-icon-label > .monaco-icon-label-description-container > .label-name {
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
color: inherit;
white-space: pre; /* enable to show labels that include multiple whitespaces */
}
.monaco-icon-label > .monaco-icon-label-description-container > .label-description {
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name > .label-separator {
margin: 0 2px;
opacity: 0.5;
}
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
opacity: .7;
margin-left: 0.5em;
font-size: 0.9em;
white-space: pre; /* enable to show labels that include multiple whitespaces */
}
.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-name,
.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-description {
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
font-style: italic;
}
@ -58,7 +65,6 @@
font-size: 90%;
font-weight: 600;
padding: 0 16px 0 5px;
margin-left: auto;
text-align: center;
}

View file

@ -220,6 +220,8 @@ export class InputBox extends Widget {
});
}
this.ignoreGesture(this.input);
setTimeout(() => this.updateMirror(), 0);
// Support actions

View file

@ -103,10 +103,11 @@ export const ListDragOverReactions = {
export interface IListDragAndDrop<T> {
getDragURI(element: T): string | null;
getDragLabel?(elements: T[]): string | undefined;
getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined;
onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
onDragEnd?(originalEvent: DragEvent): void;
}
export class ListError extends Error {

View file

@ -74,9 +74,10 @@ const DefaultOptions = {
horizontalScrolling: false
};
export class ElementsDragAndDropData<T> implements IDragAndDropData {
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
readonly elements: T[];
context: TContext | undefined;
constructor(elements: T[]) {
this.elements = elements;
@ -84,7 +85,7 @@ export class ElementsDragAndDropData<T> implements IDragAndDropData {
update(): void { }
getData(): any {
getData(): T[] {
return this.elements;
}
}
@ -99,7 +100,7 @@ export class ExternalElementsDragAndDropData<T> implements IDragAndDropData {
update(): void { }
getData(): any {
getData(): T[] {
return this.elements;
}
}
@ -766,7 +767,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
let label: string | undefined;
if (this.dnd.getDragLabel) {
label = this.dnd.getDragLabel(elements);
label = this.dnd.getDragLabel(elements, event);
}
if (typeof label === 'undefined') {
@ -846,10 +847,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
feedback = feedback[0] === -1 ? [-1] : feedback;
if (feedback.length === 0) {
throw new Error('Invalid empty feedback list');
}
if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
return true;
}
@ -910,12 +907,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
}
private onDragEnd(): void {
private onDragEnd(event: DragEvent): void {
this.canDrop = false;
this.teardownDragAndDropScrollTopAnimation();
this.clearDragOverFeedback();
this.currentDragData = undefined;
StaticDND.CurrentDragAndDropData = undefined;
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(event);
}
}
private clearDragOverFeedback(): void {

View file

@ -1062,9 +1062,9 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
return this.dnd.getDragURI(element);
}
getDragLabel?(elements: T[]): string | undefined {
getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined {
if (this.dnd.getDragLabel) {
return this.dnd.getDragLabel(elements);
return this.dnd.getDragLabel(elements, originalEvent);
}
return undefined;
@ -1080,6 +1080,12 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
}
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
}

View file

@ -10,6 +10,7 @@ import * as dom from 'vs/base/browser/dom';
import * as arrays from 'vs/base/common/arrays';
import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { isMacintosh } from 'vs/base/common/platform';
import { Gesture, EventType } from 'vs/base/browser/touch';
export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
@ -43,6 +44,12 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
}
private registerListeners() {
this._register(Gesture.addTarget(this.selectElement));
[EventType.Tap].forEach(eventType => {
this._register(dom.addDisposableListener(this.selectElement, eventType, (e) => {
this.selectElement.focus();
}));
});
this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
this.selectElement.title = e.target.value;

View file

@ -27,10 +27,24 @@ import { clamp } from 'vs/base/common/numbers';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { SetMap } from 'vs/base/common/collections';
class TreeElementsDragAndDropData<T, TFilterData, TContext> extends ElementsDragAndDropData<T, TContext> {
set context(context: TContext | undefined) {
this.data.context = context;
}
get context(): TContext | undefined {
return this.data.context;
}
constructor(private data: ElementsDragAndDropData<ITreeNode<T, TFilterData>, TContext>) {
super(data.elements.map(node => node.element));
}
}
function asTreeDragAndDropData<T, TFilterData>(data: IDragAndDropData): IDragAndDropData {
if (data instanceof ElementsDragAndDropData) {
const nodes = (data as ElementsDragAndDropData<ITreeNode<T, TFilterData>>).elements;
return new ElementsDragAndDropData(nodes.map(node => node.element));
return new TreeElementsDragAndDropData(data);
}
return data;
@ -47,9 +61,9 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
return this.dnd.getDragURI(node.element);
}
getDragLabel(nodes: ITreeNode<T, TFilterData>[]): string | undefined {
getDragLabel(nodes: ITreeNode<T, TFilterData>[], originalEvent: DragEvent): string | undefined {
if (this.dnd.getDragLabel) {
return this.dnd.getDragLabel(nodes.map(node => node.element));
return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
}
return undefined;
@ -87,7 +101,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}, 500);
}
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
if (!raw) {
const accept = typeof result === 'boolean' ? result : result.accept;
const effect = typeof result === 'boolean' ? undefined : result.effect;
@ -121,6 +135,12 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
}
}
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {

View file

@ -150,10 +150,24 @@ function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTr
};
}
class AsyncDataTreeElementsDragAndDropData<TInput, T, TContext> extends ElementsDragAndDropData<T, TContext> {
set context(context: TContext | undefined) {
this.data.context = context;
}
get context(): TContext | undefined {
return this.data.context;
}
constructor(private data: ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>, TContext>) {
super(data.elements.map(node => node.element as T));
}
}
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
if (data instanceof ElementsDragAndDropData) {
const nodes = (data as ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>>).elements;
return new ElementsDragAndDropData(nodes.map(node => node.element));
return new AsyncDataTreeElementsDragAndDropData(data);
}
return data;
@ -167,9 +181,9 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
return this.dnd.getDragURI(node.element as T);
}
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[]): string | undefined {
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[], originalEvent: DragEvent): string | undefined {
if (this.dnd.getDragLabel) {
return this.dnd.getDragLabel(nodes.map(node => node.element as T));
return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
}
return undefined;
@ -188,6 +202,12 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
}
}
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
@ -993,6 +1013,12 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
}
}
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.disposeTemplate(templateData.templateData);
}

View file

@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Disposable } from 'vs/base/common/lifecycle';
import { Gesture } from 'vs/base/browser/touch';
export abstract class Widget extends Disposable {
@ -49,4 +50,8 @@ export abstract class Widget extends Disposable {
protected onchange(domNode: HTMLElement, listener: (e: Event) => void): void {
this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener));
}
protected ignoreGesture(domNode: HTMLElement): void {
Gesture.ignoreTarget(domNode);
}
}

View file

@ -69,11 +69,11 @@ function validateString(value: string, name: string) {
}
}
function isPathSeparator(code: number) {
function isPathSeparator(code: number | undefined) {
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
}
function isPosixPathSeparator(code: number) {
function isPosixPathSeparator(code: number | undefined) {
return code === CHAR_FORWARD_SLASH;
}

View file

@ -10,6 +10,7 @@ let _isMacintosh = false;
let _isLinux = false;
let _isNative = false;
let _isWeb = false;
let _isIOS = false;
let _locale: string | undefined = undefined;
let _language: string = LANGUAGE_DEFAULT;
let _translationsConfigFile: string | undefined = undefined;
@ -41,6 +42,7 @@ declare const global: any;
interface INavigator {
userAgent: string;
language: string;
maxTouchPoints?: number;
}
declare const navigator: INavigator;
declare const self: any;
@ -52,6 +54,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
_userAgent = navigator.userAgent;
_isWindows = _userAgent.indexOf('Windows') >= 0;
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
_isIOS = _userAgent.indexOf('Macintosh') >= 0 && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
_isLinux = _userAgent.indexOf('Linux') >= 0;
_isWeb = true;
_locale = navigator.language;
@ -106,6 +109,7 @@ export const isMacintosh = _isMacintosh;
export const isLinux = _isLinux;
export const isNative = _isNative;
export const isWeb = _isWeb;
export const isIOS = _isIOS;
export const platform = _platform;
export const userAgent = _userAgent;

View file

@ -32,6 +32,7 @@
'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'xterm-addon-webgl': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
}
};

View file

@ -36,6 +36,7 @@
'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'xterm-addon-webgl': `${window.location.origin}/static/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
}
};

View file

@ -438,15 +438,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Inject headers when requests are incoming
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
this.marketplaceHeadersPromise.then(headers => {
const requestHeaders = objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined };
if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) {
requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`;
}
cb({ cancel: false, requestHeaders });
});
});
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) =>
this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } })));
}
private onWindowError(error: WindowError): void {
@ -1096,7 +1089,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
const segments: ITouchBarSegment[] = items.map(item => {
let icon: NativeImage | undefined;
if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
if (item.iconLocation && item.iconLocation?.dark?.scheme === 'file') {
icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
if (icon.isEmpty()) {
icon = undefined;

View file

@ -27,7 +27,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
* Merges mouse events when mouse move events are throttled
*/
export function createMouseMoveEventMerger(mouseTargetFactory: MouseTargetFactory | null) {
return function (lastEvent: EditorMouseEvent, currentEvent: EditorMouseEvent): EditorMouseEvent {
return function (lastEvent: EditorMouseEvent | null, currentEvent: EditorMouseEvent): EditorMouseEvent {
let targetIsWidget = false;
if (mouseTargetFactory) {
targetIsWidget = mouseTargetFactory.mouseTargetIsWidget(currentEvent);

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler';
@ -18,7 +19,7 @@ interface IThrottledGestureEvent {
translationY: number;
}
function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent, currentEvent: MSGestureEvent): IThrottledGestureEvent {
function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent | null, currentEvent: MSGestureEvent): IThrottledGestureEvent {
const r = {
translationY: currentEvent.translationY,
translationX: currentEvent.translationX
@ -53,7 +54,7 @@ class MsPointerHandler extends MouseHandler implements IDisposable {
const penGesture = new MSGesture();
touchGesture.target = this.viewHelper.linesContentDomNode;
penGesture.target = this.viewHelper.linesContentDomNode;
this.viewHelper.linesContentDomNode.addEventListener('MSPointerDown', (e: MSPointerEvent) => {
this.viewHelper.linesContentDomNode.addEventListener(<any>'MSPointerDown', (e: MSPointerEvent) => {
// Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions
const pointerType = <any>e.pointerType;
if (pointerType === ((<any>e).MSPOINTER_TYPE_MOUSE || 'mouse')) {
@ -67,7 +68,7 @@ class MsPointerHandler extends MouseHandler implements IDisposable {
penGesture.addPointer(e.pointerId);
}
});
this._register(dom.addDisposableThrottledListener<IThrottledGestureEvent>(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
this._register(dom.addDisposableThrottledListener<IThrottledGestureEvent, MSGestureEvent>(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true));
}
}, 100);
@ -132,7 +133,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
const penGesture = new MSGesture();
touchGesture.target = this.viewHelper.linesContentDomNode;
penGesture.target = this.viewHelper.linesContentDomNode;
this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: MSPointerEvent) => {
this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: PointerEvent) => {
const pointerType = <any>e.pointerType;
if (pointerType === 'mouse') {
this._lastPointerType = 'mouse';
@ -145,7 +146,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
penGesture.addPointer(e.pointerId);
}
});
this._register(dom.addDisposableThrottledListener<IThrottledGestureEvent>(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
this._register(dom.addDisposableThrottledListener<IThrottledGestureEvent, MSGestureEvent>(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true));
}
}, 100);
@ -283,7 +284,7 @@ export class PointerHandler extends Disposable {
super();
if (window.navigator.msPointerEnabled) {
this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper));
} else if (((<any>window).PointerEvent && BrowserFeatures.pointerEvents)) {
} else if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
} else if ((<any>window).TouchEvent) {
this.handler = this._register(new TouchHandler(context, viewController, viewHelper));

View file

@ -16,7 +16,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model';
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService';

View file

@ -84,7 +84,7 @@ export class EditorMouseEvent extends StandardMouseEvent {
}
export interface EditorMouseEventMerger {
(lastEvent: EditorMouseEvent, currentEvent: EditorMouseEvent): EditorMouseEvent;
(lastEvent: EditorMouseEvent | null, currentEvent: EditorMouseEvent): EditorMouseEvent;
}
export class EditorMouseEventFactory {
@ -124,7 +124,7 @@ export class EditorMouseEventFactory {
}
public onMouseMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable {
const myMerger: dom.IEventMerger<EditorMouseEvent, MouseEvent> = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => {
const myMerger: dom.IEventMerger<EditorMouseEvent, MouseEvent> = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => {
return merger(lastEvent, this._create(currentEvent));
};
return dom.addDisposableThrottledListener<EditorMouseEvent, MouseEvent>(target, 'mousemove', callback, myMerger, minimumTimeMs);
@ -162,7 +162,7 @@ export class EditorPointerEventFactory {
}
public onPointerMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable {
const myMerger: dom.IEventMerger<EditorMouseEvent, MouseEvent> = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => {
const myMerger: dom.IEventMerger<EditorMouseEvent, MouseEvent> = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => {
return merger(lastEvent, this._create(currentEvent));
};
return dom.addDisposableThrottledListener<EditorMouseEvent, MouseEvent>(target, 'pointermove', callback, myMerger, minimumTimeMs);
@ -195,7 +195,7 @@ export class GlobalEditorMouseMoveMonitor extends Disposable {
this._globalMouseMoveMonitor.stopMonitoring(true);
}, true);
const myMerger: dom.IEventMerger<EditorMouseEvent, MouseEvent> = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => {
const myMerger: dom.IEventMerger<EditorMouseEvent, MouseEvent> = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => {
return merger(lastEvent, new EditorMouseEvent(currentEvent, this._editorViewDomNode));
};

View file

@ -307,7 +307,7 @@ export function registerEditorContribution<Services extends BrandedService[]>(id
EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor);
}
export function registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void {
export function registerDiffEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void {
EditorContributionRegistry.INSTANCE.registerDiffEditorContribution(id, ctor);
}
@ -363,7 +363,7 @@ class EditorContributionRegistry {
return this.editorContributions.slice(0);
}
public registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void {
public registerDiffEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void {
this.diffEditorContributions.push({ id, ctor });
}

View file

@ -4,58 +4,132 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { parse } from 'vs/base/common/marshalling';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { normalizePath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener } from 'vs/platform/opener/common/opener';
import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener';
import { EditorOpenContext } from 'vs/platform/editor/common/editor';
export class OpenerService extends Disposable implements IOpenerService {
class CommandOpener implements IOpener {
constructor(@ICommandService private readonly _commandService: ICommandService) { }
async open(target: URI | string) {
if (!matchesScheme(target, Schemas.command)) {
return false;
}
// run command or bail out if command isn't known
if (typeof target === 'string') {
target = URI.parse(target);
}
if (!CommandsRegistry.getCommand(target.path)) {
throw new Error(`command '${target.path}' NOT known`);
}
// execute as command
let args: any = [];
try {
args = parse(target.query);
if (!Array.isArray(args)) {
args = [args];
}
} catch (e) {
// ignore error
}
await this._commandService.executeCommand(target.path, ...args);
return true;
}
}
class EditorOpener implements IOpener {
constructor(@ICodeEditorService private readonly _editorService: ICodeEditorService) { }
async open(target: URI | string, options: OpenOptions) {
if (typeof target === 'string') {
target = URI.parse(target);
}
let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined;
const match = /^L?(\d+)(?:,(\d+))?/.exec(target.fragment);
if (match) {
// support file:///some/file.js#73,84
// support file:///some/file.js#L73
selection = {
startLineNumber: parseInt(match[1]),
startColumn: match[2] ? parseInt(match[2]) : 1
};
// remove fragment
target = target.with({ fragment: '' });
}
if (target.scheme === Schemas.file) {
target = normalizePath(target); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
}
await this._editorService.openCodeEditor(
{ resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } },
this._editorService.getFocusedCodeEditor(),
options?.openToSide
);
return true;
}
}
export class OpenerService implements IOpenerService {
_serviceBrand: undefined;
private readonly _openers = new LinkedList<IOpener>();
private readonly _validators = new LinkedList<IValidator>();
private readonly _resolvers = new LinkedList<IExternalUriResolver>();
private _externalOpener: IExternalOpener;
constructor(
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@ICommandService private readonly _commandService: ICommandService,
@ICodeEditorService editorService: ICodeEditorService,
@ICommandService commandService: ICommandService,
) {
super();
// Default external opener is going through window.open()
this._externalOpener = {
openExternal: href => {
dom.windowOpenNoOpener(href);
return Promise.resolve(true);
}
};
// Default opener: maito, http(s), command, and catch-all-editors
this._openers.push({
open: async (target: URI | string, options?: OpenOptions) => {
if (options?.openExternal || matchesScheme(target, Schemas.mailto) || matchesScheme(target, Schemas.http) || matchesScheme(target, Schemas.https)) {
// open externally
await this._doOpenExternal(target, options);
return true;
}
return false;
}
});
this._openers.push(new CommandOpener(commandService));
this._openers.push(new EditorOpener(editorService));
}
registerOpener(opener: IOpener): IDisposable {
const remove = this._openers.push(opener);
const remove = this._openers.unshift(opener);
return { dispose: remove };
}
registerValidator(validator: IValidator): IDisposable {
const remove = this._validators.push(validator);
return { dispose: remove };
}
registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable {
const remove = this._resolvers.push(resolver);
return { dispose: remove };
}
@ -63,30 +137,24 @@ export class OpenerService extends Disposable implements IOpenerService {
this._externalOpener = externalOpener;
}
async open(resource: URI, options?: OpenOptions): Promise<boolean> {
// no scheme ?!?
if (!resource.scheme) {
return Promise.resolve(false);
}
async open(target: URI | string, options?: OpenOptions): Promise<boolean> {
// check with contributed validators
for (const validator of this._validators.toArray()) {
if (!(await validator.shouldOpen(resource))) {
if (!(await validator.shouldOpen(target))) {
return false;
}
}
// check with contributed openers
for (const opener of this._openers.toArray()) {
const handled = await opener.open(resource, options);
const handled = await opener.open(target, options);
if (handled) {
return true;
}
}
// use default openers
return this._doOpen(resource, options);
return false;
}
async resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise<IResolvedExternalUri> {
@ -100,68 +168,19 @@ export class OpenerService extends Disposable implements IOpenerService {
return { resolved: resource, dispose: () => { } };
}
private async _doOpen(resource: URI, options: OpenOptions | undefined): Promise<boolean> {
const { scheme, path, query, fragment } = resource;
private async _doOpenExternal(resource: URI | string, options: OpenOptions | undefined): Promise<boolean> {
if (options?.openExternal || equalsIgnoreCase(scheme, Schemas.mailto) || equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) {
// open externally
return this._doOpenExternal(resource, options);
//todo@joh IExternalUriResolver should support `uri: URI | string`
const uri = typeof resource === 'string' ? URI.parse(resource) : resource;
const { resolved } = await this.resolveExternalUri(uri, options);
if (typeof resource === 'string' && uri.toString() === resolved.toString()) {
// open the url-string AS IS
return this._externalOpener.openExternal(resource);
} else {
// open URI using the toString(noEncode)+encodeURI-trick
return this._externalOpener.openExternal(encodeURI(resolved.toString(true)));
}
if (equalsIgnoreCase(scheme, Schemas.command)) {
// run command or bail out if command isn't known
if (!CommandsRegistry.getCommand(path)) {
throw new Error(`command '${path}' NOT known`);
}
// execute as command
let args: any = [];
try {
args = parse(query);
if (!Array.isArray(args)) {
args = [args];
}
} catch (e) {
// ignore error
}
await this._commandService.executeCommand(path, ...args);
return true;
}
// finally open in editor
let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined;
const match = /^L?(\d+)(?:,(\d+))?/.exec(fragment);
if (match) {
// support file:///some/file.js#73,84
// support file:///some/file.js#L73
selection = {
startLineNumber: parseInt(match[1]),
startColumn: match[2] ? parseInt(match[2]) : 1
};
// remove fragment
resource = resource.with({ fragment: '' });
}
if (resource.scheme === Schemas.file) {
resource = resources.normalizePath(resource); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
}
await this._editorService.openCodeEditor(
{ resource, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } },
this._editorService.getFocusedCodeEditor(),
options?.openToSide
);
return true;
}
private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise<boolean> {
const { resolved } = await this.resolveExternalUri(resource, options);
// TODO@Jo neither encodeURI nor toString(true) should be needed
// once we go with URL and not URI
return this._externalOpener.openExternal(encodeURI(resolved.toString(true)));
}
dispose() {

View file

@ -11,7 +11,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler';
import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler';
import { ITextAreaHandlerHelper, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser';
import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController';
import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents';
import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays';
@ -51,17 +51,15 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
export interface IContentWidgetData {
widget: editorBrowser.IContentWidget;
position: editorBrowser.IContentWidgetPosition | null;
widget: IContentWidget;
position: IContentWidgetPosition | null;
}
export interface IOverlayWidgetData {
widget: editorBrowser.IOverlayWidget;
position: editorBrowser.IOverlayWidgetPosition | null;
widget: IOverlayWidget;
position: IOverlayWidgetPosition | null;
}
const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
export class View extends ViewEventHandler {
private readonly eventDispatcher: ViewEventDispatcher;
@ -348,7 +346,7 @@ export class View extends ViewEventHandler {
super.dispose();
}
private _renderOnce(callback: () => any): any {
private _renderOnce<T>(callback: () => T): T {
const r = safeInvokeNoArg(callback);
this._scheduleRender();
return r;
@ -458,7 +456,7 @@ export class View extends ViewEventHandler {
return visibleRange.left;
}
public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget | null {
public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {
return this.pointerHandler.getTargetAtClientPoint(clientX, clientY);
}
@ -466,42 +464,15 @@ export class View extends ViewEventHandler {
return new OverviewRuler(this._context, cssClassName);
}
public change(callback: (changeAccessor: editorBrowser.IViewZoneChangeAccessor) => any): boolean {
let zonesHaveChanged = false;
this._renderOnce(() => {
const changeAccessor: editorBrowser.IViewZoneChangeAccessor = {
addZone: (zone: editorBrowser.IViewZone): string => {
zonesHaveChanged = true;
return this.viewZones.addZone(zone);
},
removeZone: (id: string): void => {
if (!id) {
return;
}
zonesHaveChanged = this.viewZones.removeZone(id) || zonesHaveChanged;
},
layoutZone: (id: string): void => {
if (!id) {
return;
}
zonesHaveChanged = this.viewZones.layoutZone(id) || zonesHaveChanged;
}
};
safeInvoke1Arg(callback, changeAccessor);
// Invalidate changeAccessor
changeAccessor.addZone = invalidFunc;
changeAccessor.removeZone = invalidFunc;
changeAccessor.layoutZone = invalidFunc;
public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean {
return this._renderOnce(() => {
const zonesHaveChanged = this.viewZones.changeViewZones(callback);
if (zonesHaveChanged) {
this._context.viewLayout.onHeightMaybeChanged();
this._context.privateViewEventBus.emit(new viewEvents.ViewZonesChangedEvent());
}
return zonesHaveChanged;
});
return zonesHaveChanged;
}
public render(now: boolean, everything: boolean): void {
@ -582,10 +553,3 @@ function safeInvokeNoArg(func: Function): any {
}
}
function safeInvoke1Arg(func: Function, arg1: any): any {
try {
return func(arg1);
} catch (e) {
onUnexpectedError(e);
}
}

View file

@ -177,7 +177,7 @@ export class GlyphMarginOverlay extends DedupOverlay {
output[lineIndex] = '';
} else {
output[lineIndex] = (
'<div class="cgmr '
'<div class="cgmr codicon '
+ classNames.join(' ')
+ common
);

View file

@ -407,7 +407,7 @@ class FastRenderedViewLine implements IRenderedViewLine {
*/
class RenderedViewLine implements IRenderedViewLine {
public domNode: FastDomNode<HTMLElement>;
public domNode: FastDomNode<HTMLElement> | null;
public readonly input: RenderLineInput;
protected readonly _characterMapping: CharacterMapping;
@ -420,7 +420,7 @@ class RenderedViewLine implements IRenderedViewLine {
*/
private readonly _pixelOffsetCache: Int32Array | null;
constructor(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) {
constructor(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) {
this.domNode = domNode;
this.input = renderLineInput;
this._characterMapping = characterMapping;
@ -439,16 +439,19 @@ class RenderedViewLine implements IRenderedViewLine {
// --- Reading from the DOM methods
protected _getReadingTarget(): HTMLElement {
return <HTMLSpanElement>this.domNode.domNode.firstChild;
protected _getReadingTarget(myDomNode: FastDomNode<HTMLElement>): HTMLElement {
return <HTMLSpanElement>myDomNode.domNode.firstChild;
}
/**
* Width of the line in pixels
*/
public getWidth(): number {
if (!this.domNode) {
return 0;
}
if (this._cachedWidth === -1) {
this._cachedWidth = this._getReadingTarget().offsetWidth;
this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth;
}
return this._cachedWidth;
}
@ -464,14 +467,17 @@ class RenderedViewLine implements IRenderedViewLine {
* Visible ranges for a model range
*/
public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
if (!this.domNode) {
return null;
}
if (this._pixelOffsetCache !== null) {
// the text is LTR
const startOffset = this._readPixelOffset(startColumn, context);
const startOffset = this._readPixelOffset(this.domNode, startColumn, context);
if (startOffset === -1) {
return null;
}
const endOffset = this._readPixelOffset(endColumn, context);
const endOffset = this._readPixelOffset(this.domNode, endColumn, context);
if (endOffset === -1) {
return null;
}
@ -479,23 +485,23 @@ class RenderedViewLine implements IRenderedViewLine {
return [new HorizontalRange(startOffset, endOffset - startOffset)];
}
return this._readVisibleRangesForRange(startColumn, endColumn, context);
return this._readVisibleRangesForRange(this.domNode, startColumn, endColumn, context);
}
protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
protected _readVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
if (startColumn === endColumn) {
const pixelOffset = this._readPixelOffset(startColumn, context);
const pixelOffset = this._readPixelOffset(domNode, startColumn, context);
if (pixelOffset === -1) {
return null;
} else {
return [new HorizontalRange(pixelOffset, 0)];
}
} else {
return this._readRawVisibleRangesForRange(startColumn, endColumn, context);
return this._readRawVisibleRangesForRange(domNode, startColumn, endColumn, context);
}
}
protected _readPixelOffset(column: number, context: DomReadingContext): number {
protected _readPixelOffset(domNode: FastDomNode<HTMLElement>, column: number, context: DomReadingContext): number {
if (this._characterMapping.length === 0) {
// This line has no content
if (this._containsForeignElements === ForeignElementType.None) {
@ -520,18 +526,18 @@ class RenderedViewLine implements IRenderedViewLine {
return cachedPixelOffset;
}
const result = this._actualReadPixelOffset(column, context);
const result = this._actualReadPixelOffset(domNode, column, context);
this._pixelOffsetCache[column] = result;
return result;
}
return this._actualReadPixelOffset(column, context);
return this._actualReadPixelOffset(domNode, column, context);
}
private _actualReadPixelOffset(column: number, context: DomReadingContext): number {
private _actualReadPixelOffset(domNode: FastDomNode<HTMLElement>, column: number, context: DomReadingContext): number {
if (this._characterMapping.length === 0) {
// This line has no content
const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode);
const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode);
if (!r || r.length === 0) {
return -1;
}
@ -547,14 +553,14 @@ class RenderedViewLine implements IRenderedViewLine {
const partIndex = CharacterMapping.getPartIndex(partData);
const charOffsetInPart = CharacterMapping.getCharIndex(partData);
const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode);
const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode);
if (!r || r.length === 0) {
return -1;
}
return r[0].left;
}
private _readRawVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
private _readRawVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
if (startColumn === 1 && endColumn === this._characterMapping.length) {
// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line
@ -570,7 +576,7 @@ class RenderedViewLine implements IRenderedViewLine {
const endPartIndex = CharacterMapping.getPartIndex(endPartData);
const endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData);
return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode);
return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode);
}
/**
@ -591,8 +597,8 @@ class RenderedViewLine implements IRenderedViewLine {
}
class WebKitRenderedViewLine extends RenderedViewLine {
protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
const output = super._readVisibleRangesForRange(startColumn, endColumn, context);
protected _readVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null {
const output = super._readVisibleRangesForRange(domNode, startColumn, endColumn, context);
if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) {
return output;
@ -603,7 +609,7 @@ class WebKitRenderedViewLine extends RenderedViewLine {
if (!this.input.containsRTL) {
// This is an attempt to patch things up
// Find position of last column
const endPixelOffset = this._readPixelOffset(endColumn, context);
const endPixelOffset = this._readPixelOffset(domNode, endColumn, context);
if (endPixelOffset !== -1) {
const lastRange = output[output.length - 1];
if (lastRange.left < endPixelOffset) {
@ -624,10 +630,10 @@ const createRenderedLine: (domNode: FastDomNode<HTMLElement> | null, renderLineI
return createNormalRenderedLine;
})();
function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine {
function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine {
return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}
function createNormalRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine {
function createNormalRenderedLine(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine {
return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}

View file

@ -5,7 +5,7 @@
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IViewZone } from 'vs/editor/browser/editorBrowser';
import { IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
import { Position } from 'vs/editor/common/core/position';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
@ -13,7 +13,7 @@ import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IWhitespaceChangeAccessor, IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
export interface IMyViewZone {
whitespaceId: string;
@ -29,6 +29,8 @@ interface IComputedViewZoneProps {
minWidthInPx: number;
}
const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
export class ViewZones extends ViewPart {
private _zones: { [id: string]: IMyViewZone; };
@ -72,20 +74,29 @@ export class ViewZones extends ViewPart {
// ---- begin view event handlers
private _recomputeWhitespacesProps(): boolean {
let hadAChange = false;
const keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
const id = keys[i];
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
if (this._context.viewLayout.changeWhitespace(id, props.afterViewLineNumber, props.heightInPx)) {
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
hadAChange = true;
}
const whitespaces = this._context.viewLayout.getWhitespaces();
const oldWhitespaces = new Map<string, IEditorWhitespace>();
for (const whitespace of whitespaces) {
oldWhitespaces.set(whitespace.id, whitespace);
}
return this._context.viewLayout.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => {
let hadAChange = false;
return hadAChange;
const keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
const id = keys[i];
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
const oldWhitespace = oldWhitespaces.get(id);
if (oldWhitespace && (oldWhitespace.afterLineNumber !== props.afterViewLineNumber || oldWhitespace.height !== props.heightInPx)) {
whitespaceAccessor.changeOneWhitespace(id, props.afterViewLineNumber, props.heightInPx);
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
hadAChange = true;
}
}
return hadAChange;
});
}
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
@ -138,7 +149,6 @@ export class ViewZones extends ViewPart {
return 10000;
}
private _computeWhitespaceProps(zone: IViewZone): IComputedViewZoneProps {
if (zone.afterLineNumber === 0) {
return {
@ -188,9 +198,44 @@ export class ViewZones extends ViewPart {
};
}
public addZone(zone: IViewZone): string {
public changeViewZones(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean {
return this._context.viewLayout.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => {
let zonesHaveChanged = false;
const changeAccessor: IViewZoneChangeAccessor = {
addZone: (zone: IViewZone): string => {
zonesHaveChanged = true;
return this._addZone(whitespaceAccessor, zone);
},
removeZone: (id: string): void => {
if (!id) {
return;
}
zonesHaveChanged = this._removeZone(whitespaceAccessor, id) || zonesHaveChanged;
},
layoutZone: (id: string): void => {
if (!id) {
return;
}
zonesHaveChanged = this._layoutZone(whitespaceAccessor, id) || zonesHaveChanged;
}
};
safeInvoke1Arg(callback, changeAccessor);
// Invalidate changeAccessor
changeAccessor.addZone = invalidFunc;
changeAccessor.removeZone = invalidFunc;
changeAccessor.layoutZone = invalidFunc;
return zonesHaveChanged;
});
}
private _addZone(whitespaceAccessor: IWhitespaceChangeAccessor, zone: IViewZone): string {
const props = this._computeWhitespaceProps(zone);
const whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx);
const whitespaceId = whitespaceAccessor.insertWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx);
const myZone: IMyViewZone = {
whitespaceId: whitespaceId,
@ -224,11 +269,11 @@ export class ViewZones extends ViewPart {
return myZone.whitespaceId;
}
public removeZone(id: string): boolean {
private _removeZone(whitespaceAccessor: IWhitespaceChangeAccessor, id: string): boolean {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
delete this._zones[id];
this._context.viewLayout.removeWhitespace(zone.whitespaceId);
whitespaceAccessor.removeWhitespace(zone.whitespaceId);
zone.domNode.removeAttribute('monaco-visible-view-zone');
zone.domNode.removeAttribute('monaco-view-zone');
@ -247,21 +292,20 @@ export class ViewZones extends ViewPart {
return false;
}
public layoutZone(id: string): boolean {
let changed = false;
private _layoutZone(whitespaceAccessor: IWhitespaceChangeAccessor, id: string): boolean {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
// const newOrdinal = this._getZoneOrdinal(zone.delegate);
changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed;
whitespaceAccessor.changeOneWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx);
// TODO@Alex: change `newOrdinal` too
if (changed) {
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
this.setShouldRender();
}
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
this.setShouldRender();
return true;
}
return changed;
return false;
}
public shouldSuppressMouseDownOnViewZone(id: string): boolean {
@ -365,3 +409,11 @@ export class ViewZones extends ViewPart {
}
}
}
function safeInvoke1Arg(func: Function, arg1: any): any {
try {
return func(arg1);
} catch (e) {
onUnexpectedError(e);
}
}

View file

@ -40,7 +40,7 @@ import * as modes from 'vs/editor/common/modes';
import { editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity } from 'vs/editor/common/view/editorColorRegistry';
import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';

View file

@ -32,7 +32,7 @@ import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/s
import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
import { InlineDecoration, InlineDecorationType, ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -1376,12 +1376,16 @@ abstract class ViewZonesComputer {
private readonly lineChanges: editorCommon.ILineChange[];
private readonly originalForeignVZ: IEditorWhitespace[];
private readonly originalLineHeight: number;
private readonly modifiedForeignVZ: IEditorWhitespace[];
private readonly modifiedLineHeight: number;
constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]) {
constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) {
this.lineChanges = lineChanges;
this.originalForeignVZ = originalForeignVZ;
this.originalLineHeight = originalLineHeight;
this.modifiedForeignVZ = modifiedForeignVZ;
this.modifiedLineHeight = modifiedLineHeight;
}
public getViewZones(): IEditorsZones {
@ -1456,7 +1460,7 @@ abstract class ViewZonesComputer {
stepOriginal.push({
afterLineNumber: viewZoneLineNumber,
heightInLines: modifiedForeignVZ.current.heightInLines,
heightInLines: modifiedForeignVZ.current.height / this.modifiedLineHeight,
domNode: null,
marginDomNode: marginDomNode
});
@ -1473,7 +1477,7 @@ abstract class ViewZonesComputer {
}
stepModified.push({
afterLineNumber: viewZoneLineNumber,
heightInLines: originalForeignVZ.current.heightInLines,
heightInLines: originalForeignVZ.current.height / this.originalLineHeight,
domNode: null
});
originalForeignVZ.advance();
@ -1732,7 +1736,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE
}
protected _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorsZones {
let c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ);
let c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight));
return c.getViewZones();
}
@ -1859,8 +1863,8 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE
class SideBySideViewZonesComputer extends ViewZonesComputer {
constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]) {
super(lineChanges, originalForeignVZ, modifiedForeignVZ);
constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) {
super(lineChanges, originalForeignVZ, originalLineHeight, modifiedForeignVZ, modifiedLineHeight);
}
protected _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null {
@ -2020,7 +2024,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
private readonly renderIndicators: boolean;
constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean) {
super(lineChanges, originalForeignVZ, modifiedForeignVZ);
super(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight));
this.originalModel = originalEditor.getModel()!;
this.modifiedEditorOptions = modifiedEditor.getOptions();
this.modifiedEditorTabSize = modifiedEditor.getModel()!.getOptions().tabSize;

View file

@ -4,44 +4,157 @@
*--------------------------------------------------------------------------------------------*/
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace, WhitespaceComputer } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
import * as strings from 'vs/base/common/strings';
export interface IEditorWhitespace {
readonly id: string;
readonly afterLineNumber: number;
readonly height: number;
}
/**
* An accessor that allows for whtiespace to be added, removed or changed in bulk.
*/
export interface IWhitespaceChangeAccessor {
insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string;
changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void;
removeWhitespace(id: string): void;
}
interface IPendingChange { id: string; newAfterLineNumber: number; newHeight: number; }
interface IPendingRemove { id: string; }
class PendingChanges {
private _hasPending: boolean;
private _inserts: EditorWhitespace[];
private _changes: IPendingChange[];
private _removes: IPendingRemove[];
constructor() {
this._hasPending = false;
this._inserts = [];
this._changes = [];
this._removes = [];
}
public insert(x: EditorWhitespace): void {
this._hasPending = true;
this._inserts.push(x);
}
public change(x: IPendingChange): void {
this._hasPending = true;
this._changes.push(x);
}
public remove(x: IPendingRemove): void {
this._hasPending = true;
this._removes.push(x);
}
public mustCommit(): boolean {
return this._hasPending;
}
public commit(linesLayout: LinesLayout): void {
if (!this._hasPending) {
return;
}
const inserts = this._inserts;
const changes = this._changes;
const removes = this._removes;
this._hasPending = false;
this._inserts = [];
this._changes = [];
this._removes = [];
linesLayout._commitPendingChanges(inserts, changes, removes);
}
}
export class EditorWhitespace implements IEditorWhitespace {
public id: string;
public afterLineNumber: number;
public ordinal: number;
public height: number;
public minWidth: number;
public prefixSum: number;
constructor(id: string, afterLineNumber: number, ordinal: number, height: number, minWidth: number) {
this.id = id;
this.afterLineNumber = afterLineNumber;
this.ordinal = ordinal;
this.height = height;
this.minWidth = minWidth;
this.prefixSum = 0;
}
}
/**
* Layouting of objects that take vertical space (by having a height) and push down other objects.
*
* These objects are basically either text (lines) or spaces between those lines (whitespaces).
* This provides commodity operations for working with lines that contain whitespace that pushes lines lower (vertically).
* This is written with no knowledge of an editor in mind.
*/
export class LinesLayout {
/**
* Keep track of the total number of lines.
* This is useful for doing binary searches or for doing hit-testing.
*/
private _lineCount: number;
private static INSTANCE_COUNT = 0;
/**
* The height of a line in pixels.
*/
private readonly _instanceId: string;
private readonly _pendingChanges: PendingChanges;
private _lastWhitespaceId: number;
private _arr: EditorWhitespace[];
private _prefixSumValidIndex: number;
private _minWidth: number;
private _lineCount: number;
private _lineHeight: number;
/**
* Contains whitespace information in pixels
*/
private readonly _whitespaces: WhitespaceComputer;
constructor(lineCount: number, lineHeight: number) {
this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT);
this._pendingChanges = new PendingChanges();
this._lastWhitespaceId = 0;
this._arr = [];
this._prefixSumValidIndex = -1;
this._minWidth = -1; /* marker for not being computed */
this._lineCount = lineCount;
this._lineHeight = lineHeight;
this._whitespaces = new WhitespaceComputer();
}
/**
* Find the insertion index for a new value inside a sorted array of values.
* If the value is already present in the sorted array, the insertion index will be after the already existing value.
*/
public static findInsertionIndex(arr: EditorWhitespace[], afterLineNumber: number, ordinal: number): number {
let low = 0;
let high = arr.length;
while (low < high) {
const mid = ((low + high) >>> 1);
if (afterLineNumber === arr[mid].afterLineNumber) {
if (ordinal < arr[mid].ordinal) {
high = mid;
} else {
low = mid + 1;
}
} else if (afterLineNumber < arr[mid].afterLineNumber) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
/**
* Change the height of a line in pixels.
*/
public setLineHeight(lineHeight: number): void {
this._checkPendingChanges();
this._lineHeight = lineHeight;
}
@ -51,37 +164,153 @@ export class LinesLayout {
* @param lineCount New number of lines.
*/
public onFlushed(lineCount: number): void {
this._checkPendingChanges();
this._lineCount = lineCount;
}
/**
* Insert a new whitespace of a certain height after a line number.
* The whitespace has a "sticky" characteristic.
* Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line.
*
* @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below.
* @param heightInPx The height of the whitespace, in pixels.
* @return An id that can be used later to mutate or delete the whitespace
*/
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth);
public changeWhitespace<T>(callback: (accessor: IWhitespaceChangeAccessor) => T): T {
try {
const accessor = {
insertWhitespace: (afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string => {
afterLineNumber = afterLineNumber | 0;
ordinal = ordinal | 0;
heightInPx = heightInPx | 0;
minWidth = minWidth | 0;
const id = this._instanceId + (++this._lastWhitespaceId);
this._pendingChanges.insert(new EditorWhitespace(id, afterLineNumber, ordinal, heightInPx, minWidth));
return id;
},
changeOneWhitespace: (id: string, newAfterLineNumber: number, newHeight: number): void => {
newAfterLineNumber = newAfterLineNumber | 0;
newHeight = newHeight | 0;
this._pendingChanges.change({ id, newAfterLineNumber, newHeight });
},
removeWhitespace: (id: string): void => {
this._pendingChanges.remove({ id });
}
};
return callback(accessor);
} finally {
this._pendingChanges.commit(this);
}
}
/**
* Change properties associated with a certain whitespace.
*/
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight);
public _commitPendingChanges(inserts: EditorWhitespace[], changes: IPendingChange[], removes: IPendingRemove[]): void {
if (inserts.length > 0 || removes.length > 0) {
this._minWidth = -1; /* marker for not being computed */
}
if (inserts.length + changes.length + removes.length <= 1) {
// when only one thing happened, handle it "delicately"
for (const insert of inserts) {
this._insertWhitespace(insert);
}
for (const change of changes) {
this._changeOneWhitespace(change.id, change.newAfterLineNumber, change.newHeight);
}
for (const remove of removes) {
const index = this._findWhitespaceIndex(remove.id);
if (index === -1) {
continue;
}
this._removeWhitespace(index);
}
return;
}
// simply rebuild the entire datastructure
const toRemove = new Set<string>();
for (const remove of removes) {
toRemove.add(remove.id);
}
const toChange = new Map<string, IPendingChange>();
for (const change of changes) {
toChange.set(change.id, change);
}
const applyRemoveAndChange = (whitespaces: EditorWhitespace[]): EditorWhitespace[] => {
let result: EditorWhitespace[] = [];
for (const whitespace of whitespaces) {
if (toRemove.has(whitespace.id)) {
continue;
}
if (toChange.has(whitespace.id)) {
const change = toChange.get(whitespace.id)!;
whitespace.afterLineNumber = change.newAfterLineNumber;
whitespace.height = change.newHeight;
}
result.push(whitespace);
}
return result;
};
const result = applyRemoveAndChange(this._arr).concat(applyRemoveAndChange(inserts));
result.sort((a, b) => {
if (a.afterLineNumber === b.afterLineNumber) {
return a.ordinal - b.ordinal;
}
return a.afterLineNumber - b.afterLineNumber;
});
this._arr = result;
this._prefixSumValidIndex = -1;
}
/**
* Remove an existing whitespace.
*
* @param id The whitespace to remove
* @return Returns true if the whitespace is found and it is removed.
*/
public removeWhitespace(id: string): boolean {
return this._whitespaces.removeWhitespace(id);
private _checkPendingChanges(): void {
if (this._pendingChanges.mustCommit()) {
console.warn(`Commiting pending changes before change accessor leaves due to read access.`);
this._pendingChanges.commit(this);
}
}
private _insertWhitespace(whitespace: EditorWhitespace): void {
const insertIndex = LinesLayout.findInsertionIndex(this._arr, whitespace.afterLineNumber, whitespace.ordinal);
this._arr.splice(insertIndex, 0, whitespace);
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);
}
private _findWhitespaceIndex(id: string): number {
const arr = this._arr;
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i].id === id) {
return i;
}
}
return -1;
}
private _changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void {
const index = this._findWhitespaceIndex(id);
if (index === -1) {
return;
}
if (this._arr[index].height !== newHeight) {
this._arr[index].height = newHeight;
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);
}
if (this._arr[index].afterLineNumber !== newAfterLineNumber) {
// `afterLineNumber` changed for this whitespace
// Record old whitespace
const whitespace = this._arr[index];
// Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace
this._removeWhitespace(index);
whitespace.afterLineNumber = newAfterLineNumber;
// And add it again
this._insertWhitespace(whitespace);
}
}
private _removeWhitespace(removeIndex: number): void {
this._arr.splice(removeIndex, 1);
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1);
}
/**
@ -91,8 +320,24 @@ export class LinesLayout {
* @param toLineNumber The line number at which the deletion ended, inclusive
*/
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
this._checkPendingChanges();
fromLineNumber = fromLineNumber | 0;
toLineNumber = toLineNumber | 0;
this._lineCount -= (toLineNumber - fromLineNumber + 1);
this._whitespaces.onLinesDeleted(fromLineNumber, toLineNumber);
for (let i = 0, len = this._arr.length; i < len; i++) {
const afterLineNumber = this._arr[i].afterLineNumber;
if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) {
// The line this whitespace was after has been deleted
// => move whitespace to before first deleted line
this._arr[i].afterLineNumber = fromLineNumber - 1;
} else if (afterLineNumber > toLineNumber) {
// The line this whitespace was after has been moved up
// => move whitespace up
this._arr[i].afterLineNumber -= (toLineNumber - fromLineNumber + 1);
}
}
}
/**
@ -102,8 +347,53 @@ export class LinesLayout {
* @param toLineNumber The line number at which the insertion ended, inclusive.
*/
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
this._checkPendingChanges();
fromLineNumber = fromLineNumber | 0;
toLineNumber = toLineNumber | 0;
this._lineCount += (toLineNumber - fromLineNumber + 1);
this._whitespaces.onLinesInserted(fromLineNumber, toLineNumber);
for (let i = 0, len = this._arr.length; i < len; i++) {
const afterLineNumber = this._arr[i].afterLineNumber;
if (fromLineNumber <= afterLineNumber) {
this._arr[i].afterLineNumber += (toLineNumber - fromLineNumber + 1);
}
}
}
/**
* Get the sum of all the whitespaces.
*/
public getWhitespacesTotalHeight(): number {
this._checkPendingChanges();
if (this._arr.length === 0) {
return 0;
}
return this.getWhitespacesAccumulatedHeight(this._arr.length - 1);
}
/**
* Return the sum of the heights of the whitespaces at [0..index].
* This includes the whitespace at `index`.
*
* @param index The index of the whitespace.
* @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`.
*/
public getWhitespacesAccumulatedHeight(index: number): number {
this._checkPendingChanges();
index = index | 0;
let startIndex = Math.max(0, this._prefixSumValidIndex + 1);
if (startIndex === 0) {
this._arr[0].prefixSum = this._arr[0].height;
startIndex++;
}
for (let i = startIndex; i <= index; i++) {
this._arr[i].prefixSum = this._arr[i - 1].prefixSum + this._arr[i].height;
}
this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index);
return this._arr[index].prefixSum;
}
/**
@ -112,11 +402,81 @@ export class LinesLayout {
* @return The sum of heights for all objects.
*/
public getLinesTotalHeight(): number {
let linesHeight = this._lineHeight * this._lineCount;
let whitespacesHeight = this._whitespaces.getTotalHeight();
this._checkPendingChanges();
const linesHeight = this._lineHeight * this._lineCount;
const whitespacesHeight = this.getWhitespacesTotalHeight();
return linesHeight + whitespacesHeight;
}
/**
* Returns the accumulated height of whitespaces before the given line number.
*
* @param lineNumber The line number
*/
public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
this._checkPendingChanges();
lineNumber = lineNumber | 0;
const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
if (lastWhitespaceBeforeLineNumber === -1) {
return 0;
}
return this.getWhitespacesAccumulatedHeight(lastWhitespaceBeforeLineNumber);
}
private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
// Find the whitespace before line number
const arr = this._arr;
let low = 0;
let high = arr.length - 1;
while (low <= high) {
const delta = (high - low) | 0;
const halfDelta = (delta / 2) | 0;
const mid = (low + halfDelta) | 0;
if (arr[mid].afterLineNumber < lineNumber) {
if (mid + 1 >= arr.length || arr[mid + 1].afterLineNumber >= lineNumber) {
return mid;
} else {
low = (mid + 1) | 0;
}
} else {
high = (mid - 1) | 0;
}
}
return -1;
}
private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
const firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1;
if (firstWhitespaceAfterLineNumber < this._arr.length) {
return firstWhitespaceAfterLineNumber;
}
return -1;
}
/**
* Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`.
* @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found.
*/
public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number {
this._checkPendingChanges();
lineNumber = lineNumber | 0;
return this._findFirstWhitespaceAfterLineNumber(lineNumber);
}
/**
* Get the vertical offset (the sum of heights for all objects above) a certain line number.
*
@ -124,6 +484,7 @@ export class LinesLayout {
* @return The sum of heights for all objects above `lineNumber`.
*/
public getVerticalOffsetForLineNumber(lineNumber: number): number {
this._checkPendingChanges();
lineNumber = lineNumber | 0;
let previousLinesHeight: number;
@ -133,36 +494,40 @@ export class LinesLayout {
previousLinesHeight = 0;
}
let previousWhitespacesHeight = this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber);
const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber);
return previousLinesHeight + previousWhitespacesHeight;
}
/**
* Returns the accumulated height of whitespaces before the given line number.
*
* @param lineNumber The line number
*/
public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
return this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber);
}
/**
* Returns if there is any whitespace in the document.
*/
public hasWhitespace(): boolean {
return this._whitespaces.getCount() > 0;
this._checkPendingChanges();
return this.getWhitespacesCount() > 0;
}
/**
* The maximum min width for all whitespaces.
*/
public getWhitespaceMinWidth(): number {
return this._whitespaces.getMinWidth();
this._checkPendingChanges();
if (this._minWidth === -1) {
let minWidth = 0;
for (let i = 0, len = this._arr.length; i < len; i++) {
minWidth = Math.max(minWidth, this._arr[i].minWidth);
}
this._minWidth = minWidth;
}
return this._minWidth;
}
/**
* Check if `verticalOffset` is below all lines.
*/
public isAfterLines(verticalOffset: number): boolean {
let totalHeight = this.getLinesTotalHeight();
this._checkPendingChanges();
const totalHeight = this.getLinesTotalHeight();
return verticalOffset > totalHeight;
}
@ -175,6 +540,7 @@ export class LinesLayout {
* @return The line number at or after vertical offset `verticalOffset`.
*/
public getLineNumberAtOrAfterVerticalOffset(verticalOffset: number): number {
this._checkPendingChanges();
verticalOffset = verticalOffset | 0;
if (verticalOffset < 0) {
@ -187,9 +553,9 @@ export class LinesLayout {
let maxLineNumber = linesCount;
while (minLineNumber < maxLineNumber) {
let midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0;
const midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0;
let midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0;
const midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0;
if (verticalOffset >= midLineNumberVerticalOffset + lineHeight) {
// vertical offset is after mid line number
@ -218,6 +584,7 @@ export class LinesLayout {
* @return A structure describing the lines positioned between `verticalOffset1` and `verticalOffset2`.
*/
public getLinesViewportData(verticalOffset1: number, verticalOffset2: number): IPartialViewLinesViewportData {
this._checkPendingChanges();
verticalOffset1 = verticalOffset1 | 0;
verticalOffset2 = verticalOffset2 | 0;
const lineHeight = this._lineHeight;
@ -230,8 +597,8 @@ export class LinesLayout {
let endLineNumber = this._lineCount | 0;
// Also keep track of what whitespace we've got
let whitespaceIndex = this._whitespaces.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0;
const whitespaceCount = this._whitespaces.getCount() | 0;
let whitespaceIndex = this.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0;
const whitespaceCount = this.getWhitespacesCount() | 0;
let currentWhitespaceHeight: number;
let currentWhitespaceAfterLineNumber: number;
@ -240,8 +607,8 @@ export class LinesLayout {
currentWhitespaceAfterLineNumber = endLineNumber + 1;
currentWhitespaceHeight = 0;
} else {
currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
}
let currentVerticalOffset = startLineNumberVerticalOffset;
@ -258,7 +625,7 @@ export class LinesLayout {
currentLineRelativeOffset -= bigNumbersDelta;
}
let linesOffsets: number[] = [];
const linesOffsets: number[] = [];
const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2;
let centeredLineNumber = -1;
@ -267,8 +634,8 @@ export class LinesLayout {
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
if (centeredLineNumber === -1) {
let currentLineTop = currentVerticalOffset;
let currentLineBottom = currentVerticalOffset + lineHeight;
const currentLineTop = currentVerticalOffset;
const currentLineBottom = currentVerticalOffset + lineHeight;
if ((currentLineTop <= verticalCenter && verticalCenter < currentLineBottom) || currentLineTop > verticalCenter) {
centeredLineNumber = lineNumber;
}
@ -291,8 +658,8 @@ export class LinesLayout {
if (whitespaceIndex >= whitespaceCount) {
currentWhitespaceAfterLineNumber = endLineNumber + 1;
} else {
currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
}
}
@ -335,9 +702,10 @@ export class LinesLayout {
}
public getVerticalOffsetForWhitespaceIndex(whitespaceIndex: number): number {
this._checkPendingChanges();
whitespaceIndex = whitespaceIndex | 0;
let afterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex);
const afterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex);
let previousLinesHeight: number;
if (afterLineNumber >= 1) {
@ -348,7 +716,7 @@ export class LinesLayout {
let previousWhitespacesHeight: number;
if (whitespaceIndex > 0) {
previousWhitespacesHeight = this._whitespaces.getAccumulatedHeight(whitespaceIndex - 1);
previousWhitespacesHeight = this.getWhitespacesAccumulatedHeight(whitespaceIndex - 1);
} else {
previousWhitespacesHeight = 0;
}
@ -356,30 +724,28 @@ export class LinesLayout {
}
public getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset: number): number {
this._checkPendingChanges();
verticalOffset = verticalOffset | 0;
let midWhitespaceIndex: number,
minWhitespaceIndex = 0,
maxWhitespaceIndex = this._whitespaces.getCount() - 1,
midWhitespaceVerticalOffset: number,
midWhitespaceHeight: number;
let minWhitespaceIndex = 0;
let maxWhitespaceIndex = this.getWhitespacesCount() - 1;
if (maxWhitespaceIndex < 0) {
return -1;
}
// Special case: nothing to be found
let maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex);
let maxWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(maxWhitespaceIndex);
const maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex);
const maxWhitespaceHeight = this.getHeightForWhitespaceIndex(maxWhitespaceIndex);
if (verticalOffset >= maxWhitespaceVerticalOffset + maxWhitespaceHeight) {
return -1;
}
while (minWhitespaceIndex < maxWhitespaceIndex) {
midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2);
const midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2);
midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex);
midWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(midWhitespaceIndex);
const midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex);
const midWhitespaceHeight = this.getHeightForWhitespaceIndex(midWhitespaceIndex);
if (verticalOffset >= midWhitespaceVerticalOffset + midWhitespaceHeight) {
// vertical offset is after whitespace
@ -402,27 +768,28 @@ export class LinesLayout {
* @return Precisely the whitespace that is layouted at `verticaloffset` or null.
*/
public getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null {
this._checkPendingChanges();
verticalOffset = verticalOffset | 0;
let candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset);
const candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset);
if (candidateIndex < 0) {
return null;
}
if (candidateIndex >= this._whitespaces.getCount()) {
if (candidateIndex >= this.getWhitespacesCount()) {
return null;
}
let candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex);
const candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex);
if (candidateTop > verticalOffset) {
return null;
}
let candidateHeight = this._whitespaces.getHeightForWhitespaceIndex(candidateIndex);
let candidateId = this._whitespaces.getIdForWhitespaceIndex(candidateIndex);
let candidateAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(candidateIndex);
const candidateHeight = this.getHeightForWhitespaceIndex(candidateIndex);
const candidateId = this.getIdForWhitespaceIndex(candidateIndex);
const candidateAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(candidateIndex);
return {
id: candidateId,
@ -440,11 +807,12 @@ export class LinesLayout {
* @return An array with all the whitespaces in the viewport. If no whitespace is in viewport, the array is empty.
*/
public getWhitespaceViewportData(verticalOffset1: number, verticalOffset2: number): IViewWhitespaceViewportData[] {
this._checkPendingChanges();
verticalOffset1 = verticalOffset1 | 0;
verticalOffset2 = verticalOffset2 | 0;
let startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1);
let endIndex = this._whitespaces.getCount() - 1;
const startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1);
const endIndex = this.getWhitespacesCount() - 1;
if (startIndex < 0) {
return [];
@ -452,15 +820,15 @@ export class LinesLayout {
let result: IViewWhitespaceViewportData[] = [];
for (let i = startIndex; i <= endIndex; i++) {
let top = this.getVerticalOffsetForWhitespaceIndex(i);
let height = this._whitespaces.getHeightForWhitespaceIndex(i);
const top = this.getVerticalOffsetForWhitespaceIndex(i);
const height = this.getHeightForWhitespaceIndex(i);
if (top >= verticalOffset2) {
break;
}
result.push({
id: this._whitespaces.getIdForWhitespaceIndex(i),
afterLineNumber: this._whitespaces.getAfterLineNumberForWhitespaceIndex(i),
id: this.getIdForWhitespaceIndex(i),
afterLineNumber: this.getAfterLineNumberForWhitespaceIndex(i),
verticalOffset: top,
height: height
});
@ -473,6 +841,54 @@ export class LinesLayout {
* Get all whitespaces.
*/
public getWhitespaces(): IEditorWhitespace[] {
return this._whitespaces.getWhitespaces(this._lineHeight);
this._checkPendingChanges();
return this._arr.slice(0);
}
/**
* The number of whitespaces.
*/
public getWhitespacesCount(): number {
this._checkPendingChanges();
return this._arr.length;
}
/**
* Get the `id` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `id` of whitespace at `index`.
*/
public getIdForWhitespaceIndex(index: number): string {
this._checkPendingChanges();
index = index | 0;
return this._arr[index].id;
}
/**
* Get the `afterLineNumber` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `afterLineNumber` of whitespace at `index`.
*/
public getAfterLineNumberForWhitespaceIndex(index: number): number {
this._checkPendingChanges();
index = index | 0;
return this._arr[index].afterLineNumber;
}
/**
* Get the `height` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `height` of whitespace at `index`.
*/
public getHeightForWhitespaceIndex(index: number): number {
this._checkPendingChanges();
index = index | 0;
return this._arr[index].height;
}
}

View file

@ -8,9 +8,8 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout';
import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel';
const SMOOTH_SCROLLING_TIME = 125;
@ -194,15 +193,8 @@ export class ViewLayout extends Disposable implements IViewLayout {
}
// ---- IVerticalLayoutProvider
public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string {
return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height, minWidth);
}
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight);
}
public removeWhitespace(id: string): boolean {
return this._linesLayout.removeWhitespace(id);
public changeWhitespace<T>(callback: (accessor: IWhitespaceChangeAccessor) => T): T {
return this._linesLayout.changeWhitespace(callback);
}
public getVerticalOffsetForLineNumber(lineNumber: number): number {
return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber);

View file

@ -1,493 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
export interface IEditorWhitespace {
readonly id: string;
readonly afterLineNumber: number;
readonly heightInLines: number;
}
/**
* Represent whitespaces in between lines and provide fast CRUD management methods.
* The whitespaces are sorted ascending by `afterLineNumber`.
*/
export class WhitespaceComputer {
private static INSTANCE_COUNT = 0;
private readonly _instanceId: string;
/**
* heights[i] is the height in pixels for whitespace at index i
*/
private readonly _heights: number[];
/**
* minWidths[i] is the min width in pixels for whitespace at index i
*/
private readonly _minWidths: number[];
/**
* afterLineNumbers[i] is the line number whitespace at index i is after
*/
private readonly _afterLineNumbers: number[];
/**
* ordinals[i] is the orinal of the whitespace at index i
*/
private readonly _ordinals: number[];
/**
* prefixSum[i] = SUM(heights[j]), 1 <= j <= i
*/
private readonly _prefixSum: number[];
/**
* prefixSum[i], 1 <= i <= prefixSumValidIndex can be trusted
*/
private _prefixSumValidIndex: number;
/**
* ids[i] is the whitespace id of whitespace at index i
*/
private readonly _ids: string[];
/**
* index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members)
*/
private readonly _whitespaceId2Index: {
[id: string]: number;
};
/**
* last whitespace id issued
*/
private _lastWhitespaceId: number;
private _minWidth: number;
constructor() {
this._instanceId = strings.singleLetterHash(++WhitespaceComputer.INSTANCE_COUNT);
this._heights = [];
this._minWidths = [];
this._ids = [];
this._afterLineNumbers = [];
this._ordinals = [];
this._prefixSum = [];
this._prefixSumValidIndex = -1;
this._whitespaceId2Index = {};
this._lastWhitespaceId = 0;
this._minWidth = -1; /* marker for not being computed */
}
/**
* Find the insertion index for a new value inside a sorted array of values.
* If the value is already present in the sorted array, the insertion index will be after the already existing value.
*/
public static findInsertionIndex(sortedArray: number[], value: number, ordinals: number[], valueOrdinal: number): number {
let low = 0;
let high = sortedArray.length;
while (low < high) {
let mid = ((low + high) >>> 1);
if (value === sortedArray[mid]) {
if (valueOrdinal < ordinals[mid]) {
high = mid;
} else {
low = mid + 1;
}
} else if (value < sortedArray[mid]) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
/**
* Insert a new whitespace of a certain height after a line number.
* The whitespace has a "sticky" characteristic.
* Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line.
*
* @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below.
* @param heightInPx The height of the whitespace, in pixels.
* @return An id that can be used later to mutate or delete the whitespace
*/
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
afterLineNumber = afterLineNumber | 0;
ordinal = ordinal | 0;
heightInPx = heightInPx | 0;
minWidth = minWidth | 0;
let id = this._instanceId + (++this._lastWhitespaceId);
let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, afterLineNumber, this._ordinals, ordinal);
this._insertWhitespaceAtIndex(id, insertionIndex, afterLineNumber, ordinal, heightInPx, minWidth);
this._minWidth = -1; /* marker for not being computed */
return id;
}
private _insertWhitespaceAtIndex(id: string, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void {
insertIndex = insertIndex | 0;
afterLineNumber = afterLineNumber | 0;
ordinal = ordinal | 0;
heightInPx = heightInPx | 0;
minWidth = minWidth | 0;
this._heights.splice(insertIndex, 0, heightInPx);
this._minWidths.splice(insertIndex, 0, minWidth);
this._ids.splice(insertIndex, 0, id);
this._afterLineNumbers.splice(insertIndex, 0, afterLineNumber);
this._ordinals.splice(insertIndex, 0, ordinal);
this._prefixSum.splice(insertIndex, 0, 0);
let keys = Object.keys(this._whitespaceId2Index);
for (let i = 0, len = keys.length; i < len; i++) {
let sid = keys[i];
let oldIndex = this._whitespaceId2Index[sid];
if (oldIndex >= insertIndex) {
this._whitespaceId2Index[sid] = oldIndex + 1;
}
}
this._whitespaceId2Index[id] = insertIndex;
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);
}
/**
* Change properties associated with a certain whitespace.
*/
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
newAfterLineNumber = newAfterLineNumber | 0;
newHeight = newHeight | 0;
let hasChanges = false;
hasChanges = this.changeWhitespaceHeight(id, newHeight) || hasChanges;
hasChanges = this.changeWhitespaceAfterLineNumber(id, newAfterLineNumber) || hasChanges;
return hasChanges;
}
/**
* Change the height of an existing whitespace
*
* @param id The whitespace to change
* @param newHeightInPx The new height of the whitespace, in pixels
* @return Returns true if the whitespace is found and if the new height is different than the old height
*/
public changeWhitespaceHeight(id: string, newHeightInPx: number): boolean {
newHeightInPx = newHeightInPx | 0;
if (this._whitespaceId2Index.hasOwnProperty(id)) {
let index = this._whitespaceId2Index[id];
if (this._heights[index] !== newHeightInPx) {
this._heights[index] = newHeightInPx;
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);
return true;
}
}
return false;
}
/**
* Change the line number after which an existing whitespace flows.
*
* @param id The whitespace to change
* @param newAfterLineNumber The new line number the whitespace will follow
* @return Returns true if the whitespace is found and if the new line number is different than the old line number
*/
public changeWhitespaceAfterLineNumber(id: string, newAfterLineNumber: number): boolean {
newAfterLineNumber = newAfterLineNumber | 0;
if (this._whitespaceId2Index.hasOwnProperty(id)) {
let index = this._whitespaceId2Index[id];
if (this._afterLineNumbers[index] !== newAfterLineNumber) {
// `afterLineNumber` changed for this whitespace
// Record old ordinal
let ordinal = this._ordinals[index];
// Record old height
let heightInPx = this._heights[index];
// Record old min width
let minWidth = this._minWidths[index];
// Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace
this.removeWhitespace(id);
// And add it again
let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, newAfterLineNumber, this._ordinals, ordinal);
this._insertWhitespaceAtIndex(id, insertionIndex, newAfterLineNumber, ordinal, heightInPx, minWidth);
return true;
}
}
return false;
}
/**
* Remove an existing whitespace.
*
* @param id The whitespace to remove
* @return Returns true if the whitespace is found and it is removed.
*/
public removeWhitespace(id: string): boolean {
if (this._whitespaceId2Index.hasOwnProperty(id)) {
let index = this._whitespaceId2Index[id];
delete this._whitespaceId2Index[id];
this._removeWhitespaceAtIndex(index);
this._minWidth = -1; /* marker for not being computed */
return true;
}
return false;
}
private _removeWhitespaceAtIndex(removeIndex: number): void {
removeIndex = removeIndex | 0;
this._heights.splice(removeIndex, 1);
this._minWidths.splice(removeIndex, 1);
this._ids.splice(removeIndex, 1);
this._afterLineNumbers.splice(removeIndex, 1);
this._ordinals.splice(removeIndex, 1);
this._prefixSum.splice(removeIndex, 1);
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1);
let keys = Object.keys(this._whitespaceId2Index);
for (let i = 0, len = keys.length; i < len; i++) {
let sid = keys[i];
let oldIndex = this._whitespaceId2Index[sid];
if (oldIndex >= removeIndex) {
this._whitespaceId2Index[sid] = oldIndex - 1;
}
}
}
/**
* Notify the computer that lines have been deleted (a continuous zone of lines).
* This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic.
*
* @param fromLineNumber The line number at which the deletion started, inclusive
* @param toLineNumber The line number at which the deletion ended, inclusive
*/
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
fromLineNumber = fromLineNumber | 0;
toLineNumber = toLineNumber | 0;
for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) {
let afterLineNumber = this._afterLineNumbers[i];
if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) {
// The line this whitespace was after has been deleted
// => move whitespace to before first deleted line
this._afterLineNumbers[i] = fromLineNumber - 1;
} else if (afterLineNumber > toLineNumber) {
// The line this whitespace was after has been moved up
// => move whitespace up
this._afterLineNumbers[i] -= (toLineNumber - fromLineNumber + 1);
}
}
}
/**
* Notify the computer that lines have been inserted (a continuous zone of lines).
* This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic.
*
* @param fromLineNumber The line number at which the insertion started, inclusive
* @param toLineNumber The line number at which the insertion ended, inclusive.
*/
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
fromLineNumber = fromLineNumber | 0;
toLineNumber = toLineNumber | 0;
for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) {
let afterLineNumber = this._afterLineNumbers[i];
if (fromLineNumber <= afterLineNumber) {
this._afterLineNumbers[i] += (toLineNumber - fromLineNumber + 1);
}
}
}
/**
* Get the sum of all the whitespaces.
*/
public getTotalHeight(): number {
if (this._heights.length === 0) {
return 0;
}
return this.getAccumulatedHeight(this._heights.length - 1);
}
/**
* Return the sum of the heights of the whitespaces at [0..index].
* This includes the whitespace at `index`.
*
* @param index The index of the whitespace.
* @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`.
*/
public getAccumulatedHeight(index: number): number {
index = index | 0;
let startIndex = Math.max(0, this._prefixSumValidIndex + 1);
if (startIndex === 0) {
this._prefixSum[0] = this._heights[0];
startIndex++;
}
for (let i = startIndex; i <= index; i++) {
this._prefixSum[i] = this._prefixSum[i - 1] + this._heights[i];
}
this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index);
return this._prefixSum[index];
}
/**
* Find all whitespaces with `afterLineNumber` < `lineNumber` and return the sum of their heights.
*
* @param lineNumber The line number whitespaces should be before.
* @return The sum of the heights of the whitespaces before `lineNumber`.
*/
public getAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
if (lastWhitespaceBeforeLineNumber === -1) {
return 0;
}
return this.getAccumulatedHeight(lastWhitespaceBeforeLineNumber);
}
private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
// Find the whitespace before line number
let afterLineNumbers = this._afterLineNumbers;
let low = 0;
let high = afterLineNumbers.length - 1;
while (low <= high) {
let delta = (high - low) | 0;
let halfDelta = (delta / 2) | 0;
let mid = (low + halfDelta) | 0;
if (afterLineNumbers[mid] < lineNumber) {
if (mid + 1 >= afterLineNumbers.length || afterLineNumbers[mid + 1] >= lineNumber) {
return mid;
} else {
low = (mid + 1) | 0;
}
} else {
high = (mid - 1) | 0;
}
}
return -1;
}
private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
let firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1;
if (firstWhitespaceAfterLineNumber < this._heights.length) {
return firstWhitespaceAfterLineNumber;
}
return -1;
}
/**
* Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`.
* @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found.
*/
public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
return this._findFirstWhitespaceAfterLineNumber(lineNumber);
}
/**
* The number of whitespaces.
*/
public getCount(): number {
return this._heights.length;
}
/**
* The maximum min width for all whitespaces.
*/
public getMinWidth(): number {
if (this._minWidth === -1) {
let minWidth = 0;
for (let i = 0, len = this._minWidths.length; i < len; i++) {
minWidth = Math.max(minWidth, this._minWidths[i]);
}
this._minWidth = minWidth;
}
return this._minWidth;
}
/**
* Get the `afterLineNumber` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `afterLineNumber` of whitespace at `index`.
*/
public getAfterLineNumberForWhitespaceIndex(index: number): number {
index = index | 0;
return this._afterLineNumbers[index];
}
/**
* Get the `id` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `id` of whitespace at `index`.
*/
public getIdForWhitespaceIndex(index: number): string {
index = index | 0;
return this._ids[index];
}
/**
* Get the `height` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `height` of whitespace at `index`.
*/
public getHeightForWhitespaceIndex(index: number): number {
index = index | 0;
return this._heights[index];
}
/**
* Get all whitespaces.
*/
public getWhitespaces(deviceLineHeight: number): IEditorWhitespace[] {
deviceLineHeight = deviceLineHeight | 0;
let result: IEditorWhitespace[] = [];
for (let i = 0; i < this._heights.length; i++) {
result.push({
id: this._ids[i],
afterLineNumber: this._afterLineNumbers[i],
heightInLines: this._heights[i] / deviceLineHeight
});
}
return result;
}
}

View file

@ -13,7 +13,7 @@ import { INewScrollPosition } from 'vs/editor/common/editorCommon';
import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions } from 'vs/editor/common/model';
import { IViewEventListener } from 'vs/editor/common/view/viewEvents';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout';
import { ITheme } from 'vs/platform/theme/common/themeService';
export interface IViewWhitespaceViewportData {
@ -69,20 +69,8 @@ export interface IViewLayout {
getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null;
// --------------- Begin vertical whitespace management
changeWhitespace<T>(callback: (accessor: IWhitespaceChangeAccessor) => T): T;
/**
* Reserve rendering space.
* @return an identifier that can be later used to remove or change the whitespace.
*/
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string;
/**
* Change the properties of a whitespace.
*/
changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean;
/**
* Remove rendering space
*/
removeWhitespace(id: string): boolean;
/**
* Get the layout information for whitespaces currently in the viewport
*/

View file

@ -14,7 +14,6 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
import { editorHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
const $ = dom.$;
@ -151,7 +150,7 @@ class SaturationBox extends Disposable {
this.layout();
this._register(dom.addDisposableListener(this.domNode, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_DOWN : dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e)));
this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e)));
this._register(this.model.onDidChangeColor(this.onDidChangeColor, this));
this.monitor = null;
}
@ -166,7 +165,7 @@ class SaturationBox extends Disposable {
this.monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangePosition(event.posx - origin.left, event.posy - origin.top), () => null);
const mouseUpListener = dom.addDisposableListener(document, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_UP : dom.EventType.MOUSE_UP, () => {
const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => {
this._onColorFlushed.fire();
mouseUpListener.dispose();
if (this.monitor) {
@ -251,7 +250,7 @@ abstract class Strip extends Disposable {
this.slider = dom.append(this.domNode, $('.slider'));
this.slider.style.top = `0px`;
this._register(dom.addDisposableListener(this.domNode, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_DOWN : dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e)));
this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e)));
this.layout();
}
@ -273,7 +272,7 @@ abstract class Strip extends Disposable {
monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangeTop(event.posy - origin.top), () => null);
const mouseUpListener = dom.addDisposableListener(document, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_UP : dom.EventType.MOUSE_UP, () => {
const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => {
this._onColorFlushed.fire();
mouseUpListener.dispose();
monitor.stopMonitoring(true);

View file

@ -8,7 +8,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Disposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { Position } from 'vs/editor/common/core/position';
@ -50,7 +50,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE
this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
this._register(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e)));
this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e)));
this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
@ -143,7 +143,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE
}
}
private _onEditorMouseDrop(mouseEvent: IEditorMouseEvent): void {
private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void {
if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) {
let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);

View file

@ -65,7 +65,6 @@
.monaco-editor .find-widget .monaco-inputbox .input {
background-color: transparent;
/* Style to compensate for //winjs */
min-height: 0;
}

View file

@ -423,7 +423,7 @@ export class FoldingController extends Disposable implements IEditorContribution
if (iconClicked || isCollapsed) {
let toToggle = [region];
if (e.event.middleButton || e.event.shiftKey) {
toToggle.push(...foldingModel.getRegionsInside(region, r => r.isCollapsed === isCollapsed));
toToggle.push(...foldingModel.getRegionsInside(region, (r: FoldingRegion) => r.isCollapsed === isCollapsed));
}
foldingModel.toggleCollapseState(toToggle);
this.reveal({ lineNumber, column: 1 });

View file

@ -204,7 +204,7 @@ export class FoldingModel {
return null;
}
getRegionsInside(region: FoldingRegion | null, filter?: (r: FoldingRegion, level?: number) => boolean): FoldingRegion[] {
getRegionsInside(region: FoldingRegion | null, filter?: RegionFilter | RegionFilterWithLevel): FoldingRegion[] {
let result: FoldingRegion[] = [];
let index = region ? region.regionIndex + 1 : 0;
let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE;
@ -229,7 +229,7 @@ export class FoldingModel {
for (let i = index, len = this._regions.length; i < len; i++) {
let current = this._regions.toRegion(i);
if (this._regions.getStartLineNumber(i) < endLineNumber) {
if (!filter || filter(current)) {
if (!filter || (filter as RegionFilter)(current)) {
result.push(current);
}
} else {
@ -242,6 +242,10 @@ export class FoldingModel {
}
type RegionFilter = (r: FoldingRegion) => boolean;
type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean;
/**
* Collapse or expand the regions at the given locations
* @param levels The number of levels. Use 1 to only impact the regions at the location, use Number.MAX_VALUE for all levels.

View file

@ -119,8 +119,9 @@ abstract class SymbolNavigationAction extends EditorAction {
} else {
const next = model.firstReference()!;
const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide);
if (targetEditor && model.references.length > 1 && gotoLocation === 'gotoAndPeek') {
const peek = model.references.length > 1 && gotoLocation === 'gotoAndPeek';
const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide, !peek);
if (peek && targetEditor) {
this._openInPeek(targetEditor, model);
} else {
model.dispose();
@ -134,7 +135,7 @@ abstract class SymbolNavigationAction extends EditorAction {
}
}
private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise<ICodeEditor | null> {
private async _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean, highlight: boolean): Promise<ICodeEditor | undefined> {
// range is the target-selection-range when we have one
// and the the fallback is the 'full' range
let range: IRange | undefined = undefined;
@ -145,13 +146,29 @@ abstract class SymbolNavigationAction extends EditorAction {
range = reference.range;
}
return editorService.openCodeEditor({
const targetEditor = await editorService.openCodeEditor({
resource: reference.uri,
options: {
selection: Range.collapseToStart(range),
revealInCenterIfOutsideViewport: true
}
}, editor, sideBySide);
if (!targetEditor) {
return undefined;
}
if (highlight) {
const modelNow = targetEditor.getModel();
const ids = targetEditor.deltaDecorations([], [{ range, options: { className: 'rangeHighlight' } }]);
setTimeout(() => {
if (targetEditor.getModel() === modelNow) {
targetEditor.deltaDecorations(ids, []);
}
}, 350);
}
return targetEditor;
}
private _openInPeek(target: ICodeEditor, model: ReferencesModel) {

View file

@ -207,7 +207,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
private readonly _themeService: IThemeService,
private readonly _keybindingService: IKeybindingService,
private readonly _modeService: IModeService,
private readonly _openerService: IOpenerService | null = NullOpenerService,
private readonly _openerService: IOpenerService = NullOpenerService,
) {
super(ModesContentHoverWidget.ID, editor);

View file

@ -97,7 +97,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget {
constructor(
editor: ICodeEditor,
modeService: IModeService,
openerService: IOpenerService | null = NullOpenerService,
openerService: IOpenerService = NullOpenerService,
) {
super(ModesGlyphHoverWidget.ID, editor);

View file

@ -44,17 +44,9 @@ export class Link implements ILink {
return this._link.tooltip;
}
resolve(token: CancellationToken): Promise<URI> {
async resolve(token: CancellationToken): Promise<URI | string> {
if (this._link.url) {
try {
if (typeof this._link.url === 'string') {
return Promise.resolve(URI.parse(this._link.url));
} else {
return Promise.resolve(this._link.url);
}
} catch (e) {
return Promise.reject(new Error('invalid'));
}
return this._link.url;
}
if (typeof this._provider.resolveLink === 'function') {

View file

@ -7,7 +7,6 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
import { IModeService } from 'vs/editor/common/services/modeService';
import { URI } from 'vs/base/common/uri';
import { onUnexpectedError } from 'vs/base/common/errors';
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@ -29,7 +28,7 @@ export class MarkdownRenderer extends Disposable {
constructor(
private readonly _editor: ICodeEditor,
@IModeService private readonly _modeService: IModeService,
@optional(IOpenerService) private readonly _openerService: IOpenerService | null = NullOpenerService,
@optional(IOpenerService) private readonly _openerService: IOpenerService = NullOpenerService,
) {
super();
}
@ -64,15 +63,7 @@ export class MarkdownRenderer extends Disposable {
codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(),
actionHandler: {
callback: (content) => {
let uri: URI | undefined;
try {
uri = URI.parse(content);
} catch {
// ignore
}
if (uri && this._openerService) {
this._openerService.open(uri, { fromUserGesture: true }).catch(onUnexpectedError);
}
this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError);
},
disposeables
}

View file

@ -89,7 +89,7 @@ export module StaticServices {
let _all: LazyStaticService<any>[] = [];
function define<T>(serviceId: ServiceIdentifier<T>, factory: (overrides: IEditorOverrideServices) => T): LazyStaticService<T> {
function define<T>(serviceId: ServiceIdentifier<T>, factory: (overrides: IEditorOverrideServices | undefined) => T): LazyStaticService<T> {
let r = new LazyStaticService(serviceId, factory);
_all.push(r);
return r;

View file

@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { OpenerService } from 'vs/editor/browser/services/openerService';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands';
import { matchesScheme } from 'vs/platform/opener/common/opener';
suite('OpenerService', function () {
const editorService = new TestCodeEditorService();
@ -28,27 +29,27 @@ suite('OpenerService', function () {
lastCommand = undefined;
});
test('delegate to editorService, scheme:///fff', function () {
test('delegate to editorService, scheme:///fff', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('another:///somepath'));
await openerService.open(URI.parse('another:///somepath'));
assert.equal(editorService.lastInput!.options!.selection, undefined);
});
test('delegate to editorService, scheme:///fff#L123', function () {
test('delegate to editorService, scheme:///fff#L123', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('file:///somepath#L23'));
await openerService.open(URI.parse('file:///somepath#L23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.equal(editorService.lastInput!.resource.fragment, '');
openerService.open(URI.parse('another:///somepath#L23'));
await openerService.open(URI.parse('another:///somepath#L23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1);
openerService.open(URI.parse('another:///somepath#L23,45'));
await openerService.open(URI.parse('another:///somepath#L23,45'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
@ -56,17 +57,17 @@ suite('OpenerService', function () {
assert.equal(editorService.lastInput!.resource.fragment, '');
});
test('delegate to editorService, scheme:///fff#123,123', function () {
test('delegate to editorService, scheme:///fff#123,123', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('file:///somepath#23'));
await openerService.open(URI.parse('file:///somepath#23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.equal(editorService.lastInput!.resource.fragment, '');
openerService.open(URI.parse('file:///somepath#23,45'));
await openerService.open(URI.parse('file:///somepath#23,45'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
@ -74,22 +75,22 @@ suite('OpenerService', function () {
assert.equal(editorService.lastInput!.resource.fragment, '');
});
test('delegate to commandsService, command:someid', function () {
test('delegate to commandsService, command:someid', async function () {
const openerService = new OpenerService(editorService, commandService);
const id = `aCommand${Math.random()}`;
CommandsRegistry.registerCommand(id, function () { });
openerService.open(URI.parse('command:' + id));
await openerService.open(URI.parse('command:' + id));
assert.equal(lastCommand!.id, id);
assert.equal(lastCommand!.args.length, 0);
openerService.open(URI.parse('command:' + id).with({ query: '123' }));
await openerService.open(URI.parse('command:' + id).with({ query: '123' }));
assert.equal(lastCommand!.id, id);
assert.equal(lastCommand!.args.length, 1);
assert.equal(lastCommand!.args[0], '123');
openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) }));
await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) }));
assert.equal(lastCommand!.id, id);
assert.equal(lastCommand!.args.length, 2);
assert.equal(lastCommand!.args[0], 12);
@ -199,4 +200,18 @@ suite('OpenerService', function () {
assert.equal(v1, 2);
assert.equal(v2, 0);
});
test('matchesScheme', function () {
assert.ok(matchesScheme('https://microsoft.com', 'https'));
assert.ok(matchesScheme('http://microsoft.com', 'http'));
assert.ok(matchesScheme('hTTPs://microsoft.com', 'https'));
assert.ok(matchesScheme('httP://microsoft.com', 'http'));
assert.ok(matchesScheme(URI.parse('https://microsoft.com'), 'https'));
assert.ok(matchesScheme(URI.parse('http://microsoft.com'), 'http'));
assert.ok(matchesScheme(URI.parse('hTTPs://microsoft.com'), 'https'));
assert.ok(matchesScheme(URI.parse('httP://microsoft.com'), 'http'));
assert.ok(!matchesScheme(URI.parse('https://microsoft.com'), 'http'));
assert.ok(!matchesScheme(URI.parse('htt://microsoft.com'), 'http'));
assert.ok(!matchesScheme(URI.parse('z://microsoft.com'), 'http'));
});
});

View file

@ -3,10 +3,28 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout';
import { LinesLayout, EditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
suite('Editor ViewLayout - LinesLayout', () => {
function insertWhitespace(linesLayout: LinesLayout, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
return linesLayout.changeWhitespace((accessor) => {
return accessor.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth);
});
}
function changeOneWhitespace(linesLayout: LinesLayout, id: string, newAfterLineNumber: number, newHeight: number): void {
linesLayout.changeWhitespace((accessor) => {
accessor.changeOneWhitespace(id, newAfterLineNumber, newHeight);
});
}
function removeWhitespace(linesLayout: LinesLayout, id: string): void {
linesLayout.changeWhitespace((accessor) => {
accessor.removeWhitespace(id);
});
}
test('LinesLayout 1', () => {
// Start off with 10 lines
@ -39,7 +57,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(29), 3);
// Add whitespace of height 5px after 2nd line
linesLayout.insertWhitespace(2, 0, 5, 0);
insertWhitespace(linesLayout, 2, 0, 5, 0);
// lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
// whitespace: a(2,5)
assert.equal(linesLayout.getLinesTotalHeight(), 105);
@ -63,8 +81,8 @@ suite('Editor ViewLayout - LinesLayout', () => {
assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(105), 10);
// Add two more whitespaces of height 5px
linesLayout.insertWhitespace(3, 0, 5, 0);
linesLayout.insertWhitespace(4, 0, 5, 0);
insertWhitespace(linesLayout, 3, 0, 5, 0);
insertWhitespace(linesLayout, 4, 0, 5, 0);
// lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
// whitespace: a(2,5), b(3, 5), c(4, 5)
assert.equal(linesLayout.getLinesTotalHeight(), 115);
@ -120,7 +138,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
// Start off with 10 lines and one whitespace after line 2, of height 5
let linesLayout = new LinesLayout(10, 1);
let a = linesLayout.insertWhitespace(2, 0, 5, 0);
let a = insertWhitespace(linesLayout, 2, 0, 5, 0);
// 10 lines
// whitespace: - a(2,5)
@ -139,7 +157,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
// Change whitespace height
// 10 lines
// whitespace: - a(2,10)
linesLayout.changeWhitespace(a, 2, 10);
changeOneWhitespace(linesLayout, a, 2, 10);
assert.equal(linesLayout.getLinesTotalHeight(), 20);
assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0);
assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1);
@ -155,7 +173,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
// Change whitespace position
// 10 lines
// whitespace: - a(5,10)
linesLayout.changeWhitespace(a, 5, 10);
changeOneWhitespace(linesLayout, a, 5, 10);
assert.equal(linesLayout.getLinesTotalHeight(), 20);
assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0);
assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1);
@ -200,7 +218,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
// Remove whitespace
// 10 lines
linesLayout.removeWhitespace(a);
removeWhitespace(linesLayout, a);
assert.equal(linesLayout.getLinesTotalHeight(), 10);
assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0);
assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1);
@ -216,7 +234,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
test('LinesLayout getLineNumberAtOrAfterVerticalOffset', () => {
let linesLayout = new LinesLayout(10, 1);
linesLayout.insertWhitespace(6, 0, 10, 0);
insertWhitespace(linesLayout, 6, 0, 10, 0);
// 10 lines
// whitespace: - a(6,10)
@ -265,7 +283,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
test('LinesLayout getCenteredLineInViewport', () => {
let linesLayout = new LinesLayout(10, 1);
linesLayout.insertWhitespace(6, 0, 10, 0);
insertWhitespace(linesLayout, 6, 0, 10, 0);
// 10 lines
// whitespace: - a(6,10)
@ -348,7 +366,7 @@ suite('Editor ViewLayout - LinesLayout', () => {
test('LinesLayout getLinesViewportData 1', () => {
let linesLayout = new LinesLayout(10, 10);
linesLayout.insertWhitespace(6, 0, 100, 0);
insertWhitespace(linesLayout, 6, 0, 100, 0);
// 10 lines
// whitespace: - a(6,100)
@ -479,11 +497,10 @@ suite('Editor ViewLayout - LinesLayout', () => {
assert.deepEqual(viewportData.relativeVerticalOffset, [160, 170, 180, 190]);
});
test('LinesLayout getLinesViewportData 2 & getWhitespaceViewportData', () => {
let linesLayout = new LinesLayout(10, 10);
let a = linesLayout.insertWhitespace(6, 0, 100, 0);
let b = linesLayout.insertWhitespace(7, 0, 50, 0);
let a = insertWhitespace(linesLayout, 6, 0, 100, 0);
let b = insertWhitespace(linesLayout, 7, 0, 50, 0);
// 10 lines
// whitespace: - a(6,100), b(7, 50)
@ -553,8 +570,8 @@ suite('Editor ViewLayout - LinesLayout', () => {
test('LinesLayout getWhitespaceAtVerticalOffset', () => {
let linesLayout = new LinesLayout(10, 10);
let a = linesLayout.insertWhitespace(6, 0, 100, 0);
let b = linesLayout.insertWhitespace(7, 0, 50, 0);
let a = insertWhitespace(linesLayout, 6, 0, 100, 0);
let b = insertWhitespace(linesLayout, 7, 0, 50, 0);
let whitespace = linesLayout.getWhitespaceAtVerticalOffset(0);
assert.equal(whitespace, null);
@ -592,4 +609,536 @@ suite('Editor ViewLayout - LinesLayout', () => {
whitespace = linesLayout.getWhitespaceAtVerticalOffset(220);
assert.equal(whitespace, null);
});
test('LinesLayout', () => {
const linesLayout = new LinesLayout(100, 20);
// Insert a whitespace after line number 2, of height 10
const a = insertWhitespace(linesLayout, 2, 0, 10, 0);
// whitespaces: a(2, 10)
assert.equal(linesLayout.getWhitespacesCount(), 1);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 10);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10);
// Insert a whitespace again after line number 2, of height 20
let b = insertWhitespace(linesLayout, 2, 0, 20, 0);
// whitespaces: a(2, 10), b(2, 20)
assert.equal(linesLayout.getWhitespacesCount(), 2);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 30);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 30);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30);
// Change last inserted whitespace height to 30
changeOneWhitespace(linesLayout, b, 2, 30);
// whitespaces: a(2, 10), b(2, 30)
assert.equal(linesLayout.getWhitespacesCount(), 2);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 40);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 40);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 40);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 40);
// Remove last inserted whitespace
removeWhitespace(linesLayout, b);
// whitespaces: a(2, 10)
assert.equal(linesLayout.getWhitespacesCount(), 1);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 10);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10);
// Add a whitespace before the first line of height 50
b = insertWhitespace(linesLayout, 0, 0, 50, 0);
// whitespaces: b(0, 50), a(2, 10)
assert.equal(linesLayout.getWhitespacesCount(), 2);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 60);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60);
// Add a whitespace after line 4 of height 20
insertWhitespace(linesLayout, 4, 0, 20, 0);
// whitespaces: b(0, 50), a(2, 10), c(4, 20)
assert.equal(linesLayout.getWhitespacesCount(), 3);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4);
assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 80);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 80);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 80);
// Add a whitespace after line 3 of height 30
insertWhitespace(linesLayout, 3, 0, 30, 0);
// whitespaces: b(0, 50), a(2, 10), d(3, 30), c(4, 20)
assert.equal(linesLayout.getWhitespacesCount(), 4);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4);
assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 90);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 110);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 110);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 90);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 110);
// Change whitespace after line 2 to height of 100
changeOneWhitespace(linesLayout, a, 2, 100);
// whitespaces: b(0, 50), a(2, 100), d(3, 30), c(4, 20)
assert.equal(linesLayout.getWhitespacesCount(), 4);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 100);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4);
assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 150);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 180);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 200);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 200);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 150);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 180);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 200);
// Remove whitespace after line 2
removeWhitespace(linesLayout, a);
// whitespaces: b(0, 50), d(3, 30), c(4, 20)
assert.equal(linesLayout.getWhitespacesCount(), 3);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4);
assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 80);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 100);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 100);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 80);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 100);
// Remove whitespace before line 1
removeWhitespace(linesLayout, b);
// whitespaces: d(3, 30), c(4, 20)
assert.equal(linesLayout.getWhitespacesCount(), 2);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50);
// Delete line 1
linesLayout.onLinesDeleted(1, 1);
// whitespaces: d(2, 30), c(3, 20)
assert.equal(linesLayout.getWhitespacesCount(), 2);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50);
// Insert a line before line 1
linesLayout.onLinesInserted(1, 1);
// whitespaces: d(3, 30), c(4, 20)
assert.equal(linesLayout.getWhitespacesCount(), 2);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50);
// Delete line 4
linesLayout.onLinesDeleted(4, 4);
// whitespaces: d(3, 30), c(3, 20)
assert.equal(linesLayout.getWhitespacesCount(), 2);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30);
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30);
assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50);
assert.equal(linesLayout.getWhitespacesTotalHeight(), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50);
assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50);
});
test('LinesLayout findInsertionIndex', () => {
const makeInternalWhitespace = (afterLineNumbers: number[], ordinal: number = 0) => {
return afterLineNumbers.map((afterLineNumber) => new EditorWhitespace('', afterLineNumber, ordinal, 0, 0));
};
let arr: EditorWhitespace[];
arr = makeInternalWhitespace([]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 0);
arr = makeInternalWhitespace([1]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
arr = makeInternalWhitespace([1, 3]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
arr = makeInternalWhitespace([1, 3, 5]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3);
arr = makeInternalWhitespace([1, 3, 5], 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3);
arr = makeInternalWhitespace([1, 3, 5, 7]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4);
arr = makeInternalWhitespace([1, 3, 5, 7, 9]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5);
assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5);
arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5);
assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5);
assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6);
assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6);
arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5);
assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5);
assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6);
assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6);
assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7);
assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7);
arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13, 15]);
assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0);
assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1);
assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2);
assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3);
assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4);
assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5);
assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5);
assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6);
assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6);
assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7);
assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7);
assert.equal(LinesLayout.findInsertionIndex(arr, 15, 0), 8);
assert.equal(LinesLayout.findInsertionIndex(arr, 16, 0), 8);
});
test('LinesLayout changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => {
const linesLayout = new LinesLayout(100, 20);
const a = insertWhitespace(linesLayout, 0, 0, 1, 0);
const b = insertWhitespace(linesLayout, 7, 0, 1, 0);
const c = insertWhitespace(linesLayout, 3, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Do not really move a
changeOneWhitespace(linesLayout, a, 1, 1);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 1
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 1);
assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Do not really move a
changeOneWhitespace(linesLayout, a, 2, 1);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 2
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Change a to conflict with c => a gets placed after c
changeOneWhitespace(linesLayout, a, 3, 1);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Make a no-op
changeOneWhitespace(linesLayout, c, 3, 1);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Conflict c with b => c gets placed after b
changeOneWhitespace(linesLayout, c, 7, 1);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 3
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 7);
assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 7
assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b
assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
});
test('LinesLayout Bug', () => {
const linesLayout = new LinesLayout(100, 20);
const a = insertWhitespace(linesLayout, 0, 0, 1, 0);
const b = insertWhitespace(linesLayout, 7, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7
const c = insertWhitespace(linesLayout, 3, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3
assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7
const d = insertWhitespace(linesLayout, 2, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2
assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3
assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7
const e = insertWhitespace(linesLayout, 8, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2
assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3
assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7
assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8
const f = insertWhitespace(linesLayout, 11, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2
assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3
assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7
assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8
assert.equal(linesLayout.getIdForWhitespaceIndex(5), f); // 11
const g = insertWhitespace(linesLayout, 10, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2
assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3
assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7
assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8
assert.equal(linesLayout.getIdForWhitespaceIndex(5), g); // 10
assert.equal(linesLayout.getIdForWhitespaceIndex(6), f); // 11
const h = insertWhitespace(linesLayout, 0, 0, 1, 0);
assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(1), h); // 0
assert.equal(linesLayout.getIdForWhitespaceIndex(2), d); // 2
assert.equal(linesLayout.getIdForWhitespaceIndex(3), c); // 3
assert.equal(linesLayout.getIdForWhitespaceIndex(4), b); // 7
assert.equal(linesLayout.getIdForWhitespaceIndex(5), e); // 8
assert.equal(linesLayout.getIdForWhitespaceIndex(6), g); // 10
assert.equal(linesLayout.getIdForWhitespaceIndex(7), f); // 11
});
});

View file

@ -1,558 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { WhitespaceComputer } from 'vs/editor/common/viewLayout/whitespaceComputer';
suite('Editor ViewLayout - WhitespaceComputer', () => {
test('WhitespaceComputer', () => {
let whitespaceComputer = new WhitespaceComputer();
// Insert a whitespace after line number 2, of height 10
let a = whitespaceComputer.insertWhitespace(2, 0, 10, 0);
// whitespaces: a(2, 10)
assert.equal(whitespaceComputer.getCount(), 1);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10);
assert.equal(whitespaceComputer.getTotalHeight(), 10);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 10);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 10);
// Insert a whitespace again after line number 2, of height 20
let b = whitespaceComputer.insertWhitespace(2, 0, 20, 0);
// whitespaces: a(2, 10), b(2, 20)
assert.equal(whitespaceComputer.getCount(), 2);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 30);
assert.equal(whitespaceComputer.getTotalHeight(), 30);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 30);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30);
// Change last inserted whitespace height to 30
whitespaceComputer.changeWhitespaceHeight(b, 30);
// whitespaces: a(2, 10), b(2, 30)
assert.equal(whitespaceComputer.getCount(), 2);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 30);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 40);
assert.equal(whitespaceComputer.getTotalHeight(), 40);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 40);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 40);
// Remove last inserted whitespace
whitespaceComputer.removeWhitespace(b);
// whitespaces: a(2, 10)
assert.equal(whitespaceComputer.getCount(), 1);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10);
assert.equal(whitespaceComputer.getTotalHeight(), 10);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 10);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 10);
// Add a whitespace before the first line of height 50
b = whitespaceComputer.insertWhitespace(0, 0, 50, 0);
// whitespaces: b(0, 50), a(2, 10)
assert.equal(whitespaceComputer.getCount(), 2);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60);
assert.equal(whitespaceComputer.getTotalHeight(), 60);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 60);
// Add a whitespace after line 4 of height 20
whitespaceComputer.insertWhitespace(4, 0, 20, 0);
// whitespaces: b(0, 50), a(2, 10), c(4, 20)
assert.equal(whitespaceComputer.getCount(), 3);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 4);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60);
assert.equal(whitespaceComputer.getAccumulatedHeight(2), 80);
assert.equal(whitespaceComputer.getTotalHeight(), 80);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 60);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 80);
// Add a whitespace after line 3 of height 30
whitespaceComputer.insertWhitespace(3, 0, 30, 0);
// whitespaces: b(0, 50), a(2, 10), d(3, 30), c(4, 20)
assert.equal(whitespaceComputer.getCount(), 4);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 30);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(3), 4);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(3), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60);
assert.equal(whitespaceComputer.getAccumulatedHeight(2), 90);
assert.equal(whitespaceComputer.getAccumulatedHeight(3), 110);
assert.equal(whitespaceComputer.getTotalHeight(), 110);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 90);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 110);
// Change whitespace after line 2 to height of 100
whitespaceComputer.changeWhitespaceHeight(a, 100);
// whitespaces: b(0, 50), a(2, 100), d(3, 30), c(4, 20)
assert.equal(whitespaceComputer.getCount(), 4);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 100);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 30);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(3), 4);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(3), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 150);
assert.equal(whitespaceComputer.getAccumulatedHeight(2), 180);
assert.equal(whitespaceComputer.getAccumulatedHeight(3), 200);
assert.equal(whitespaceComputer.getTotalHeight(), 200);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 150);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 180);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 200);
// Remove whitespace after line 2
whitespaceComputer.removeWhitespace(a);
// whitespaces: b(0, 50), d(3, 30), c(4, 20)
assert.equal(whitespaceComputer.getCount(), 3);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 30);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 4);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 80);
assert.equal(whitespaceComputer.getAccumulatedHeight(2), 100);
assert.equal(whitespaceComputer.getTotalHeight(), 100);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 80);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 100);
// Remove whitespace before line 1
whitespaceComputer.removeWhitespace(b);
// whitespaces: d(3, 30), c(4, 20)
assert.equal(whitespaceComputer.getCount(), 2);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 4);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50);
assert.equal(whitespaceComputer.getTotalHeight(), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50);
// Delete line 1
whitespaceComputer.onLinesDeleted(1, 1);
// whitespaces: d(2, 30), c(3, 20)
assert.equal(whitespaceComputer.getCount(), 2);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50);
assert.equal(whitespaceComputer.getTotalHeight(), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 30);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50);
// Insert a line before line 1
whitespaceComputer.onLinesInserted(1, 1);
// whitespaces: d(3, 30), c(4, 20)
assert.equal(whitespaceComputer.getCount(), 2);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 4);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50);
assert.equal(whitespaceComputer.getTotalHeight(), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50);
// Delete line 4
whitespaceComputer.onLinesDeleted(4, 4);
// whitespaces: d(3, 30), c(3, 20)
assert.equal(whitespaceComputer.getCount(), 2);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30);
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20);
assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30);
assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50);
assert.equal(whitespaceComputer.getTotalHeight(), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 50);
assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50);
});
test('WhitespaceComputer findInsertionIndex', () => {
let makeArray = (size: number, fillValue: number) => {
let r: number[] = [];
for (let i = 0; i < size; i++) {
r[i] = fillValue;
}
return r;
};
let arr: number[];
let ordinals: number[];
arr = [];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 0);
arr = [1];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
arr = [1, 3];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
arr = [1, 3, 5];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3);
arr = [1, 3, 5];
ordinals = makeArray(arr.length, 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3);
arr = [1, 3, 5, 7];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4);
arr = [1, 3, 5, 7, 9];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5);
arr = [1, 3, 5, 7, 9, 11];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6);
arr = [1, 3, 5, 7, 9, 11, 13];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 13, ordinals, 0), 7);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 14, ordinals, 0), 7);
arr = [1, 3, 5, 7, 9, 11, 13, 15];
ordinals = makeArray(arr.length, 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 13, ordinals, 0), 7);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 14, ordinals, 0), 7);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 15, ordinals, 0), 8);
assert.equal(WhitespaceComputer.findInsertionIndex(arr, 16, ordinals, 0), 8);
});
test('WhitespaceComputer changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => {
let whitespaceComputer = new WhitespaceComputer();
let a = whitespaceComputer.insertWhitespace(0, 0, 1, 0);
let b = whitespaceComputer.insertWhitespace(7, 0, 1, 0);
let c = whitespaceComputer.insertWhitespace(3, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Do not really move a
whitespaceComputer.changeWhitespaceAfterLineNumber(a, 1);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 1
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 1);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Do not really move a
whitespaceComputer.changeWhitespaceAfterLineNumber(a, 2);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 2
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Change a to conflict with c => a gets placed after c
whitespaceComputer.changeWhitespaceAfterLineNumber(a, 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), c); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), a); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Make a no-op
whitespaceComputer.changeWhitespaceAfterLineNumber(c, 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), c); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), a); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
// Conflict c with b => c gets placed after b
whitespaceComputer.changeWhitespaceAfterLineNumber(c, 7);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 3
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), b); // 7
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 7);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 7
assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7);
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b
assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // --
});
test('WhitespaceComputer Bug', () => {
let whitespaceComputer = new WhitespaceComputer();
let a = whitespaceComputer.insertWhitespace(0, 0, 1, 0);
let b = whitespaceComputer.insertWhitespace(7, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), b); // 7
let c = whitespaceComputer.insertWhitespace(3, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7
let d = whitespaceComputer.insertWhitespace(2, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7
let e = whitespaceComputer.insertWhitespace(8, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8
let f = whitespaceComputer.insertWhitespace(11, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), f); // 11
let g = whitespaceComputer.insertWhitespace(10, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), g); // 10
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(6), f); // 11
let h = whitespaceComputer.insertWhitespace(0, 0, 1, 0);
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), h); // 0
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), d); // 2
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), c); // 3
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), b); // 7
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), e); // 8
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(6), g); // 10
assert.equal(whitespaceComputer.getIdForWhitespaceIndex(7), f); // 11
});
});

View file

@ -236,28 +236,52 @@ export class MenuEntryActionViewItem extends ActionViewItem {
_updateItemClass(item: ICommandAction): void {
this._itemClassDispose.value = undefined;
if (item.iconLocation) {
let iconClass: string;
const iconPathMapKey = item.iconLocation.dark.toString();
if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
} else {
iconClass = ids.nextId();
createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`);
MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
}
// icon class
if (item.iconClassName) {
let iconClass = item.iconClassName;
if (this.label) {
addClasses(this.label, 'icon', iconClass);
addClasses(this.label, 'codicon', iconClass);
this._itemClassDispose.value = toDisposable(() => {
if (this.label) {
removeClasses(this.label, 'icon', iconClass);
removeClasses(this.label, 'codicon', iconClass);
}
});
}
}
// icon path
else if (item.iconLocation) {
let iconClass: string;
if (item.iconLocation?.dark?.scheme) {
const iconPathMapKey = item.iconLocation.dark.toString();
if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
} else {
iconClass = ids.nextId();
createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`);
MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
}
if (this.label) {
addClasses(this.label, 'icon', iconClass);
this._itemClassDispose.value = toDisposable(() => {
if (this.label) {
removeClasses(this.label, 'icon', iconClass);
}
});
}
}
}
}
}

View file

@ -22,7 +22,8 @@ export interface ICommandAction {
id: string;
title: string | ILocalizedString;
category?: string | ILocalizedString;
iconLocation?: { dark: URI; light?: URI; };
iconClassName?: string;
iconLocation?: { dark?: URI; light?: URI; };
precondition?: ContextKeyExpr;
toggled?: ContextKeyExpr;
}

View file

@ -86,6 +86,7 @@ export interface ParsedArgs {
'disable-gpu'?: boolean;
'nolazy'?: boolean;
'force-device-scale-factor'?: string;
'force-renderer-accessibility'?: boolean;
}
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');

View file

@ -118,6 +118,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'inspect-brk': { type: 'string' },
'nolazy': { type: 'boolean' }, // node inspect
'force-device-scale-factor': { type: 'string' },
'force-renderer-accessibility': { type: 'boolean' },
'_urls': { type: 'string[]' },
_: { type: 'string[]' } // main arguments

View file

@ -217,6 +217,16 @@ export class ExtensionManagementService extends Disposable implements IExtension
} else if (semver.gt(existing.manifest.version, manifest.version)) {
return this.uninstall(existing, true);
}
} else {
// Remove the extension with same version if it is already uninstalled.
// Installing a VSIX extension shall replace the existing extension always.
return this.unsetUninstalledAndGetLocal(identifierWithVersion)
.then(existing => {
if (existing) {
return this.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name))));
}
return undefined;
});
}
return undefined;
})

View file

@ -6,6 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings';
export const IOpenerService = createDecorator<IOpenerService>('openerService');
@ -35,8 +36,7 @@ export interface IResolvedExternalUri extends IDisposable {
}
export interface IOpener {
open(resource: URI, options?: OpenInternalOptions): Promise<boolean>;
open(resource: URI, options?: OpenExternalOptions): Promise<boolean>;
open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise<boolean>;
}
export interface IExternalOpener {
@ -44,7 +44,7 @@ export interface IExternalOpener {
}
export interface IValidator {
shouldOpen(resource: URI): Promise<boolean>;
shouldOpen(resource: URI | string): Promise<boolean>;
}
export interface IExternalUriResolver {
@ -83,8 +83,7 @@ export interface IOpenerService {
* @param resource A resource
* @return A promise that resolves when the opening is done.
*/
open(resource: URI, options?: OpenInternalOptions): Promise<boolean>;
open(resource: URI, options?: OpenExternalOptions): Promise<boolean>;
open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise<boolean>;
/**
* Resolve a resource to its external form.
@ -101,3 +100,11 @@ export const NullOpenerService: IOpenerService = Object.freeze({
async open() { return false; },
async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; },
});
export function matchesScheme(target: URI | string, scheme: string) {
if (URI.isUri(target)) {
return equalsIgnoreCase(target.scheme, scheme);
} else {
return startsWithIgnoreCase(target, scheme + ':');
}
}

View file

@ -89,8 +89,8 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
options.host,
options.port,
`reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
(err: any, socket: ISocket) => {
if (err) {
(err: any, socket: ISocket | undefined) => {
if (err || !socket) {
options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`);
options.logService.error(err);
e(err);

5
src/vs/vscode.d.ts vendored
View file

@ -2009,8 +2009,9 @@ declare module 'vscode' {
/**
* Base kind for source actions: `source`
*
* Source code actions apply to the entire file and can be run on save
* using `editor.codeActionsOnSave`. They also are shown in `source` context menu.
* Source code actions apply to the entire file. They must be explicitly requested and will not show in the
* normal [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) menu. Source actions
* can be run on save using `editor.codeActionsOnSave` and are also shown in the `source` context menu.
*/
static readonly Source: CodeActionKind;

View file

@ -30,7 +30,8 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { ISaveParticipant, SaveReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { ISaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { SaveReason } from 'vs/workbench/common/editor';
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol';
export interface ICodeActionsOnSaveOptions {

View file

@ -12,7 +12,6 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { localize } from 'vs/nls';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -21,7 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr
import { IEditorInput } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/browser/customEditorModel';
import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
@ -96,14 +95,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
private readonly _webviewInputs = new WebviewInputStore();
private readonly _revivers = new Map<string, IDisposable>();
private readonly _editorProviders = new Map<string, IDisposable>();
private readonly _models = new Map<string, CustomEditorModel>();
constructor(
context: extHostProtocol.IExtHostContext,
@IExtensionService extensionService: IExtensionService,
@ICustomEditorService private readonly _customEditorService: ICustomEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorService private readonly _editorService: IEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IOpenerService private readonly _openerService: IOpenerService,
@IProductService private readonly _productService: IProductService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ -273,18 +271,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
webviewInput.webview.options = options;
webviewInput.webview.extension = extension;
const model = this._instantiationService.createInstance(CustomEditorModel, webviewInput.getResource());
webviewInput.setModel(model);
this._models.set(handle, model);
webviewInput.onDispose(() => {
this._models.delete(handle);
});
const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType);
model.onUndo(edit => {
this._proxy.$undoEdits(handle, [edit]);
});
model.onRedo(edit => {
this._proxy.$redoEdits(handle, [edit]);
});
webviewInput.onDispose(() => {
this._customEditorService.models.disposeModel(model);
});
try {
await this._proxy.$resolveWebviewEditor(
webviewInput.getResource(),
@ -318,7 +318,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
throw new Error('Webview is not a webview editor');
}
const model = this._models.get(handle);
const model = this._customEditorService.models.get(webview.getResource(), webview.viewType);
if (!model) {
throw new Error('Could not find model for webview editor');
}

View file

@ -42,9 +42,17 @@ export class MainThreadWindow implements MainThreadWindowShape {
return Promise.resolve(this.hostService.hasFocus);
}
async $openUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<boolean> {
async $openUri(uriComponents: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise<boolean> {
const uri = URI.from(uriComponents);
return this.openerService.open(uri, { openExternal: true, allowTunneling: options.allowTunneling });
let target: URI | string;
if (uriString && URI.parse(uriString).toString() === uri.toString()) {
// called with string and no transformation happened -> keep string
target = uriString;
} else {
// called with URI or transformed -> use uri
target = uri;
}
return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling });
}
async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<UriComponents> {

View file

@ -15,7 +15,7 @@ import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'v
import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@ -37,7 +37,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
extHostContext: IExtHostContext,
@ISearchService private readonly _searchService: ISearchService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IEditorService private readonly _editorService: IEditorService,
@IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService,
@INotificationService private readonly _notificationService: INotificationService,
@IRequestService private readonly _requestService: IRequestService,
@ -212,9 +212,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
// --- save & edit resources ---
$saveAll(includeUntitled?: boolean): Promise<boolean> {
return this._textFileService.saveAll(includeUntitled).then(result => {
return result.results.every(each => each.success === true);
});
return this._editorService.saveAll({ includeUntitled });
}
$resolveProxy(url: string): Promise<string | undefined> {

View file

@ -185,8 +185,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}
return activeTextEditor.edit((edit: vscode.TextEditorEdit) => {
args.unshift(activeTextEditor, edit);
callback.apply(thisArg, args);
callback.apply(thisArg, [activeTextEditor, edit, ...args]);
}).then((result) => {
if (!result) {

View file

@ -45,7 +45,7 @@ import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/te
import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import * as search from 'vs/workbench/services/search/common/search';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
export interface IEnvironment {
@ -591,6 +591,7 @@ export interface ExtHostWebviewsShape {
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$undoEdits(handle: WebviewPanelHandle, edits: string[]): void;
$redoEdits(handle: WebviewPanelHandle, edits: string[]): void;
}
export interface MainThreadUrlsShape extends IDisposable {
@ -760,7 +761,7 @@ export interface IOpenUriOptions {
export interface MainThreadWindowShape extends IDisposable {
$getWindowVisibility(): Promise<boolean>;
$openUri(uri: UriComponents, options: IOpenUriOptions): Promise<boolean>;
$openUri(uri: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise<boolean>;
$asExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise<UriComponents>;
}

View file

@ -11,7 +11,7 @@ import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IResou
import { TextEdit } from 'vs/workbench/api/common/extHostTypes';
import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { SaveReason } from 'vs/workbench/common/editor';
import * as vscode from 'vscode';
import { LinkedList } from 'vs/base/common/linkedList';
import { ILogService } from 'vs/platform/log/common/log';

View file

@ -245,9 +245,9 @@ class OpenNodeModuleFactory implements INodeModuleFactory {
return this.callOriginal(target, options);
}
if (uri.scheme === 'http' || uri.scheme === 'https') {
return mainThreadWindow.$openUri(uri, { allowTunneling: true });
return mainThreadWindow.$openUri(uri, target, { allowTunneling: true });
} else if (uri.scheme === 'mailto' || uri.scheme === this._appUriScheme) {
return mainThreadWindow.$openUri(uri, {});
return mainThreadWindow.$openUri(uri, target, {});
}
return this.callOriginal(target, options);
};

View file

@ -9,7 +9,7 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI, UriComponents } from 'vs/base/common/uri';
import { EXT_HOST_CREATION_DELAY, ITerminalChildProcess, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalChildProcess, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal';
import { timeout } from 'vs/base/common/async';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering';
@ -437,22 +437,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
}
}
public performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void {
// TODO: Use await this._getTerminalByIdEventually(id);
let terminal = this._getTerminalById(id);
if (terminal) {
callback(terminal);
} else {
// Retry one more time in case the terminal has not yet been initialized.
setTimeout(() => {
terminal = this._getTerminalById(id);
if (terminal) {
callback(terminal);
}
}, EXT_HOST_CREATION_DELAY * 2);
}
}
public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<void> {
// Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call
// Pseudoterminal.start
@ -550,10 +534,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal | undefined> {
if (!this._getTerminalPromises[id]) {
this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries);
} else {
this._getTerminalPromises[id].then(c => {
return this._createGetTerminalPromise(id, retries);
});
}
return this._getTerminalPromises[id];
}
@ -571,7 +551,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
} else {
// This should only be needed immediately after createTerminalRenderer is called as
// the ExtHostTerminal has not yet been iniitalized
timeout(200).then(() => c(this._createGetTerminalPromise(id, retries - 1)));
timeout(EXT_HOST_CREATION_DELAY * 2).then(() => c(this._createGetTerminalPromise(id, retries - 1)));
}
});
}

View file

@ -13,7 +13,7 @@ import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/mode
import * as vscode from 'vscode';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { SaveReason } from 'vs/workbench/common/editor';
import { IPosition } from 'vs/editor/common/core/position';
import * as editorRange from 'vs/editor/common/core/range';
import { ISelection } from 'vs/editor/common/core/selection';
@ -31,7 +31,6 @@ import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log';
import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
export interface PositionLike {
line: number;
character: number;

View file

@ -252,6 +252,10 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits);
}
_redoEdits(edits: string[]): void {
assertIsDefined(this._capabilities).editingCapability?.applyEdits(edits);
}
private assertNotDisposed() {
if (this._isDisposed) {
throw new Error('Webview is disposed');
@ -447,6 +451,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
panel._undoEdits(edits);
}
$redoEdits(handle: WebviewPanelHandle, edits: string[]): void {
const panel = this.getWebviewPanel(handle);
if (!panel) {
return;
}
panel._redoEdits(edits);
}
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined {
return this._webviewPanels.get(handle);
}

View file

@ -39,7 +39,9 @@ export class ExtHostWindow implements ExtHostWindowShape {
}
openUri(stringOrUri: string | URI, options: IOpenUriOptions): Promise<boolean> {
let uriAsString: string | undefined;
if (typeof stringOrUri === 'string') {
uriAsString = stringOrUri;
try {
stringOrUri = URI.parse(stringOrUri);
} catch (e) {
@ -51,7 +53,7 @@ export class ExtHostWindow implements ExtHostWindowShape {
} else if (stringOrUri.scheme === Schemas.command) {
return Promise.reject(`Invalid scheme '${stringOrUri.scheme}'`);
}
return this._proxy.$openUri(stringOrUri, options);
return this._proxy.$openUri(stringOrUri, uriAsString, options);
}
async asExternalUri(uri: URI, options: IOpenUriOptions): Promise<URI> {

View file

@ -38,6 +38,7 @@ import { ISignService } from 'vs/platform/sign/common/sign';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService';
import { withNullAsUndefined } from 'vs/base/common/types';
export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugServiceShape {
@ -114,7 +115,7 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe
this._onDidStartDebugSession = new Emitter<vscode.DebugSession>();
this._onDidTerminateDebugSession = new Emitter<vscode.DebugSession>();
this._onDidChangeActiveDebugSession = new Emitter<vscode.DebugSession>();
this._onDidChangeActiveDebugSession = new Emitter<vscode.DebugSession | undefined>();
this._onDidReceiveDebugSessionCustomEvent = new Emitter<vscode.DebugSessionCustomEvent>();
this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService);
@ -511,11 +512,11 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe
}
this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack);
});
debugAdapter.onExit((code: number) => {
debugAdapter.onExit((code: number | null) => {
if (tracker && tracker.onExit) {
tracker.onExit(code, undefined);
tracker.onExit(withNullAsUndefined(code), undefined);
}
this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, code, undefined);
this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, withNullAsUndefined(code), undefined);
});
if (tracker && tracker.onWillStartSession) {

View file

@ -22,7 +22,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
export class OpenFileAction extends Action {
@ -213,7 +213,7 @@ export class SaveWorkspaceAsAction extends Action {
async run(): Promise<any> {
const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath();
if (configPathUri) {
if (configPathUri && hasWorkspaceFileExtension(configPathUri)) {
switch (this.contextService.getWorkbenchState()) {
case WorkbenchState.EMPTY:
case WorkbenchState.FOLDER:

View file

@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
import { IWindowsConfiguration } from 'vs/platform/windows/common/windows';
import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, toResource, SideBySideEditor, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -21,8 +21,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform';
import { PanelPositionContext } from 'vs/workbench/common/panel';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
export const IsMacContext = new RawContextKey<boolean>('isMac', isMacintosh);
export const IsLinuxContext = new RawContextKey<boolean>('isLinux', isLinux);
@ -51,6 +50,8 @@ export const IsFullscreenContext = new RawContextKey<boolean>('isFullscreen', fa
export class WorkbenchContextKeysHandler extends Disposable {
private inputFocusedContext: IContextKey<boolean>;
private dirtyWorkingCopiesContext: IContextKey<boolean>;
private activeEditorContext: IContextKey<string | null>;
private activeEditorIsSaveable: IContextKey<boolean>;
@ -75,19 +76,18 @@ export class WorkbenchContextKeysHandler extends Disposable {
private panelPositionContext: IContextKey<string>;
constructor(
@IContextKeyService private contextKeyService: IContextKeyService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IConfigurationService private configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
@IEditorService private editorService: IEditorService,
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService,
@IViewletService private viewletService: IViewletService,
@IFileService private fileService: IFileService
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IEditorService private readonly editorService: IEditorService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IViewletService private readonly viewletService: IViewletService,
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService
) {
super();
// Platform
IsMacContext.bindTo(this.contextKeyService);
IsLinuxContext.bindTo(this.contextKeyService);
@ -116,6 +116,9 @@ export class WorkbenchContextKeysHandler extends Disposable {
this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService);
this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService);
// Working Copies
this.dirtyWorkingCopiesContext = DirtyWorkingCopiesContext.bindTo(this.contextKeyService);
// Inputs
this.inputFocusedContext = InputFocusedContext.bindTo(this.contextKeyService);
@ -183,6 +186,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys()));
this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART))));
this._register(this.workingCopyService.onDidChangeDirty(w => this.dirtyWorkingCopiesContext.set(w.isDirty() || this.workingCopyService.hasDirty)));
}
private updateEditorContextKeys(): void {
@ -217,10 +222,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
if (activeControl) {
this.activeEditorContext.set(activeControl.getId());
const resource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER });
const canSave = resource ? this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled : false;
this.activeEditorIsSaveable.set(canSave);
this.activeEditorIsSaveable.set(!activeControl.input.isReadonly());
} else {
this.activeEditorContext.reset();
this.activeEditorIsSaveable.reset();

View file

@ -28,7 +28,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
export interface IResourceLabelProps {
resource?: URI;
name?: string;
name?: string | string[];
description?: string;
}
@ -41,6 +41,7 @@ export interface IResourceLabelOptions extends IIconLabelValueOptions {
export interface IFileLabelOptions extends IResourceLabelOptions {
hideLabel?: boolean;
hidePath?: boolean;
readonly parentCount?: number;
}
export interface IResourceLabel extends IDisposable {
@ -442,7 +443,8 @@ class ResourceLabelWidget extends IconLabel {
title: '',
italic: this.options && this.options.italic,
matches: this.options && this.options.matches,
extraClasses: []
extraClasses: [],
separator: this.options?.separator
};
const resource = this.label.resource;

View file

@ -44,6 +44,10 @@ body.web {
position: fixed; /* prevent bounce effect */
}
.monaco-workbench.web {
touch-action: initial; /* reenable touch events on workbench */
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.monaco-workbench {

View file

@ -22,7 +22,7 @@ import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarCol
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { IActivity } from 'vs/workbench/common/activity';
import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@ -297,6 +297,20 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
`);
}
const activeFocusBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_FOCUS_BORDER);
if (activeFocusBorderColor) {
collector.addRule(`
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus::before {
visibility: hidden;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before {
visibility: visible;
border-left-color: ${activeFocusBorderColor};
}
`);
}
const activeBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND);
if (activeBackgroundColor) {
collector.addRule(`

View file

@ -6,14 +6,15 @@
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item {
display: block;
position: relative;
padding: 5px 0;
margin-bottom: 4px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
position: relative;
z-index: 1;
display: flex;
overflow: hidden;
height: 40px;
height: 48px;
margin-right: 0;
box-sizing: border-box;
@ -35,11 +36,10 @@
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
content: "";
position: absolute;
top: 9px;
height: 32px;
top: 0;
z-index: 1;
top: 5px;
height: 40px;
top: 0;
height: 100%;
width: 0;
border-left: 2px solid;
}
@ -50,7 +50,7 @@
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before {
border-left: none; /* don't show active border + focus at the same time, focus takes priority */
visibility: hidden; /* don't show active border + focus at the same time, focus takes priority */
}
/* Hides active elements in high contrast mode */
@ -62,20 +62,14 @@
border-left: none !important; /* no focus feedback when using mouse */
}
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before,
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before{
left: 0;
}
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
left: 1px;
}
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before,
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
right: 1px;
}
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before {
right: 2px;
right: 0;
}
/* Hides outline on HC as focus is handled by border */
@ -88,16 +82,18 @@
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge {
position: absolute;
z-index: 1;
top: 5px;
top: 0;
bottom: 0;
margin: auto;
left: 0;
overflow: hidden;
width: 50px;
height: 40px;
width: 100%;
height: 100%;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .badge-content {
position: absolute;
top: 20px;
top: 24px;
right: 8px;
font-size: 9px;
font-weight: 600;
@ -113,7 +109,7 @@
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label:not(.codicon) {
margin-left: 0;
padding: 0 50px 0 0;
padding: 0 48px 0 0;
background-position: calc(100% - 9px) center;
}

View file

@ -15,7 +15,6 @@ import { IResourceInput } from 'vs/platform/editor/common/editor';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands';
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
@ -507,7 +506,7 @@ export class CloseOneEditorAction extends Action {
// Close specific editor in group
if (typeof editorIndex === 'number') {
const editorAtIndex = group.getEditor(editorIndex);
const editorAtIndex = group.getEditorByIndex(editorIndex);
if (editorAtIndex) {
return group.closeEditor(editorAtIndex);
}
@ -598,10 +597,10 @@ export abstract class BaseCloseAllAction extends Action {
id: string,
label: string,
clazz: string | undefined,
private textFileService: ITextFileService,
private workingCopyService: IWorkingCopyService,
private fileDialogService: IFileDialogService,
protected editorGroupService: IEditorGroupsService
protected editorGroupService: IEditorGroupsService,
private editorService: IEditorService
) {
super(id, label, clazz);
}
@ -647,11 +646,10 @@ export abstract class BaseCloseAllAction extends Action {
let saveOrRevert: boolean;
if (confirm === ConfirmResult.DONT_SAVE) {
await this.textFileService.revertAll(undefined, { soft: true });
await this.editorService.revertAll({ soft: true });
saveOrRevert = true;
} else {
const res = await this.textFileService.saveAll(true);
saveOrRevert = res.results.every(r => !!r.success);
saveOrRevert = await this.editorService.saveAll({ includeUntitled: true });
}
if (saveOrRevert) {
@ -670,12 +668,12 @@ export class CloseAllEditorsAction extends BaseCloseAllAction {
constructor(
id: string,
label: string,
@ITextFileService textFileService: ITextFileService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IFileDialogService fileDialogService: IFileDialogService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService editorService: IEditorService
) {
super(id, label, 'codicon-close-all', textFileService, workingCopyService, fileDialogService, editorGroupService);
super(id, label, 'codicon-close-all', workingCopyService, fileDialogService, editorGroupService, editorService);
}
protected doCloseAll(): Promise<any> {
@ -691,12 +689,12 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction {
constructor(
id: string,
label: string,
@ITextFileService textFileService: ITextFileService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IFileDialogService fileDialogService: IFileDialogService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService editorService: IEditorService
) {
super(id, label, undefined, textFileService, workingCopyService, fileDialogService, editorGroupService);
super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService);
}
protected async doCloseAll(): Promise<any> {

View file

@ -318,7 +318,7 @@ function registerOpenEditorAtIndexCommands(): void {
const editorService = accessor.get(IEditorService);
const activeControl = editorService.activeControl;
if (activeControl) {
const editor = activeControl.group.getEditor(editorIndex);
const editor = activeControl.group.getEditorByIndex(editorIndex);
if (editor) {
editorService.openEditor(editor);
}
@ -448,7 +448,7 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction:
// Split editor (if it can be split)
let editorToCopy: IEditorInput | undefined;
if (context && typeof context.editorIndex === 'number') {
editorToCopy = sourceGroup.getEditor(context.editorIndex);
editorToCopy = sourceGroup.getEditorByIndex(context.editorIndex);
} else {
editorToCopy = types.withNullAsUndefined(sourceGroup.activeEditor);
}
@ -548,7 +548,7 @@ function registerCloseEditorCommands() {
if (group) {
const editors = coalesce(contexts
.filter(context => context.groupId === groupId)
.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor));
.map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor));
return group.closeEditors(editors);
}
@ -603,7 +603,7 @@ function registerCloseEditorCommands() {
if (group) {
const editors = contexts
.filter(context => context.groupId === groupId)
.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
.map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor);
const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
if (group.activeEditor) {
@ -715,7 +715,7 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex
// Resolve from context
let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined;
let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditor(context.editorIndex)) : undefined;
let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined;
let control = group ? group.activeControl : undefined;
// Fallback to active group as needed

View file

@ -518,11 +518,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
editorsToClose.push(editor.master, editor.details);
}
// Close the editor when it is no longer open in any group including diff editors
// Dispose the editor when it is no longer open in any group including diff editors
editorsToClose.forEach(editorToClose => {
const resource = editorToClose ? editorToClose.getResource() : undefined; // prefer resource to not close right-hand side editors of a diff editor
if (!this.accessor.groups.some(groupView => groupView.group.contains(resource || editorToClose))) {
editorToClose.close();
if (!this.accessor.groups.some(groupView => groupView.group.contains(editorToClose, true /* include side by side editor master & details */))) {
editorToClose.dispose();
}
});
@ -761,8 +760,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this.editors;
}
getEditor(index: number): EditorInput | undefined {
return this._group.getEditor(index);
getEditorByIndex(index: number): EditorInput | undefined {
return this._group.getEditorByIndex(index);
}
getIndexOfEditor(editor: EditorInput): number {
@ -1121,7 +1120,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Check for dirty and veto
const veto = await this.handleDirty([editor]);
const veto = await this.handleDirtyClosing([editor]);
if (veto) {
return;
}
@ -1232,7 +1231,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this._group.closeEditor(editor);
}
private async handleDirty(editors: EditorInput[]): Promise<boolean /* veto */> {
private async handleDirtyClosing(editors: EditorInput[]): Promise<boolean /* veto */> {
if (!editors.length) {
return false; // no veto
}
@ -1241,13 +1240,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// To prevent multiple confirmation dialogs from showing up one after the other
// we check if a pending confirmation is currently showing and if so, join that
let handleDirtyPromise = this.mapEditorToPendingConfirmation.get(editor);
if (!handleDirtyPromise) {
handleDirtyPromise = this.doHandleDirty(editor);
this.mapEditorToPendingConfirmation.set(editor, handleDirtyPromise);
let handleDirtyClosingPromise = this.mapEditorToPendingConfirmation.get(editor);
if (!handleDirtyClosingPromise) {
handleDirtyClosingPromise = this.doHandleDirtyClosing(editor);
this.mapEditorToPendingConfirmation.set(editor, handleDirtyClosingPromise);
}
const veto = await handleDirtyPromise;
const veto = await handleDirtyClosingPromise;
// Make sure to remove from our map of cached pending confirmations
this.mapEditorToPendingConfirmation.delete(editor);
@ -1258,16 +1257,40 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Otherwise continue with the remainders
return this.handleDirty(editors);
return this.handleDirtyClosing(editors);
}
private async doHandleDirty(editor: EditorInput): Promise<boolean /* veto */> {
if (
!editor.isDirty() || // editor must be dirty
this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) || // editor is opened in other group
editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened
) {
private async doHandleDirtyClosing(editor: EditorInput): Promise<boolean /* veto */> {
if (!editor.isDirty()) {
return false; // editor must be dirty
}
if (editor instanceof SideBySideEditorInput && this.isOpened(editor.master)) {
return false; // master-side of editor is still opened somewhere else
}
// Note: we explicitly decide to ask for confirm if closing a normal editor even
// if it is opened in a side-by-side editor in the group. This decision is made
// because it may be less obvious that one side of a side by side editor is dirty
// and can still be changed.
if (this.accessor.groups.some(groupView => {
if (groupView === this) {
return false; // skip this group to avoid false assumptions about the editor being opened still
}
const otherGroup = groupView.group;
if (otherGroup.contains(editor)) {
return true; // exact editor still opened
}
if (editor instanceof SideBySideEditorInput && otherGroup.contains(editor.master)) {
return true; // master side of side by side editor still opened
}
return false;
})) {
return false; // editor is still editable somewhere else
}
// Switch to editor that we want to handle and confirm to save/revert
@ -1287,7 +1310,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Otherwise, handle accordingly
switch (res) {
case ConfirmResult.SAVE:
const result = await editor.save();
const result = await editor.save(this._group.id);
return !result;
case ConfirmResult.DONT_SAVE:
@ -1324,7 +1347,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const editors = this.getEditorsToClose(args);
// Check for dirty and veto
const veto = await this.handleDirty(editors.slice(0));
const veto = await this.handleDirtyClosing(editors.slice(0));
if (veto) {
return;
}
@ -1403,7 +1426,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Check for dirty and veto
const editors = this._group.getEditors(true);
const veto = await this.handleDirty(editors.slice(0));
const veto = await this.handleDirtyClosing(editors.slice(0));
if (veto) {
return;
}

View file

@ -501,7 +501,7 @@ export class TabsTitleControl extends TitleControl {
}
// Open tabs editor
const input = this.group.getEditor(index);
const input = this.group.getEditorByIndex(index);
if (input) {
this.group.openEditor(input);
}
@ -512,7 +512,7 @@ export class TabsTitleControl extends TitleControl {
const showContextMenu = (e: Event) => {
EventHelper.stop(e);
const input = this.group.getEditor(index);
const input = this.group.getEditorByIndex(index);
if (input) {
this.onContextMenu(input, e, tab);
}
@ -562,7 +562,7 @@ export class TabsTitleControl extends TitleControl {
// Run action on Enter/Space
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
handled = true;
const input = this.group.getEditor(index);
const input = this.group.getEditorByIndex(index);
if (input) {
this.group.openEditor(input);
}
@ -581,7 +581,7 @@ export class TabsTitleControl extends TitleControl {
targetIndex = this.group.count - 1;
}
const target = this.group.getEditor(targetIndex);
const target = this.group.getEditorByIndex(targetIndex);
if (target) {
handled = true;
this.group.openEditor(target, { preserveFocus: true });
@ -603,7 +603,7 @@ export class TabsTitleControl extends TitleControl {
disposables.add(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => {
EventHelper.stop(e);
const editor = this.group.getEditor(index);
const editor = this.group.getEditorByIndex(index);
if (editor && this.group.isPinned(editor)) {
this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.group);
} else {
@ -615,7 +615,7 @@ export class TabsTitleControl extends TitleControl {
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => {
EventHelper.stop(e, true);
const input = this.group.getEditor(index);
const input = this.group.getEditorByIndex(index);
if (input) {
this.onContextMenu(input, e, tab);
}
@ -623,7 +623,7 @@ export class TabsTitleControl extends TitleControl {
// Drag support
disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => {
const editor = this.group.getEditor(index);
const editor = this.group.getEditorByIndex(index);
if (!editor) {
return;
}
@ -669,7 +669,7 @@ export class TabsTitleControl extends TitleControl {
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const localDraggedEditor = data[0].identifier;
if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) {
if (localDraggedEditor.editor === this.group.getEditorByIndex(index) && localDraggedEditor.groupId === this.group.id) {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
@ -739,7 +739,7 @@ export class TabsTitleControl extends TitleControl {
private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void {
const isTab = (typeof index === 'number');
const editor = typeof index === 'number' ? this.group.getEditor(index) : undefined;
const editor = typeof index === 'number' ? this.group.getEditorByIndex(index) : undefined;
const isActiveTab = isTab && !!editor && this.group.isActive(editor);
// Background

View file

@ -6,17 +6,17 @@
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { distinct, deepClone, assign } from 'vs/base/common/objects';
import { isObject, assertIsDefined } from 'vs/base/common/types';
import { isObject, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
import { Dimension } from 'vs/base/browser/dom';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, IEditorMemento, ITextEditor, SaveReason } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorViewState, IEditor } from 'vs/editor/common/editorCommon';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITextFileService, SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { isDiffEditor, isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
@ -248,6 +248,15 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
this.editorMemento.saveEditorState(this.group, resource, editorViewState);
}
getViewState(): IEditorViewState | undefined {
const resource = this.input?.getResource();
if (resource) {
return withNullAsUndefined(this.retrieveTextEditorViewState(resource));
}
return undefined;
}
protected retrieveTextEditorViewState(resource: URI): IEditorViewState | null {
const control = this.getControl();
if (!isCodeEditor(control)) {

View file

@ -127,7 +127,7 @@
cursor: pointer;
min-width: 110px;
min-height: 18px;
padding: 2px 8px;
padding: 2px 23px 2px 8px;
}
/* Rotate icons when panel is on right */

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