Merge remote-tracking branch 'origin/master' into enable-tslint

This commit is contained in:
Joao Moreno 2016-01-18 09:51:04 +01:00
commit ef3f9d9d88
52 changed files with 2123 additions and 1714 deletions

View file

@ -1,6 +1,6 @@
{
"account": "monacobuild",
"container": "debuggers",
"zip": "5fdb54d/node-debug.zip",
"zip": "17ccd91/node-debug.zip",
"output": ""
}

View file

@ -14,7 +14,7 @@
<key>settings</key>
<dict>
<key>foreground</key>
<string>#777759</string>
<string>#795E26</string>
</dict>
</dict>
<dict>

6
npm-shrinkwrap.json generated
View file

@ -416,9 +416,9 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
},
"vscode-debugprotocol": {
"version": "1.2.1",
"from": "vscode-debugprotocol@>=1.2.1",
"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.2.1.tgz"
"version": "1.3.0",
"from": "vscode-debugprotocol@>=1.3.0",
"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.3.0.tgz"
},
"vscode-textmate": {
"version": "1.0.9",

View file

@ -26,7 +26,7 @@
"iconv-lite": "^0.4.13",
"sax": "^1.1.1",
"semver": "^4.2.0",
"vscode-debugprotocol": "^1.2.1",
"vscode-debugprotocol": "^1.3.0",
"vscode-textmate": "^1.0.9",
"native-keymap": "^0.1.2",
"weak": "^1.0.1",

View file

@ -15,6 +15,12 @@ export interface IKeyboardController {
addListener(type:'input', callback:(event:Event)=>void): ()=>void;
addListener(type:string, callback:(event:any)=>void): ()=>void;
addListener2(type:'keydown', callback:(event:DomUtils.IKeyboardEvent)=>void): Lifecycle.IDisposable;
addListener2(type:'keypress', callback:(event:DomUtils.IKeyboardEvent)=>void): Lifecycle.IDisposable;
addListener2(type:'keyup', callback:(event:DomUtils.IKeyboardEvent)=>void): Lifecycle.IDisposable;
addListener2(type:'input', callback:(event:Event)=>void): Lifecycle.IDisposable;
addListener2(type:string, callback:(event:any)=>void): Lifecycle.IDisposable;
dispose(): void;
}
@ -46,10 +52,23 @@ export class KeyboardController implements IKeyboardController, Lifecycle.IDispo
public addListener(type:string, callback:(event:DomUtils.IKeyboardEvent)=>void):()=>void {
this._listeners[type] = callback;
return () => {
if (!this._listeners) {
// disposed
return;
}
this._listeners[type] = null;
};
}
public addListener2(type:string, callback:(event:DomUtils.IKeyboardEvent)=>void): Lifecycle.IDisposable {
let unbind = this.addListener(type, callback);
return {
dispose: () => {
unbind();
}
};
}
private _fire(type:string, event:any): void {
if (this._listeners.hasOwnProperty(type)) {
this._listeners[type](event);

View file

@ -8,58 +8,56 @@
import 'vs/css!./actionbar';
import nls = require('vs/nls');
import Lifecycle = require('vs/base/common/lifecycle');
import WinJS = require('vs/base/common/winjs.base');
import Builder = require('vs/base/browser/builder');
import lifecycle = require('vs/base/common/lifecycle');
import {Promise} from 'vs/base/common/winjs.base';
import {Builder, $} from 'vs/base/browser/builder';
import actions = require('vs/base/common/actions');
import DomUtils = require('vs/base/browser/dom');
import Events1 = require('vs/base/common/events');
import Types = require('vs/base/common/types');
import Events = require('vs/base/common/eventEmitter');
import Touch = require('vs/base/browser/touch');
import Keyboard = require('vs/base/browser/keyboardEvent');
import DOM = require('vs/base/browser/dom');
import {EventType as CommonEventType} from 'vs/base/common/events';
import types = require('vs/base/common/types');
import {IEventEmitter, EventEmitter, IEmitterEvent} from 'vs/base/common/eventEmitter';
import {Gesture, EventType} from 'vs/base/browser/touch';
import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent';
import {CommonKeybindings} from 'vs/base/common/keyCodes';
var $ = Builder.$;
export interface IActionItem extends Events.IEventEmitter {
actionRunner:actions.IActionRunner;
setActionContext(context:any):void;
render(element:HTMLElement):void;
isEnabled():boolean;
focus():void;
blur():void;
dispose():void;
export interface IActionItem extends IEventEmitter {
actionRunner: actions.IActionRunner;
setActionContext(context: any): void;
render(element: HTMLElement): void;
isEnabled(): boolean;
focus(): void;
blur(): void;
dispose(): void;
}
export class BaseActionItem extends Events.EventEmitter implements IActionItem {
export class BaseActionItem extends EventEmitter implements IActionItem {
public builder:Builder.Builder;
private gesture:Touch.Gesture;
public builder: Builder;
private gesture: Gesture;
private _actionRunner: actions.IActionRunner;
public _callOnDispose:Function[];
public _context:any;
public _action:actions.IAction;
public _callOnDispose: Function[];
public _context: any;
public _action: actions.IAction;
constructor(context:any, action:actions.IAction) {
constructor(context: any, action: actions.IAction) {
super();
this._callOnDispose = [];
this._context = context || this;
this._action = action;
if(action instanceof actions.Action) {
var l = (<actions.Action>action).addBulkListener((events:Events.IEmitterEvent[]) => {
if (action instanceof actions.Action) {
var l = (<actions.Action>action).addBulkListener((events: IEmitterEvent[]) => {
if(!this.builder) {
if (!this.builder) {
// we have not been rendered yet, so there
// is no point in updating the UI
return;
}
events.forEach((event:Events.IEmitterEvent) => {
events.forEach((event: IEmitterEvent) => {
switch(event.getType()) {
switch (event.getType()) {
case actions.Action.ENABLED:
this._updateEnabled();
break;
@ -98,77 +96,81 @@ export class BaseActionItem extends Events.EventEmitter implements IActionItem {
return this._actionRunner;
}
public getAction():actions.IAction {
public getAction(): actions.IAction {
return this._action;
}
public isEnabled():boolean {
public isEnabled(): boolean {
return this._action.enabled;
}
public setActionContext(newContext:any):void {
public setActionContext(newContext: any): void {
this._context = newContext;
}
public render(container:HTMLElement):void {
public render(container: HTMLElement): void {
this.builder = $(container);
this.gesture = new Touch.Gesture(container);
this.gesture = new Gesture(container);
this.builder.on(DomUtils.EventType.CLICK, (event:Event) => { this.onClick(event); });
this.builder.on(Touch.EventType.Tap, e => { this.onClick(e); });
this.builder.on(DOM.EventType.CLICK, (event: Event) => { this.onClick(event); });
this.builder.on(EventType.Tap, e => { this.onClick(e); });
this.builder.on('mousedown', (e:MouseEvent) => {
this.builder.on('mousedown', (e: MouseEvent) => {
if (e.button === 0 && this._action.enabled) {
this.builder.addClass('active');
}
});
this.builder.on(['mouseup', 'mouseout'], (e:MouseEvent) => {
this.builder.on(['mouseup', 'mouseout'], (e: MouseEvent) => {
if (e.button === 0 && this._action.enabled) {
this.builder.removeClass('active');
}
});
}
public onClick(event:Event):void {
DomUtils.EventHelper.stop(event, true);
public onClick(event: Event): void {
DOM.EventHelper.stop(event, true);
this._actionRunner.run(this._action, this._context || event);
}
public focus():void {
this.builder.domFocus();
this.builder.addClass('focused');
public focus(): void {
if (this.builder) {
this.builder.domFocus();
this.builder.addClass('focused');
}
}
public blur():void {
this.builder.removeClass('focused');
public blur(): void {
if (this.builder) {
this.builder.removeClass('focused');
}
}
public _updateEnabled():void {
public _updateEnabled(): void {
// implement in subclass
}
public _updateLabel():void {
public _updateLabel(): void {
// implement in subclass
}
public _updateTooltip():void {
public _updateTooltip(): void {
// implement in subclass
}
public _updateClass():void {
public _updateClass(): void {
// implement in subclass
}
public _updateChecked():void {
public _updateChecked(): void {
// implement in subclass
}
public _updateUnknown(event:Events.IEmitterEvent):void {
public _updateUnknown(event: IEmitterEvent): void {
// can implement in subclass
}
public dispose():void {
public dispose(): void {
super.dispose();
if (this.builder) {
@ -181,16 +183,16 @@ export class BaseActionItem extends Events.EventEmitter implements IActionItem {
this.gesture = null;
}
Lifecycle.cAll(this._callOnDispose);
lifecycle.cAll(this._callOnDispose);
}
}
export class Separator extends actions.Action {
public static ID = 'actions.monaco.separator';
public static ID = 'actions.monaco.separator';
constructor (label?:string, order?) {
constructor(label?: string, order?) {
super(Separator.ID, label, label ? 'separator text' : 'separator');
this.checked = false;
this.enabled = false;
@ -199,18 +201,18 @@ export class Separator extends actions.Action {
}
export interface IActionItemOptions {
icon?:boolean;
label?:boolean;
keybinding?:string;
icon?: boolean;
label?: boolean;
keybinding?: string;
}
export class ActionItem extends BaseActionItem {
$e:Builder.Builder;
private cssClass:string;
private options:IActionItemOptions;
$e: Builder;
private cssClass: string;
private options: IActionItemOptions;
constructor(context:any, action:actions.IAction, options:IActionItemOptions = {}) {
constructor(context: any, action: actions.IAction, options: IActionItemOptions = {}) {
super(context, action);
this.options = options;
@ -219,11 +221,11 @@ export class ActionItem extends BaseActionItem {
this.cssClass = '';
}
public render(container:HTMLElement):void {
public render(container: HTMLElement): void {
super.render(container);
this.$e = $('a.action-label').attr('tabIndex', '-1').appendTo(this.builder);
this.$e.attr({role: 'menuitem'});
this.$e.attr({ role: 'menuitem' });
if (this.options.label && this.options.keybinding) {
$('span.keybinding').text(this.options.keybinding).appendTo(this.builder);
@ -236,18 +238,18 @@ export class ActionItem extends BaseActionItem {
this._updateChecked();
}
public focus():void {
public focus(): void {
super.focus();
this.$e.domFocus();
}
public _updateLabel():void {
public _updateLabel(): void {
if (this.options.label) {
this.$e.text(this.getAction().label);
}
}
public _updateTooltip():void {
public _updateTooltip(): void {
var title: string = null;
if (this.getAction().tooltip) {
@ -266,7 +268,7 @@ export class ActionItem extends BaseActionItem {
}
}
public _updateClass():void {
public _updateClass(): void {
if (this.cssClass) {
this.$e.removeClass(this.cssClass);
}
@ -282,8 +284,8 @@ export class ActionItem extends BaseActionItem {
}
}
public _updateEnabled():void {
if(this.getAction().enabled) {
public _updateEnabled(): void {
if (this.getAction().enabled) {
this.builder.removeClass('disabled');
this.$e.removeClass('disabled');
} else {
@ -292,8 +294,8 @@ export class ActionItem extends BaseActionItem {
}
}
public _updateChecked():void {
if(this.getAction().checked) {
public _updateChecked(): void {
if (this.getAction().checked) {
this.$e.addClass('checked');
} else {
this.$e.removeClass('checked');
@ -303,7 +305,7 @@ export class ActionItem extends BaseActionItem {
export class ProgressItem extends BaseActionItem {
public render(parent:HTMLElement):void {
public render(parent: HTMLElement): void {
var container = document.createElement('div');
$(container).addClass('progress-item');
@ -326,15 +328,15 @@ export class ProgressItem extends BaseActionItem {
error.textContent = '!';
$(error).addClass('tag', 'error');
this.callOnDispose.push(this.addListener(Events1.EventType.BEFORE_RUN, () => {
this.callOnDispose.push(this.addListener(CommonEventType.BEFORE_RUN, () => {
$(progress).addClass('active');
$(done).removeClass('active');
$(error).removeClass('active');
}));
this.callOnDispose.push(this.addListener(Events1.EventType.RUN, (result) => {
this.callOnDispose.push(this.addListener(CommonEventType.RUN, (result) => {
$(progress).removeClass('active');
if(result.error) {
if (result.error) {
$(done).removeClass('active');
$(error).addClass('active');
} else {
@ -350,8 +352,8 @@ export class ProgressItem extends BaseActionItem {
parent.appendChild(container);
}
public dispose():void {
Lifecycle.cAll(this.callOnDispose);
public dispose(): void {
lifecycle.cAll(this.callOnDispose);
super.dispose();
}
}
@ -366,46 +368,47 @@ export interface IActionItemProvider {
}
export interface IActionBarOptions {
orientation?:ActionsOrientation;
context?:any;
actionItemProvider?:IActionItemProvider;
actionRunner?:actions.IActionRunner;
orientation?: ActionsOrientation;
context?: any;
disableTabIndex?: boolean;
actionItemProvider?: IActionItemProvider;
actionRunner?: actions.IActionRunner;
}
var defaultOptions:IActionBarOptions = {
var defaultOptions: IActionBarOptions = {
orientation: ActionsOrientation.HORIZONTAL,
context: null
};
export interface IActionOptions extends IActionItemOptions {
index?:number;
index?: number;
}
export class ActionBar extends Events.EventEmitter implements actions.IActionRunner {
export class ActionBar extends EventEmitter implements actions.IActionRunner {
private static nlsActionBarAccessibleLabel = nls.localize('actionBarAccessibleLabel', "Action Bar");
static DEFAULT_OPTIONS:IActionBarOptions = {
static DEFAULT_OPTIONS: IActionBarOptions = {
orientation: ActionsOrientation.HORIZONTAL
};
public options:IActionBarOptions;
private _actionRunner:actions.IActionRunner;
public options: IActionBarOptions;
private _actionRunner: actions.IActionRunner;
private _context: any;
// Items
public items:IActionItem[];
private focusedItem:number;
public items: IActionItem[];
private focusedItem: number;
// Elements
public domNode:HTMLElement;
private actionsList:HTMLElement;
public domNode: HTMLElement;
private actionsList: HTMLElement;
private toDispose: Lifecycle.IDisposable[];
private toDispose: lifecycle.IDisposable[];
constructor(container: HTMLElement, options?:IActionBarOptions);
constructor(container: Builder.Builder, options?:IActionBarOptions);
constructor(container: any, options:IActionBarOptions = defaultOptions) {
constructor(container: HTMLElement, options?: IActionBarOptions);
constructor(container: Builder, options?: IActionBarOptions);
constructor(container: any, options: IActionBarOptions = defaultOptions) {
super();
this.options = options;
this._context = options.context;
@ -424,7 +427,6 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
this.domNode = document.createElement('div');
this.domNode.className = 'monaco-action-bar';
this.domNode.tabIndex = 0;
var isVertical = this.options.orientation === ActionsOrientation.VERTICAL;
@ -432,11 +434,11 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
this.domNode.className += ' vertical';
}
$(this.domNode).on(DomUtils.EventType.KEY_DOWN, (e:KeyboardEvent) => {
var event = new Keyboard.StandardKeyboardEvent(e);
$(this.domNode).on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
var event = new StandardKeyboardEvent(e);
var eventHandled = true;
if (event.equals(isVertical? CommonKeybindings.UP_ARROW : CommonKeybindings.LEFT_ARROW)) {
if (event.equals(isVertical ? CommonKeybindings.UP_ARROW : CommonKeybindings.LEFT_ARROW)) {
this.focusPrevious();
} else if (event.equals(isVertical ? CommonKeybindings.DOWN_ARROW : CommonKeybindings.RIGHT_ARROW)) {
this.focusNext();
@ -448,20 +450,20 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
eventHandled = false;
}
if(eventHandled) {
if (eventHandled) {
event.preventDefault();
event.stopPropagation();
}
});
// Prevent native context menu on actions
$(this.domNode).on(DomUtils.EventType.CONTEXT_MENU, (e:Event) => {
$(this.domNode).on(DOM.EventType.CONTEXT_MENU, (e: Event) => {
e.preventDefault();
e.stopPropagation();
});
$(this.domNode).on(DomUtils.EventType.KEY_UP, (e:KeyboardEvent) => {
var event = new Keyboard.StandardKeyboardEvent(e);
$(this.domNode).on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
var event = new StandardKeyboardEvent(e);
if (event.equals(CommonKeybindings.ENTER)) {
this.doTrigger(event);
@ -470,9 +472,9 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
}
});
var focusTracker = DomUtils.trackFocus(this.domNode);
var focusTracker = DOM.trackFocus(this.domNode);
focusTracker.addBlurListener((e: Event) => {
if (document.activeElement === this.domNode || !DomUtils.isAncestor(document.activeElement, this.domNode)) {
if (document.activeElement === this.domNode || !DOM.isAncestor(document.activeElement, this.domNode)) {
this.emit('blur', e);
}
});
@ -483,7 +485,7 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
this.actionsList.setAttribute('aria-label', ActionBar.nlsActionBarAccessibleLabel);
this.domNode.appendChild(this.actionsList);
container = (container instanceof Builder.Builder) ? container.getHTMLElement() : container;
container = (container instanceof Builder) ? container.getHTMLElement() : container;
container.appendChild(this.domNode);
}
@ -507,25 +509,25 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
}
}
public getContainer():Builder.Builder {
public getContainer(): Builder {
return $(this.domNode);
}
public push(actions:actions.IAction, options?:IActionOptions):void;
public push(actions:actions.IAction[], options?:IActionOptions):void;
public push(actions:any, options:IActionOptions = {}):void {
if(!Array.isArray(actions)) {
public push(actions: actions.IAction, options?: IActionOptions): void;
public push(actions: actions.IAction[], options?: IActionOptions): void;
public push(actions: any, options: IActionOptions = {}): void {
if (!Array.isArray(actions)) {
actions = [actions];
}
var index = Types.isNumber(options.index) ? options.index : null;
var index = types.isNumber(options.index) ? options.index : null;
actions.forEach((action:actions.IAction) => {
actions.forEach((action: actions.IAction) => {
var actionItemElement = document.createElement('li');
actionItemElement.className = 'action-item';
actionItemElement.setAttribute('role', 'presentation');
var item:IActionItem = null;
var item: IActionItem = null;
if (this.options.actionItemProvider) {
item = this.options.actionItemProvider(action);
@ -546,31 +548,39 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
this.actionsList.insertBefore(actionItemElement, this.actionsList.children[index++]);
}
if (!this.options.disableTabIndex && !this.domNode.hasAttribute('tabIndex')) {
this.domNode.tabIndex = 0; // make sure an action bar with actions participates in tab navigation
}
this.items.push(item);
});
}
public clear():void {
var item:IActionItem;
public clear(): void {
var item: IActionItem;
while (item = this.items.pop()) {
item.dispose();
}
$(this.actionsList).empty();
if (!this.options.disableTabIndex) {
this.domNode.removeAttribute('tabIndex'); // empty action bar does not participate in tab navigation
}
}
public length():number {
public length(): number {
return this.items.length;
}
public isEmpty():boolean {
public isEmpty(): boolean {
return this.items.length === 0;
}
public onContentsChange():void {
this.emit(Events1.EventType.CONTENTS_CHANGED);
public onContentsChange(): void {
this.emit(CommonEventType.CONTENTS_CHANGED);
}
public focus(selectFirst?:boolean):void {
public focus(selectFirst?: boolean): void {
if (selectFirst && typeof this.focusedItem === 'undefined') {
this.focusedItem = 0;
}
@ -578,7 +588,7 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
this.updateFocus();
}
private focusNext():void {
private focusNext(): void {
if (typeof this.focusedItem === 'undefined') {
this.focusedItem = this.items.length - 1;
}
@ -598,7 +608,7 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
this.updateFocus();
}
private focusPrevious():void {
private focusPrevious(): void {
if (typeof this.focusedItem === 'undefined') {
this.focusedItem = 0;
}
@ -623,7 +633,7 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
this.updateFocus();
}
private updateFocus():void {
private updateFocus(): void {
if (typeof this.focusedItem === 'undefined') {
this.domNode.focus();
return;
@ -632,14 +642,14 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
for (var i = 0; i < this.items.length; i++) {
var item = this.items[i];
var actionItem = <any> item;
var actionItem = <any>item;
if(i === this.focusedItem) {
if (Types.isFunction(actionItem.focus)) {
if (i === this.focusedItem) {
if (types.isFunction(actionItem.focus)) {
actionItem.focus();
}
} else {
if (Types.isFunction(actionItem.blur)) {
if (types.isFunction(actionItem.blur)) {
actionItem.blur();
}
}
@ -648,30 +658,30 @@ export class ActionBar extends Events.EventEmitter implements actions.IActionRun
private doTrigger(event): void {
//nothing to focus
if(typeof this.focusedItem === 'undefined') {
if (typeof this.focusedItem === 'undefined') {
return;
}
// trigger action
var actionItem = (<BaseActionItem> this.items[this.focusedItem]);
var actionItem = (<BaseActionItem>this.items[this.focusedItem]);
this.run(actionItem._action, actionItem._context || event).done();
}
private cancel():void {
this.emit(Events1.EventType.CANCEL);
private cancel(): void {
this.emit(CommonEventType.CANCEL);
}
public run(action: actions.IAction, context?: any):WinJS.Promise {
public run(action: actions.IAction, context?: any): Promise {
return this._actionRunner.run(action, context);
}
public dispose():void {
public dispose(): void {
if (this.items !== null) {
this.clear();
}
this.items = null;
this.toDispose = Lifecycle.disposeAll(this.toDispose);
this.toDispose = lifecycle.disposeAll(this.toDispose);
this.getContainer().destroy();
@ -683,9 +693,9 @@ export class SelectActionItem extends BaseActionItem {
private select: HTMLSelectElement;
private options: string[];
private selected: number;
private toDispose: Lifecycle.IDisposable[];
private toDispose: lifecycle.IDisposable[];
constructor(ctx: any, action: actions.IAction, options:string[], selected:number) {
constructor(ctx: any, action: actions.IAction, options: string[], selected: number) {
super(ctx, action);
this.select = document.createElement('select');
@ -699,7 +709,7 @@ export class SelectActionItem extends BaseActionItem {
this.registerListeners();
}
public setOptions(options:string[], selected:number): void {
public setOptions(options: string[], selected: number): void {
this.options = options;
this.selected = selected;
@ -707,13 +717,13 @@ export class SelectActionItem extends BaseActionItem {
}
private registerListeners(): void {
this.toDispose.push(DomUtils.addStandardDisposableListener(this.select, 'change', (e) => {
this.toDispose.push(DOM.addStandardDisposableListener(this.select, 'change', (e) => {
this.actionRunner.run(this._action, e.target.value).done();
}));
}
public render(container:HTMLElement): void {
DomUtils.addClass(container, 'select-container');
public render(container: HTMLElement): void {
DOM.addClass(container, 'select-container');
container.appendChild(this.select);
this.doSetOptions();
}
@ -739,7 +749,7 @@ export class SelectActionItem extends BaseActionItem {
}
public dispose(): void {
this.toDispose = Lifecycle.disposeAll(this.toDispose);
this.toDispose = lifecycle.disposeAll(this.toDispose);
super.dispose();
}

View file

@ -6,17 +6,15 @@
'use strict';
import 'vs/css!./button';
import EventEmitter = require('vs/base/common/eventEmitter');
import {EventEmitter} from 'vs/base/common/eventEmitter';
import DOM = require('vs/base/browser/dom');
import Builder = require('vs/base/browser/builder');
import {Builder, $} from 'vs/base/browser/builder';
var $ = Builder.$;
export class Button extends EventEmitter {
export class Button extends EventEmitter.EventEmitter {
private $el: Builder;
private $el: Builder.Builder;
constructor(container: Builder.Builder);
constructor(container: Builder);
constructor(container: HTMLElement);
constructor(container: any) {
super();

View file

@ -17,7 +17,7 @@ export interface ICheckboxOpts {
title: string;
isChecked: boolean;
onChange: () => void;
onKeyDown?: (e:StandardKeyboardEvent) => void;
onKeyDown?: (e: StandardKeyboardEvent) => void;
}
export class Checkbox extends Widget {
@ -27,7 +27,7 @@ export class Checkbox extends Widget {
private _checked: boolean;
constructor(opts:ICheckboxOpts) {
constructor(opts: ICheckboxOpts) {
super();
this._opts = opts;
this._checked = this._opts.isChecked;
@ -70,7 +70,7 @@ export class Checkbox extends Widget {
return this._checked;
}
public set checked(newIsChecked:boolean) {
public set checked(newIsChecked: boolean) {
this._checked = newIsChecked;
this.domNode.setAttribute('aria-checked', String(this._checked));
this.domNode.className = this._className();

View file

@ -6,18 +6,16 @@
'use strict';
import 'vs/css!./countBadge';
import Builder = require('vs/base/browser/builder');
import Strings = require('vs/base/common/strings');
const $ = Builder.$;
import {Builder, $} from 'vs/base/browser/builder';
import strings = require('vs/base/common/strings');
export class CountBadge {
private $el: Builder.Builder;
private $el: Builder;
private count: number;
private titleFormat: string;
constructor(container: Builder.Builder, count?: number, titleFormat?: string);
constructor(container: Builder, count?: number, titleFormat?: string);
constructor(container: HTMLElement, count?: number, titleFormat?: string);
constructor(container: any, count?: number, titleFormat?: string) {
this.$el = $('.monaco-count-badge').appendTo(container);
@ -37,7 +35,7 @@ export class CountBadge {
private render() {
this.$el.text('' + this.count);
this.$el.title(Strings.format(this.titleFormat, this.count));
this.$el.title(strings.format(this.titleFormat, this.count));
}
public dispose() {

View file

@ -7,21 +7,19 @@
import 'vs/css!./toolbar';
import nls = require('vs/nls');
import Lifecycle = require('vs/base/common/lifecycle');
import Builder = require('vs/base/browser/builder');
import Types = require('vs/base/common/types');
import Actions = require('vs/base/common/actions');
import ActionBar = require('vs/base/browser/ui/actionbar/actionbar');
import Dropdown = require('vs/base/browser/ui/dropdown/dropdown');
import EventEmitter = require('vs/base/common/eventEmitter');
var $ = <Builder.QuickBuilder> Builder.$;
import {IDisposable} from 'vs/base/common/lifecycle';
import {Builder, $} from 'vs/base/browser/builder';
import types = require('vs/base/common/types');
import {Action, IActionRunner, IAction} from 'vs/base/common/actions';
import {ActionBar, ActionsOrientation, IActionItemProvider, BaseActionItem} from 'vs/base/browser/ui/actionbar/actionbar';
import {IContextMenuProvider, DropdownMenu, IActionProvider, ILabelRenderer, IDropdownMenuOptions} from 'vs/base/browser/ui/dropdown/dropdown';
import {ListenerUnbind} from 'vs/base/common/eventEmitter';
export var CONTEXT = 'context.toolbar';
export interface IToolBarOptions {
orientation?: ActionBar.ActionsOrientation;
actionItemProvider?: ActionBar.IActionItemProvider;
orientation?: ActionsOrientation;
actionItemProvider?: IActionItemProvider;
}
/**
@ -29,12 +27,12 @@ export interface IToolBarOptions {
*/
export class ToolBar {
private options: IToolBarOptions;
private actionBar: ActionBar.ActionBar;
private actionBar: ActionBar;
private toggleMenuAction: ToggleMenuAction;
private toggleMenuActionItem: DropdownMenuActionItem;
private hasSecondaryActions: boolean;
constructor(container: HTMLElement, contextMenuProvider: Dropdown.IContextMenuProvider, options: IToolBarOptions = { orientation: ActionBar.ActionsOrientation.HORIZONTAL }) {
constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) {
this.options = options;
this.toggleMenuAction = new ToggleMenuAction();
@ -42,9 +40,9 @@ export class ToolBar {
element.className = 'monaco-toolbar';
container.appendChild(element);
this.actionBar = new ActionBar.ActionBar($(element), {
this.actionBar = new ActionBar($(element), {
orientation: options.orientation,
actionItemProvider: (action: Actions.Action) => {
actionItemProvider: (action: Action) => {
// Return special action item for the toggle menu action
if (action.id === ToggleMenuAction.ID) {
@ -60,7 +58,7 @@ export class ToolBar {
(<ToggleMenuAction>action).menuActions,
contextMenuProvider,
this.options.actionItemProvider,
this.options.orientation === ActionBar.ActionsOrientation.HORIZONTAL,
this.options.orientation === ActionsOrientation.HORIZONTAL,
this.actionRunner,
'toolbar-toggle-more'
);
@ -73,19 +71,19 @@ export class ToolBar {
});
}
public set actionRunner(actionRunner: Actions.IActionRunner) {
public set actionRunner(actionRunner: IActionRunner) {
this.actionBar.actionRunner = actionRunner;
}
public get actionRunner(): Actions.IActionRunner {
public get actionRunner(): IActionRunner {
return this.actionBar.actionRunner;
}
public getContainer(): Builder.Builder {
public getContainer(): Builder {
return this.actionBar.getContainer();
}
public setActions(primaryActions: Actions.IAction[], secondaryActions?: Actions.IAction[]): () => void {
public setActions(primaryActions: IAction[], secondaryActions?: IAction[]): () => void {
return () => {
var primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
@ -101,7 +99,7 @@ export class ToolBar {
};
}
public addPrimaryAction(primaryActions: Actions.IAction): () => void {
public addPrimaryAction(primaryActions: IAction): () => void {
return () => {
// Add after the "..." action if we have secondary actions
@ -127,11 +125,11 @@ export class ToolBar {
}
}
class ToggleMenuAction extends Actions.Action {
class ToggleMenuAction extends Action {
public static ID = 'toolbar.toggle.more';
private _menuActions: Actions.IAction[];
private _menuActions: IAction[];
constructor() {
super(ToggleMenuAction.ID, nls.localize('more', "More"), null, true);
@ -141,24 +139,24 @@ class ToggleMenuAction extends Actions.Action {
return this._menuActions;
}
public set menuActions(actions: Actions.IAction[]) {
public set menuActions(actions: IAction[]) {
this._menuActions = actions;
}
}
export class DropdownMenuActionItem extends ActionBar.BaseActionItem {
export class DropdownMenuActionItem extends BaseActionItem {
private menuActionsOrProvider: any;
private animateClick: boolean;
private dropdownMenu: Dropdown.DropdownMenu;
private toUnbind: EventEmitter.ListenerUnbind;
private contextMenuProvider: Dropdown.IContextMenuProvider;
private actionItemProvider: ActionBar.IActionItemProvider;
private dropdownMenu: DropdownMenu;
private toUnbind: ListenerUnbind;
private contextMenuProvider: IContextMenuProvider;
private actionItemProvider: IActionItemProvider;
private clazz: string;
constructor(action: Actions.IAction, menuActions: Actions.IAction[], contextMenuProvider: Dropdown.IContextMenuProvider, actionItemProvider: ActionBar.IActionItemProvider, animateClick: boolean, actionRunner: Actions.IActionRunner, clazz: string);
constructor(action: Actions.IAction, actionProvider: Dropdown.IActionProvider, contextMenuProvider: Dropdown.IContextMenuProvider, actionItemProvider: ActionBar.IActionItemProvider, animateClick: boolean, actionRunner: Actions.IActionRunner, clazz: string);
constructor(action: Actions.IAction, menuActionsOrProvider: any, contextMenuProvider: Dropdown.IContextMenuProvider, actionItemProvider: ActionBar.IActionItemProvider, animateClick: boolean, actionRunner: Actions.IActionRunner, clazz: string) {
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, animateClick: boolean, actionRunner: IActionRunner, clazz: string);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, animateClick: boolean, actionRunner: IActionRunner, clazz: string);
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, animateClick: boolean, actionRunner: IActionRunner, clazz: string) {
super(null, action);
this.menuActionsOrProvider = menuActionsOrProvider;
@ -172,8 +170,8 @@ export class DropdownMenuActionItem extends ActionBar.BaseActionItem {
public render(container: HTMLElement): void {
super.render(container);
var labelRenderer: Dropdown.ILabelRenderer = (el: HTMLElement): Lifecycle.IDisposable => {
var e = Builder.$('a.action-label').attr({
var labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => {
var e = $('a.action-label').attr({
tabIndex: '-1',
role: 'menuitem',
title: this._action.label || '',
@ -199,19 +197,19 @@ export class DropdownMenuActionItem extends ActionBar.BaseActionItem {
return null;
};
var options: Dropdown.IDropdownMenuOptions = {
var options: IDropdownMenuOptions = {
contextMenuProvider: this.contextMenuProvider,
labelRenderer: labelRenderer
};
// Render the DropdownMenu around a simple action to toggle it
if (Types.isArray(this.menuActionsOrProvider)) {
if (types.isArray(this.menuActionsOrProvider)) {
options.actions = this.menuActionsOrProvider;
} else {
options.actionProvider = this.menuActionsOrProvider;
}
this.dropdownMenu = new Dropdown.DropdownMenu(container, options);
this.dropdownMenu = new DropdownMenu(container, options);
this.dropdownMenu.menuOptions = {
actionItemProvider: this.actionItemProvider,

View file

@ -176,7 +176,7 @@ function _matchesCamelCase(word: string, camelCaseWord: string, i: number, j: nu
// Heuristic to avoid computing camel case matcher for words that don't
// look like camelCaseWords.
function isCamelCaseWord(word: string): boolean {
if (word.length > 40) {
if (word.length > 60) {
return false;
}

View file

@ -48,10 +48,10 @@ export function score(target: string, query: string, cache?: {[id: string]: numb
const queryLower = query.toLowerCase();
let index = 0;
let lastIndexOf = 0;
let lastIndexOf = -1;
let score = 0;
while (index < queryLen) {
var indexOf = targetLower.indexOf(queryLower[index], lastIndexOf);
var indexOf = targetLower.indexOf(queryLower[index], lastIndexOf + 1);
if (indexOf < 0) {
score = 0; // This makes sure that the query is contained in the target
break;
@ -94,4 +94,31 @@ export function score(target: string, query: string, cache?: {[id: string]: numb
function isUpper(code: number): boolean {
return 65 <= code && code <= 90;
}
/**
* A fast method to check if a given string would produce a score > 0 for the given query.
*/
export function matches(target: string, queryLower: string): boolean {
if (!target || !queryLower) {
return false // return early if target or query are undefined
}
const queryLen = queryLower.length;
const targetLower = target.toLowerCase();
let index = 0;
let lastIndexOf = -1;
while (index < queryLen) {
var indexOf = targetLower.indexOf(queryLower[index], lastIndexOf + 1);
if (indexOf < 0) {
return false;
}
lastIndexOf = indexOf;
index++;
}
return true;
}

View file

@ -41,4 +41,14 @@ suite('Scorer', () => {
assert.equal(Object.getOwnPropertyNames(cache).length, 2);
});
test("matches", function() {
assert.ok(scorer.matches('hello world', 'h'));
assert.ok(!scorer.matches('hello world', 'q'));
assert.ok(scorer.matches('hello world', 'hw'));
assert.ok(scorer.matches('hello world', 'horl'));
assert.ok(scorer.matches('hello world', 'd'));
assert.ok(!scorer.matches('hello world', 'wh'));
assert.ok(!scorer.matches('d', 'dd'));
});
});

View file

@ -4,33 +4,18 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import Env = require('vs/base/common/platform');
import Browser = require('vs/base/browser/browser');
import Objects = require('vs/base/common/objects');
import EventEmitter = require('vs/base/common/eventEmitter');
import Strings = require('vs/base/common/strings');
import DomUtils = require('vs/base/browser/dom');
import ConfigurationRegistry = require('vs/platform/configuration/common/configurationRegistry');
import EditorCommon = require('vs/editor/common/editorCommon');
import {DefaultConfig} from 'vs/editor/common/config/defaultConfig';
import {HandlerDispatcher} from 'vs/editor/common/controller/handlerDispatcher';
import Config = require('vs/editor/common/config/config');
import EditorBrowser = require('vs/editor/browser/editorBrowser');
import ElementSizeObserver = require('vs/editor/browser/config/elementSizeObserver');
import * as Env from 'vs/base/common/platform';
import * as Browser from 'vs/base/browser/browser';
import * as DomUtils from 'vs/base/browser/dom';
import {IEditorStyling, IGuessedIndentation, IDimension} from 'vs/editor/common/editorCommon';
import {ElementSizeObserver} from 'vs/editor/browser/config/elementSizeObserver';
import {CommonEditorConfiguration, ICSSConfig} from 'vs/editor/common/config/commonEditorConfig';
interface ICSSBasedConfigurationChangeListener {
(): void;
}
import Event, {Emitter} from 'vs/base/common/event';
import {Disposable} from 'vs/base/common/lifecycle';
class CSSBasedConfigurationCache {
private _keys: { [key: string]: EditorCommon.IEditorStyling; };
private _keys: { [key: string]: IEditorStyling; };
private _values: { [key: string]: ICSSConfig; };
constructor() {
@ -38,50 +23,68 @@ class CSSBasedConfigurationCache {
this._values = {};
}
public has(item: EditorCommon.IEditorStyling): boolean {
return this._values.hasOwnProperty(CSSBasedConfigurationCache.key(item));
public has(item: IEditorStyling): boolean {
return this._values.hasOwnProperty(CSSBasedConfigurationCache._key(item));
}
public get(item: EditorCommon.IEditorStyling): ICSSConfig {
return this._values[CSSBasedConfigurationCache.key(item)];
public get(item: IEditorStyling): ICSSConfig {
return this._values[CSSBasedConfigurationCache._key(item)];
}
public put(item: EditorCommon.IEditorStyling, value: ICSSConfig): void {
this._values[CSSBasedConfigurationCache.key(item)] = value;
public put(item: IEditorStyling, value: ICSSConfig): void {
this._values[CSSBasedConfigurationCache._key(item)] = value;
}
public getKeys(): EditorCommon.IEditorStyling[]{
var r: EditorCommon.IEditorStyling[] = [];
for (var key in this._keys) {
public getKeys(): IEditorStyling[] {
let r: IEditorStyling[] = [];
for (let key in this._keys) {
r.push(this._keys[key]);
}
return r;
}
private static key(item: EditorCommon.IEditorStyling): string {
private static _key(item: IEditorStyling): string {
return item.editorClassName + '-' + item.fontFamily + '-' + item.fontSize + '-' + item.lineHeight;
}
}
class CSSBasedConfiguration {
class CSSBasedConfiguration extends Disposable {
public static INSTANCE = new CSSBasedConfiguration();
private static _HALF_WIDTH_TYPICAL = 'n';
private static _FULL_WIDTH_TYPICAL = '\uff4d';
private static _USUAL_CHARS = '0123456789' + CSSBasedConfiguration._HALF_WIDTH_TYPICAL + CSSBasedConfiguration._FULL_WIDTH_TYPICAL;
private static _CACHE = new CSSBasedConfigurationCache();
private static _CHANGE_LISTENERS: ICSSBasedConfigurationChangeListener[] = [];
private _cache: CSSBasedConfigurationCache;
private _changeMonitorTimeout: number = -1;
public static readConfiguration(editorClassName: string, fontFamily: string, fontSize: number, lineHeight: number): ICSSConfig {
var styling: EditorCommon.IEditorStyling = {
private _onDidChange = this._register(new Emitter<void>());
public onDidChange: Event<void> = this._onDidChange.event;
constructor() {
super();
this._cache = new CSSBasedConfigurationCache();
}
public dispose(): void {
if (this._changeMonitorTimeout !== -1) {
clearTimeout(this._changeMonitorTimeout);
this._changeMonitorTimeout = -1;
}
super.dispose();
}
public readConfiguration(editorClassName: string, fontFamily: string, fontSize: number, lineHeight: number): ICSSConfig {
let styling: IEditorStyling = {
editorClassName: editorClassName,
fontFamily: fontFamily,
fontSize: fontSize,
lineHeight: lineHeight
};
if (!CSSBasedConfiguration._CACHE.has(styling)) {
var readConfig = CSSBasedConfiguration._actualReadConfiguration(styling);
if (!this._cache.has(styling)) {
let readConfig = CSSBasedConfiguration._actualReadConfiguration(styling);
if (readConfig.lineHeight <= 2 || readConfig.typicalHalfwidthCharacterWidth <= 2 || readConfig.typicalFullwidthCharacterWidth <= 2 || readConfig.maxDigitWidth <= 2) {
// Hey, it's Bug 14341 ... we couldn't read
@ -89,62 +92,41 @@ class CSSBasedConfiguration {
readConfig.typicalHalfwidthCharacterWidth = Math.max(readConfig.typicalHalfwidthCharacterWidth, readConfig.fontSize, 5);
readConfig.typicalFullwidthCharacterWidth = Math.max(readConfig.typicalFullwidthCharacterWidth, readConfig.fontSize, 5);
readConfig.maxDigitWidth = Math.max(readConfig.maxDigitWidth, readConfig.fontSize, 5);
CSSBasedConfiguration._installChangeMonitor();
this._installChangeMonitor();
}
CSSBasedConfiguration._CACHE.put(styling, readConfig);
this._cache.put(styling, readConfig);
}
return CSSBasedConfiguration._CACHE.get(styling);
return this._cache.get(styling);
}
private static _CHANGE_MONITOR_TIMEOUT: number = -1;
private static _installChangeMonitor(): void {
if (CSSBasedConfiguration._CHANGE_MONITOR_TIMEOUT === -1) {
CSSBasedConfiguration._CHANGE_MONITOR_TIMEOUT = setTimeout(() => {
CSSBasedConfiguration._CHANGE_MONITOR_TIMEOUT = -1;
CSSBasedConfiguration._monitorForChanges();
private _installChangeMonitor(): void {
if (this._changeMonitorTimeout === -1) {
this._changeMonitorTimeout = setTimeout(() => {
this._changeMonitorTimeout = -1;
this._monitorForChanges();
}, 500);
}
}
private static _monitorForChanges(): void {
var shouldInstallChangeMonitor = false;
var keys = CSSBasedConfiguration._CACHE.getKeys();
for (var i = 0; i < keys.length; i++) {
var styling = keys[i];
private _monitorForChanges(): void {
let shouldInstallChangeMonitor = false;
let keys = this._cache.getKeys();
for (let i = 0; i < keys.length; i++) {
let styling = keys[i];
var newValue = CSSBasedConfiguration._actualReadConfiguration(styling);
let newValue = CSSBasedConfiguration._actualReadConfiguration(styling);
if (newValue.lineHeight <= 2 || newValue.typicalHalfwidthCharacterWidth <= 2 || newValue.typicalFullwidthCharacterWidth <= 2 || newValue.maxDigitWidth <= 2) {
// We still couldn't read the CSS config
shouldInstallChangeMonitor = true;
} else {
CSSBasedConfiguration._CACHE.put(styling, newValue);
CSSBasedConfiguration._invokeChangeListeners();
this._cache.put(styling, newValue);
this._onDidChange.fire();
}
}
if (shouldInstallChangeMonitor) {
CSSBasedConfiguration._installChangeMonitor();
}
}
private static _invokeChangeListeners(): void {
var listeners = CSSBasedConfiguration._CHANGE_LISTENERS.slice(0);
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
}
public static addChangeListener(listener: ICSSBasedConfigurationChangeListener): void {
CSSBasedConfiguration._CHANGE_LISTENERS.push(listener);
}
public static removeChangeListener(listener: ICSSBasedConfigurationChangeListener): void {
for (var i = 0; i < CSSBasedConfiguration._CHANGE_LISTENERS.length; i++) {
if (CSSBasedConfiguration._CHANGE_LISTENERS[i] === listener) {
CSSBasedConfiguration._CHANGE_LISTENERS.splice(i, 1);
break;
}
this._installChangeMonitor();
}
}
@ -153,13 +135,13 @@ class CSSBasedConfiguration {
}
private static _createTestElement(index:number, character:string): HTMLSpanElement {
var r = document.createElement('span');
r.id = CSSBasedConfiguration._testElementId(index);
let r = document.createElement('span');
r.id = this._testElementId(index);
var testString = (character === ' ' ? '&nbsp;' : character);
let testString = (character === ' ' ? '&nbsp;' : character);
// Repeat character 256 (2^8) times
for (var i = 0; i < 8; i++) {
for (let i = 0; i < 8; i++) {
testString += testString;
}
@ -167,20 +149,20 @@ class CSSBasedConfiguration {
return r;
}
private static _createTestElements(styling: EditorCommon.IEditorStyling): HTMLElement {
var container = document.createElement('div');
private static _createTestElements(styling: IEditorStyling): HTMLElement {
let container = document.createElement('div');
Configuration.applyEditorStyling(container, styling);
container.style.position = 'absolute';
container.style.top = '-50000px';
container.style.width = '50000px';
for (var i = 0, len = CSSBasedConfiguration._USUAL_CHARS.length; i < len; i++) {
for (let i = 0, len = CSSBasedConfiguration._USUAL_CHARS.length; i < len; i++) {
container.appendChild(document.createElement('br'));
container.appendChild(CSSBasedConfiguration._createTestElement(i, CSSBasedConfiguration._USUAL_CHARS[i]));
container.appendChild(this._createTestElement(i, CSSBasedConfiguration._USUAL_CHARS[i]));
}
var heightTestElementId = CSSBasedConfiguration._testElementId(CSSBasedConfiguration._USUAL_CHARS.length);
var heightTestElement = document.createElement('div');
let heightTestElementId = this._testElementId(CSSBasedConfiguration._USUAL_CHARS.length);
let heightTestElement = document.createElement('div');
heightTestElement.id = heightTestElementId;
heightTestElement.appendChild(document.createTextNode('heightTestContent'));
@ -190,48 +172,44 @@ class CSSBasedConfiguration {
return container;
}
private static _readTestElementWidth(index:number): number {
return document.getElementById(CSSBasedConfiguration._testElementId(index)).offsetWidth / 256;
}
private static _readFromTestElements(): number[] {
var r:number[] = [];
let r:number[] = [];
for (var i = 0, len = CSSBasedConfiguration._USUAL_CHARS.length; i < len; i++) {
r.push(CSSBasedConfiguration._readTestElementWidth(i));
for (let i = 0, len = CSSBasedConfiguration._USUAL_CHARS.length; i < len; i++) {
r.push(document.getElementById(this._testElementId(i)).offsetWidth / 256);
}
return r;
}
private static _actualReadConfiguration(styling: EditorCommon.IEditorStyling): ICSSConfig {
private static _actualReadConfiguration(styling: IEditorStyling): ICSSConfig {
// Create a test container with all these test elements
var testContainer = CSSBasedConfiguration._createTestElements(styling);
let testContainer = this._createTestElements(styling);
// Add the container to the DOM
document.body.appendChild(testContainer);
// Read various properties
var usualCharsWidths = CSSBasedConfiguration._readFromTestElements();
var firstTestElement = document.getElementById(CSSBasedConfiguration._testElementId(0));
var computedStyle = DomUtils.getComputedStyle(firstTestElement);
var result_font = CSSBasedConfiguration._getFontFromComputedStyle(computedStyle);
var result_fontSize = computedStyle ? parseInt(computedStyle.fontSize, 10) : 0;
let usualCharsWidths = this._readFromTestElements();
let firstTestElement = document.getElementById(this._testElementId(0));
let computedStyle = DomUtils.getComputedStyle(firstTestElement);
let result_font = this._getFontFromComputedStyle(computedStyle);
let result_fontSize = computedStyle ? parseInt(computedStyle.fontSize, 10) : 0;
var heightTestElement = document.getElementById(CSSBasedConfiguration._testElementId(CSSBasedConfiguration._USUAL_CHARS.length));
var result_lineHeight = heightTestElement.clientHeight;
let heightTestElement = document.getElementById(this._testElementId(CSSBasedConfiguration._USUAL_CHARS.length));
let result_lineHeight = heightTestElement.clientHeight;
// Remove the container from the DOM
document.body.removeChild(testContainer);
// Find maximum digit width and thinnest character width
var maxDigitWidth = 0,
let maxDigitWidth = 0,
typicalHalfwidthCharacterWidth = 0,
typicalFullwidthCharacterWidth = 0;
for (var i = 0, len = CSSBasedConfiguration._USUAL_CHARS.length; i < len; i++) {
var character = CSSBasedConfiguration._USUAL_CHARS.charAt(i);
for (let i = 0, len = CSSBasedConfiguration._USUAL_CHARS.length; i < len; i++) {
let character = CSSBasedConfiguration._USUAL_CHARS.charAt(i);
if (character >= '0' && character <= '9') {
maxDigitWidth = Math.max(maxDigitWidth, usualCharsWidths[i]);
@ -270,14 +248,9 @@ class CSSBasedConfiguration {
}
}
interface ICSSConfigMap {
[key:string]:ICSSConfig;
}
export class Configuration extends CommonEditorConfiguration {
public static applyEditorStyling(domNode: HTMLElement, styling: EditorCommon.IEditorStyling): void {
public static applyEditorStyling(domNode: HTMLElement, styling: IEditorStyling): void {
domNode.className = styling.editorClassName;
if (styling.fontFamily && styling.fontFamily.length > 0) {
domNode.style.fontFamily = styling.fontFamily;
@ -296,16 +269,14 @@ export class Configuration extends CommonEditorConfiguration {
}
}
private _cssBasedConfigurationChangeListener: () => void;
private _elementSizeObserver: ElementSizeObserver.ElementSizeObserver;
private _elementSizeObserver: ElementSizeObserver;
constructor(options:any, referenceDomElement:HTMLElement = null, indentationGuesser:(tabSize:number)=>EditorCommon.IGuessedIndentation = null) {
this._elementSizeObserver = new ElementSizeObserver.ElementSizeObserver(referenceDomElement, () => this._onReferenceDomElementSizeChanged());
constructor(options:any, referenceDomElement:HTMLElement = null, indentationGuesser:(tabSize:number)=>IGuessedIndentation = null) {
this._elementSizeObserver = new ElementSizeObserver(referenceDomElement, () => this._onReferenceDomElementSizeChanged());
super(options, indentationGuesser);
this._cssBasedConfigurationChangeListener = () => this._onCSSBasedConfigurationChanged();
CSSBasedConfiguration.addChangeListener(this._cssBasedConfigurationChangeListener);
this._register(CSSBasedConfiguration.INSTANCE.onDidChange(() => () => this._onCSSBasedConfigurationChanged()));
if (this._configWithDefaults.getEditorOptions().automaticLayout) {
this._elementSizeObserver.startObserving();
@ -320,18 +291,17 @@ export class Configuration extends CommonEditorConfiguration {
this._recomputeOptions();
}
public observeReferenceElement(dimension?:EditorCommon.IDimension): void {
public observeReferenceElement(dimension?:IDimension): void {
this._elementSizeObserver.observe(dimension);
}
public dispose(): void {
CSSBasedConfiguration.removeChangeListener(this._cssBasedConfigurationChangeListener);
this._elementSizeObserver.dispose();
super.dispose();
}
protected _getEditorClassName(theme:string, fontLigatures:boolean): string {
var extra = '';
let extra = '';
if (Browser.isIE11orEarlier) {
extra += 'ie ';
} else if (Browser.isFirefox) {
@ -358,6 +328,6 @@ export class Configuration extends CommonEditorConfiguration {
}
protected readConfiguration(editorClassName: string, fontFamily: string, fontSize: number, lineHeight: number): ICSSConfig {
return CSSBasedConfiguration.readConfiguration(editorClassName, fontFamily, fontSize, lineHeight);
return CSSBasedConfiguration.INSTANCE.readConfiguration(editorClassName, fontFamily, fontSize, lineHeight);
}
}

View file

@ -4,11 +4,10 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Lifecycle = require('vs/base/common/lifecycle');
import EditorBrowser = require('vs/editor/browser/editorBrowser');
import EditorCommon = require('vs/editor/common/editorCommon');
import {IDimension} from 'vs/editor/common/editorCommon';
import {Disposable} from 'vs/base/common/lifecycle';
export class ElementSizeObserver implements Lifecycle.IDisposable {
export class ElementSizeObserver extends Disposable {
private referenceDomElement:HTMLElement;
private measureReferenceDomElementToken:number;
@ -17,6 +16,7 @@ export class ElementSizeObserver implements Lifecycle.IDisposable {
private height:number;
constructor(referenceDomElement:HTMLElement, changeCallback:()=>void) {
super();
this.referenceDomElement = referenceDomElement;
this.changeCallback = changeCallback;
this.measureReferenceDomElementToken = -1;
@ -25,6 +25,11 @@ export class ElementSizeObserver implements Lifecycle.IDisposable {
this.measureReferenceDomElement(false);
}
public dispose(): void {
this.stopObserving();
super.dispose();
}
public getWidth(): number {
return this.width;
}
@ -33,28 +38,24 @@ export class ElementSizeObserver implements Lifecycle.IDisposable {
return this.height;
}
public dispose(): void {
this.stopObserving();
}
public startObserving(): void {
if (this.measureReferenceDomElementToken === -1) {
this.measureReferenceDomElementToken = window.setInterval(() => this.measureReferenceDomElement(true), 100);
this.measureReferenceDomElementToken = setInterval(() => this.measureReferenceDomElement(true), 100);
}
}
public stopObserving(): void {
if (this.measureReferenceDomElementToken !== -1) {
window.clearInterval(this.measureReferenceDomElementToken);
clearInterval(this.measureReferenceDomElementToken);
this.measureReferenceDomElementToken = -1;
}
}
public observe(dimension?:EditorCommon.IDimension): void {
public observe(dimension?:IDimension): void {
this.measureReferenceDomElement(true, dimension);
}
private measureReferenceDomElement(callChangeCallback:boolean, dimension?:EditorCommon.IDimension): void {
private measureReferenceDomElement(callChangeCallback:boolean, dimension?:IDimension): void {
var observedWidth = 0;
var observedHeight = 0;
if (dimension) {

View file

@ -13,729 +13,189 @@ import EditorBrowser = require('vs/editor/browser/editorBrowser');
import EventEmitter = require('vs/base/common/eventEmitter');
import {ViewEventHandler} from 'vs/editor/common/viewModel/viewEventHandler';
import Schedulers = require('vs/base/common/async');
import Lifecycle = require('vs/base/common/lifecycle');
import * as Lifecycle from 'vs/base/common/lifecycle';
import Strings = require('vs/base/common/strings');
import {Range} from 'vs/editor/common/core/range';
import {Position} from 'vs/editor/common/core/position';
import {CommonKeybindings} from 'vs/base/common/keyCodes';
import Event, {Emitter} from 'vs/base/common/event';
import {ITextAreaWrapper, ITextAreaStyle, ISimpleModel, TextAreaHandler} from 'vs/editor/browser/controller/textAreaHandler';
enum ReadFromTextArea {
Type,
Paste
}
class TextAreaWrapper extends Lifecycle.Disposable implements ITextAreaWrapper {
class TextAreaState {
private value:string;
private selectionStart:number;
private selectionEnd:number;
private selectionToken:number;
private _textArea: HTMLTextAreaElement;
constructor(value:string, selectionStart:number, selectionEnd:number, selectionToken:number) {
this.value = value;
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
this.selectionToken = selectionToken;
private _onKeyDown = this._register(new Emitter<DomUtils.IKeyboardEvent>());
public onKeyDown: Event<DomUtils.IKeyboardEvent> = this._onKeyDown.event;
private _onKeyUp = this._register(new Emitter<DomUtils.IKeyboardEvent>());
public onKeyUp: Event<DomUtils.IKeyboardEvent> = this._onKeyUp.event;
private _onKeyPress = this._register(new Emitter<DomUtils.IKeyboardEvent>());
public onKeyPress: Event<DomUtils.IKeyboardEvent> = this._onKeyPress.event;
private _onCompositionStart = this._register(new Emitter<void>());
public onCompositionStart: Event<void> = this._onCompositionStart.event;
private _onCompositionEnd = this._register(new Emitter<void>());
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
private _onInput = this._register(new Emitter<void>());
public onInput: Event<void> = this._onInput.event;
private _onCut = this._register(new Emitter<ClipboardEvent>());
public onCut: Event<ClipboardEvent> = this._onCut.event;
private _onCopy = this._register(new Emitter<ClipboardEvent>());
public onCopy: Event<ClipboardEvent> = this._onCopy.event;
private _onPaste = this._register(new Emitter<ClipboardEvent>());
public onPaste: Event<ClipboardEvent> = this._onPaste.event;
constructor(textArea: HTMLTextAreaElement) {
super();
this._textArea = textArea;
let kbController = this._register(new keyboardController.KeyboardController(this._textArea));
this._register(kbController.addListener2('keydown', (e) => this._onKeyDown.fire(e)));
this._register(kbController.addListener2('keyup', (e) => this._onKeyUp.fire(e)));
this._register(kbController.addListener2('keypress', (e) => this._onKeyPress.fire(e)));
this._register(DomUtils.addDisposableListener(this._textArea, 'compositionstart', (e) => this._onCompositionStart.fire()));
this._register(DomUtils.addDisposableListener(this._textArea, 'compositionend', (e) => this._onCompositionEnd.fire()));
this._register(DomUtils.addDisposableListener(this._textArea, 'input', (e) => this._onInput.fire()));
this._register(DomUtils.addDisposableListener(this._textArea, 'cut', (e) => this._onCut.fire(e)));
this._register(DomUtils.addDisposableListener(this._textArea, 'copy', (e) => this._onCopy.fire(e)));
this._register(DomUtils.addDisposableListener(this._textArea, 'paste', (e) => this._onPaste.fire(e)));
}
public toString(): string {
return '[ <' + this.value + '>, selectionStart: ' + this.selectionStart + ', selectionEnd: ' + this.selectionEnd + ']';
public get value(): string {
return this._textArea.value;
}
public static fromTextArea(textArea:HTMLTextAreaElement, selectionToken:number): TextAreaState {
return new TextAreaState(textArea.value, textArea.selectionStart, textArea.selectionEnd, selectionToken);
public set value(value:string) {
this._textArea.value = value;
}
public static fromEditorSelectionAndPreviousState(model:EditorCommon.IViewModel, selection:EditorCommon.IEditorRange, previousSelectionToken:number): TextAreaState {
if (Browser.isIPad) {
// Do not place anything in the textarea for the iPad
return new TextAreaState('', 0, 0, selectionStartLineNumber);
}
var LIMIT_CHARS = 100;
var PADDING_LINES_COUNT = 0;
var selectionStartLineNumber = selection.startLineNumber,
selectionStartColumn = selection.startColumn,
selectionEndLineNumber = selection.endLineNumber,
selectionEndColumn = selection.endColumn,
selectionEndLineNumberMaxColumn = model.getLineMaxColumn(selectionEndLineNumber);
// If the selection is empty and we have switched line numbers, expand selection to full line (helps Narrator trigger a full line read)
if (selection.isEmpty() && previousSelectionToken !== selectionStartLineNumber) {
selectionStartColumn = 1;
selectionEndColumn = selectionEndLineNumberMaxColumn;
}
// `pretext` contains the text before the selection
var pretext = '';
var startLineNumber = Math.max(1, selectionStartLineNumber - PADDING_LINES_COUNT);
if (startLineNumber < selectionStartLineNumber) {
pretext = model.getValueInRange(new Range(startLineNumber, 1, selectionStartLineNumber, 1), EditorCommon.EndOfLinePreference.LF);
}
pretext += model.getValueInRange(new Range(selectionStartLineNumber, 1, selectionStartLineNumber, selectionStartColumn), EditorCommon.EndOfLinePreference.LF);
if (pretext.length > LIMIT_CHARS) {
pretext = pretext.substring(pretext.length - LIMIT_CHARS, pretext.length);
}
// `posttext` contains the text after the selection
var posttext = '';
var endLineNumber = Math.min(selectionEndLineNumber + PADDING_LINES_COUNT, model.getLineCount());
posttext += model.getValueInRange(new Range(selectionEndLineNumber, selectionEndColumn, selectionEndLineNumber, selectionEndLineNumberMaxColumn), EditorCommon.EndOfLinePreference.LF);
if (endLineNumber > selectionEndLineNumber) {
posttext = '\n' + model.getValueInRange(new Range(selectionEndLineNumber + 1, 1, endLineNumber, model.getLineMaxColumn(endLineNumber)), EditorCommon.EndOfLinePreference.LF);
}
if (posttext.length > LIMIT_CHARS) {
posttext = posttext.substring(0, LIMIT_CHARS);
}
// `text` contains the text of the selection
var text = model.getValueInRange(new Range(selectionStartLineNumber, selectionStartColumn, selectionEndLineNumber, selectionEndColumn), EditorCommon.EndOfLinePreference.LF);
if (text.length > 2 * LIMIT_CHARS) {
text = text.substring(0, LIMIT_CHARS) + String.fromCharCode(8230) + text.substring(text.length - LIMIT_CHARS, text.length);
}
return new TextAreaState(pretext + text + posttext, pretext.length, pretext.length + text.length, selectionStartLineNumber);
public get selectionStart(): number {
return this._textArea.selectionStart;
}
public getSelectionStart(): number {
return this.selectionStart;
public get selectionEnd(): number {
return this._textArea.selectionEnd;
}
public resetSelection(): void {
this.selectionStart = this.value.length;
this.selectionEnd = this.value.length;
}
public getValue(): string {
return this.value;
}
public getSelectionToken(): number {
return this.selectionToken;
}
public applyToTextArea(textArea:HTMLTextAreaElement, select:boolean): void {
if (textArea.value !== this.value) {
textArea.value = this.value;
}
if (select) {
try {
var scrollState = DomUtils.saveParentsScrollTop(textArea);
textArea.focus();
textArea.setSelectionRange(this.selectionStart, this.selectionEnd);
DomUtils.restoreParentsScrollTop(textArea, scrollState);
} catch(e) {
// Sometimes IE throws when setting selection (e.g. textarea is off-DOM)
}
public setSelectionRange(selectionStart:number, selectionEnd:number): void {
// console.log('setSelectionRange: ' + selectionStart + ', ' + selectionEnd);
try {
var scrollState = DomUtils.saveParentsScrollTop(this._textArea);
this._textArea.focus();
this._textArea.setSelectionRange(selectionStart, selectionEnd);
DomUtils.restoreParentsScrollTop(this._textArea, scrollState);
} catch(e) {
// Sometimes IE throws when setting selection (e.g. textarea is off-DOM)
console.log('an error has been thrown!');
}
}
public extractNewText(previousState:TextAreaState): string {
if (this.selectionStart !== this.selectionEnd) {
// There is a selection in the textarea => ignore input
return '';
public setStyle(style:ITextAreaStyle): void {
if (typeof style.top !== 'undefined') {
this._textArea.style.top = style.top;
}
if (!previousState) {
return this.value;
if (typeof style.left !== 'undefined') {
this._textArea.style.left = style.left;
}
var previousPrefix = previousState.value.substring(0, previousState.selectionStart);
var previousSuffix = previousState.value.substring(previousState.selectionEnd, previousState.value.length);
// In IE, pressing Insert will bring the typing into overwrite mode
if (Browser.isIE11orEarlier && document.queryCommandValue('OverWrite')) {
previousSuffix = previousSuffix.substr(1);
if (typeof style.width !== 'undefined') {
this._textArea.style.width = style.width;
}
var value = this.value;
if (value.substring(0, previousPrefix.length) === previousPrefix) {
value = value.substring(previousPrefix.length);
if (typeof style.height !== 'undefined') {
this._textArea.style.height = style.height;
}
if (value.substring(value.length - previousSuffix.length, value.length) === previousSuffix) {
value = value.substring(0, value.length - previousSuffix.length);
}
return value;
}
}
export class KeyboardHandler extends ViewEventHandler implements Lifecycle.IDisposable {
private context:EditorBrowser.IViewContext;
private viewController:EditorBrowser.IViewController;
private viewHelper:EditorBrowser.IKeyboardHandlerHelper;
private textArea:HTMLTextAreaElement;
private selection:EditorCommon.IEditorRange;
private hasFocus:boolean;
private kbController:keyboardController.IKeyboardController;
private listenersToRemove:EventEmitter.ListenerUnbind[];
private asyncReadFromTextArea: Schedulers.RunOnceScheduler;
private asyncSetSelectionToTextArea: Schedulers.RunOnceScheduler;
private asyncTriggerCut: Schedulers.RunOnceScheduler;
// keypress, paste & composition end also trigger an input event
// the popover input method on macs triggers only an input event
// in this case the expectInputTime would be too much in the past
private justHadAPaste:boolean;
private justHadACut:boolean;
private lastKeyPressTime:number;
private lastCompositionEndTime:number;
private lastValueWrittenToTheTextArea:string;
private cursorPosition:EditorCommon.IEditorPosition;
private contentLeft:number;
private contentWidth:number;
private scrollLeft:number;
private previousSetTextAreaState:TextAreaState;
private textareaIsShownAtCursor: boolean;
private lastCopiedValue: string;
private lastCopiedValueIsFromEmptySelection: boolean;
private textArea:TextAreaWrapper;
private textAreaHandler:TextAreaHandler;
constructor(context:EditorBrowser.IViewContext, viewController:EditorBrowser.IViewController, viewHelper:EditorBrowser.IKeyboardHandlerHelper) {
super();
this.context = context;
this.viewController = viewController;
this.textArea = viewHelper.textArea;
this.textArea = new TextAreaWrapper(viewHelper.textArea);
this.viewHelper = viewHelper;
this.selection = new Range(1, 1, 1, 1);
this.cursorPosition = new Position(1, 1);
this.contentLeft = 0;
this.contentWidth = 0;
this.scrollLeft = 0;
this.asyncReadFromTextArea = new Schedulers.RunOnceScheduler(null, 0);
this.asyncSetSelectionToTextArea = new Schedulers.RunOnceScheduler(() => this._writePlaceholderAndSelectTextArea(), 0);
this.asyncTriggerCut = new Schedulers.RunOnceScheduler(() => this._triggerCut(), 0);
this.textAreaHandler = new TextAreaHandler(this.textArea, {
getModel: (): ISimpleModel => this.context.model,
emitKeyDown: (e:DomUtils.IKeyboardEvent): void => this.viewController.emitKeyDown(e),
emitKeyUp: (e:DomUtils.IKeyboardEvent): void => this.viewController.emitKeyUp(e),
paste: (source:string, txt:string, pasteOnNewLine:boolean): void => this.viewController.paste(source, txt, pasteOnNewLine),
type: (source:string, txt:string): void => this.viewController.type(source, txt),
replacePreviousChar: (source:string, txt:string): void => this.viewController.replacePreviousChar(source, txt),
cut: (source:string): void => this.viewController.cut(source),
visibleRangeForPositionRelativeToEditor: (lineNumber:number, column1:number, column2:number): { column1: EditorBrowser.VisibleRange; column2: EditorBrowser.VisibleRange; } => {
var revealInterestingColumn1Event:EditorCommon.IViewRevealRangeEvent = {
range: new Range(lineNumber, column1, lineNumber, column1),
verticalType: EditorCommon.VerticalRevealType.Simple,
revealHorizontal: true
};
this.context.privateViewEventBus.emit(EditorCommon.ViewEventNames.RevealRangeEvent, revealInterestingColumn1Event);
this.lastCopiedValue = null;
this.lastCopiedValueIsFromEmptySelection = false;
this.previousSetTextAreaState = null;
// Find range pixel position
var visibleRange1 = this.viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column1);
var visibleRange2 = this.viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column2);
this.hasFocus = false;
this.justHadAPaste = false;
this.justHadACut = false;
this.lastKeyPressTime = 0;
this.lastCompositionEndTime = 0;
this.lastValueWrittenToTheTextArea = '';
this.kbController = new keyboardController.KeyboardController(this.textArea);
this.listenersToRemove = [];
this.listenersToRemove.push(this.kbController.addListener('keydown', (e) => this._onKeyDown(e)));
this.listenersToRemove.push(this.kbController.addListener('keyup', (e) => this._onKeyUp(e)));
this.listenersToRemove.push(this.kbController.addListener('keypress', (e) => this._onKeyPress(e)));
// this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'change', (e) => this._scheduleLookout(EditorCommon.Handler.Type)));
this.textareaIsShownAtCursor = false;
this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'compositionstart', (e) => {
var timeSinceLastCompositionEnd = (new Date().getTime()) - this.lastCompositionEndTime;
if (!this.textareaIsShownAtCursor) {
this.textareaIsShownAtCursor = true;
this.showTextAreaAtCursor(timeSinceLastCompositionEnd >= 100);
return {
column1: visibleRange1,
column2: visibleRange2
}
},
startIME: (): void => {
DomUtils.addClass(this.viewHelper.viewDomNode, 'ime-input');
},
stopIME: (): void => {
DomUtils.removeClass(this.viewHelper.viewDomNode, 'ime-input');
}
this.asyncReadFromTextArea.cancel();
}));
this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'compositionend', (e) => {
if (this.textareaIsShownAtCursor) {
this.textareaIsShownAtCursor = false;
this.hideTextArea();
}
this.lastCompositionEndTime = (new Date()).getTime();
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
}));
// on the iPad the text area is not fast enough to get the content of the keypress,
// so we leverage the input event instead
if (Browser.isIPad) {
this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'input', (e) => {
var myTime = (new Date()).getTime();
// A keypress will trigger an input event (very quickly)
var keyPressDeltaTime = myTime - this.lastKeyPressTime;
if (keyPressDeltaTime <= 500) {
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
this.lastKeyPressTime = 0;
}
}));
}
// on the mac the character viewer input generates an input event (no keypress)
// on windows, the Chinese IME, when set to insert wide punctuation generates an input event (no keypress)
this.listenersToRemove.push(this.kbController.addListener('input', (e) => {
// Ignore input event if we are in composition mode
if (!this.textareaIsShownAtCursor) {
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
}
}));
if (Platform.isMacintosh) {
this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'input', (e) => {
// We are fishing for the input event that comes in the mac popover input method case
// A paste will trigger an input event, but the event might happen very late
if (this.justHadAPaste) {
this.justHadAPaste = false;
return;
}
// A cut will trigger an input event, but the event might happen very late
if (this.justHadACut) {
this.justHadACut = false;
return;
}
var myTime = (new Date()).getTime();
// A keypress will trigger an input event (very quickly)
var keyPressDeltaTime = myTime - this.lastKeyPressTime;
if (keyPressDeltaTime <= 500) {
return;
}
// A composition end will trigger an input event (very quickly)
var compositionEndDeltaTime = myTime - this.lastCompositionEndTime;
if (compositionEndDeltaTime <= 500) {
return;
}
// Ignore input if we are in the middle of a composition
if (this.textareaIsShownAtCursor) {
return;
}
// Ignore if the textarea has selection
if (this.textArea.selectionStart !== this.textArea.selectionEnd) {
return;
}
// In Chrome, only the first character gets replaced, while in Safari the entire line gets replaced
var typedText:string;
var textAreaValue = this.textArea.value;
if (!Browser.isChrome) {
// TODO: Also check this on Safari & FF before removing this
return;
}
if (this.lastValueWrittenToTheTextArea.length !== textAreaValue.length) {
return;
}
var prefixLength = Strings.commonPrefixLength(this.lastValueWrittenToTheTextArea, textAreaValue);
var suffixLength = Strings.commonSuffixLength(this.lastValueWrittenToTheTextArea, textAreaValue);
if (prefixLength + suffixLength + 1 !== textAreaValue.length) {
return;
}
typedText = textAreaValue.charAt(prefixLength);
this.executeReplacePreviousChar(typedText);
this.previousSetTextAreaState = TextAreaState.fromTextArea(this.textArea, 0);
this.asyncSetSelectionToTextArea.schedule();
}));
}
this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'cut', (e) => this._onCut(e)));
this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'copy', (e) => this._onCopy(e)));
this.listenersToRemove.push(DomUtils.addListener(this.textArea, 'paste', (e) => this._onPaste(e)));
this._writePlaceholderAndSelectTextArea();
}, this.context.configuration);
this.context.addEventHandler(this);
}
public dispose(): void {
this.context.removeEventHandler(this);
this.listenersToRemove.forEach((element) => {
element();
});
this.listenersToRemove = [];
this.kbController.dispose();
this.asyncReadFromTextArea.dispose();
this.asyncSetSelectionToTextArea.dispose();
this.asyncTriggerCut.dispose();
this.textAreaHandler.dispose();
this.textArea.dispose();
}
private showTextAreaAtCursor(emptyIt:boolean): void {
var interestingLineNumber:number,
interestingColumn1:number,
interestingColumn2:number;
// In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled.
if (Browser.isIE11orEarlier) {
// Ensure selection start is in viewport
interestingLineNumber = this.selection.startLineNumber;
interestingColumn1 = this.selection.startColumn;
interestingColumn2 = this.previousSetTextAreaState.getSelectionStart() + 1;
} else {
// Ensure primary cursor is in viewport
interestingLineNumber = this.cursorPosition.lineNumber;
interestingColumn1 = this.cursorPosition.column;
interestingColumn2 = interestingColumn1;
}
// Ensure range is in viewport
var revealInterestingColumn1Event:EditorCommon.IViewRevealRangeEvent = {
range: new Range(interestingLineNumber, interestingColumn1, interestingLineNumber, interestingColumn1),
verticalType: EditorCommon.VerticalRevealType.Simple,
revealHorizontal: true
};
this.context.privateViewEventBus.emit(EditorCommon.ViewEventNames.RevealRangeEvent, revealInterestingColumn1Event);
// Find range pixel position
var visibleRange1 = this.viewHelper.visibleRangeForPositionRelativeToEditor(interestingLineNumber, interestingColumn1);
var visibleRange2 = this.viewHelper.visibleRangeForPositionRelativeToEditor(interestingLineNumber, interestingColumn2);
if (Browser.isIE11orEarlier) {
// Position textarea at the beginning of the line
if (visibleRange1 && visibleRange2) {
this.textArea.style.top = visibleRange1.top + 'px';
this.textArea.style.left = this.contentLeft + visibleRange1.left - visibleRange2.left - this.scrollLeft + 'px';
this.textArea.style.width = this.contentWidth + 'px';
}
} else {
// Position textarea at cursor location
if (visibleRange1) {
this.textArea.style.left = this.contentLeft + visibleRange1.left - this.scrollLeft + 'px';
this.textArea.style.top = visibleRange1.top + 'px';
}
// Empty the textarea
if (emptyIt) {
this.setTextAreaState(new TextAreaState('', 0, 0, 0), false);
}
}
// Show the textarea
this.textArea.style.height = this.context.configuration.editor.lineHeight + 'px';
DomUtils.addClass(this.viewHelper.viewDomNode, 'ime-input');
}
private hideTextArea(): void {
this.textArea.style.height = '';
this.textArea.style.width = '';
this.textArea.style.left = '0px';
this.textArea.style.top = '0px';
DomUtils.removeClass(this.viewHelper.viewDomNode, 'ime-input');
}
// --- begin event handlers
public onScrollChanged(e:EditorCommon.IScrollEvent): boolean {
this.scrollLeft = e.scrollLeft;
this.textAreaHandler.onScrollChanged(e);
return false;
}
public onViewFocusChanged(isFocused:boolean): boolean {
this.hasFocus = isFocused;
if (this.hasFocus) {
this.asyncSetSelectionToTextArea.schedule();
}
this.textAreaHandler.onViewFocusChanged(isFocused);
return false;
}
public onCursorSelectionChanged(e:EditorCommon.IViewCursorSelectionChangedEvent): boolean {
this.selection = e.selection;
this.asyncSetSelectionToTextArea.schedule();
this.textAreaHandler.onCursorSelectionChanged(e);
return false;
}
public onCursorPositionChanged(e:EditorCommon.IViewCursorPositionChangedEvent): boolean {
this.cursorPosition = e.position;
this.textAreaHandler.onCursorPositionChanged(e);
return false;
}
public onLayoutChanged(layoutInfo:EditorCommon.IEditorLayoutInfo): boolean {
this.contentLeft = layoutInfo.contentLeft;
this.contentWidth = layoutInfo.contentWidth;
this.textAreaHandler.onLayoutChanged(layoutInfo);
return false;
}
// --- end event handlers
private setTextAreaState(textAreaState:TextAreaState, select:boolean): void {
// IE doesn't like calling select on a hidden textarea and the textarea is hidden during the tests
var shouldSetSelection = select && this.hasFocus;
if (!shouldSetSelection) {
textAreaState.resetSelection();
}
this.lastValueWrittenToTheTextArea = textAreaState.getValue();
textAreaState.applyToTextArea(this.textArea, shouldSetSelection);
this.previousSetTextAreaState = textAreaState;
}
private _onKeyDown(e:DomUtils.IKeyboardEvent): void {
if (e.equals(CommonKeybindings.ESCAPE)) {
// Prevent default always for `Esc`, otherwise it will generate a keypress
// See http://msdn.microsoft.com/en-us/library/ie/ms536939(v=vs.85).aspx
e.preventDefault();
}
this.viewController.emitKeyDown(e);
// Work around for issue spotted in electron on the mac
// TODO@alex: check if this issue exists after updating electron
// Steps:
// * enter a line at an offset
// * go down to a line with [
// * go up, go left, go right
// => press ctrl+h => a keypress is generated even though the keydown is prevent defaulted
// Another case would be if focus goes outside the app on keydown (spotted under windows)
// Steps:
// * press Ctrl+K
// * press R
// => focus moves out while keydown is not finished
setTimeout(() => {
// cancel reading if previous keydown was canceled, but a keypress/input were still generated
if (e.browserEvent && e.browserEvent.defaultPrevented) {
// this._scheduleReadFromTextArea
this.asyncReadFromTextArea.cancel();
this.asyncSetSelectionToTextArea.schedule();
}
}, 0);
}
private _onKeyUp(e:DomUtils.IKeyboardEvent): void {
this.viewController.emitKeyUp(e);
}
private _onKeyPress(e:DomUtils.IKeyboardEvent): void {
if (!this.hasFocus) {
// Sometimes, when doing Alt-Tab, in FF, a 'keypress' is sent before a 'focus'
return;
}
this.lastKeyPressTime = (new Date()).getTime();
// on the iPad the text area is not fast enough to get the content of the keypress,
// so we leverage the input event instead
if (!Browser.isIPad) {
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
}
}
// ------------- Operations that are always executed asynchronously
private _scheduleReadFromTextArea(command:ReadFromTextArea): void {
this.asyncSetSelectionToTextArea.cancel();
this.asyncReadFromTextArea.setRunner(() => this._readFromTextArea(command));
this.asyncReadFromTextArea.schedule();
}
/**
* Read text from textArea and trigger `command` on the editor
*/
private _readFromTextArea(command:ReadFromTextArea): void {
var previousSelectionToken = this.previousSetTextAreaState ? this.previousSetTextAreaState.getSelectionToken() : 0;
var observedState = TextAreaState.fromTextArea(this.textArea, previousSelectionToken);
var txt = observedState.extractNewText(this.previousSetTextAreaState);
if (txt !== '') {
if (command === ReadFromTextArea.Type) {
// console.log("deduced input:", txt);
this.executeType(txt);
} else {
this.executePaste(txt);
}
}
this.previousSetTextAreaState = observedState;
this.asyncSetSelectionToTextArea.schedule();
}
private executePaste(txt:string): void {
if(txt === '') {
return;
}
var pasteOnNewLine = false;
if (Browser.enableEmptySelectionClipboard) {
pasteOnNewLine = (txt === this.lastCopiedValue && this.lastCopiedValueIsFromEmptySelection);
}
this.viewController.paste('keyboard', txt, pasteOnNewLine);
}
private executeType(txt:string): void {
if(txt === '') {
return;
}
this.viewController.type('keyboard', txt);
}
private executeReplacePreviousChar(txt: string): void {
this.viewController.replacePreviousChar('keyboard', txt);
}
private _writePlaceholderAndSelectTextArea(): void {
if (!this.textareaIsShownAtCursor) {
// Do not write to the textarea if it is visible.
var previousSelectionToken = this.previousSetTextAreaState ? this.previousSetTextAreaState.getSelectionToken() : 0;
var newState = TextAreaState.fromEditorSelectionAndPreviousState(this.context.model, this.selection, previousSelectionToken);
this.setTextAreaState(newState, true);
}
}
// ------------- Clipboard operations
private _onPaste(e:Event): void {
if (e && (<any>e).clipboardData) {
e.preventDefault();
this.executePaste((<any>e).clipboardData.getData('text/plain'));
} else if (e && (<any>window).clipboardData) {
e.preventDefault();
this.executePaste((<any>window).clipboardData.getData('Text'));
} else {
if (this.textArea.selectionStart !== this.textArea.selectionEnd) {
// Clean up the textarea, to get a clean paste
this.setTextAreaState(new TextAreaState('', 0, 0, 0), false);
}
this._scheduleReadFromTextArea(ReadFromTextArea.Paste);
}
this.justHadAPaste = true;
}
private _onCopy(e:Event): void {
this._ensureClipboardGetsEditorSelection(e);
}
private _triggerCut(): void {
this.viewController.cut('keyboard');
}
private _onCut(e:Event): void {
this._ensureClipboardGetsEditorSelection(e);
this.asyncTriggerCut.schedule();
this.justHadACut = true;
}
private _ensureClipboardGetsEditorSelection(e:Event): void {
var whatToCopy = this._getPlainTextToCopy();
if (e && (<any>e).clipboardData) {
(<any>e).clipboardData.setData('text/plain', whatToCopy);
// (<any>e).clipboardData.setData('text/html', this._getHTMLToCopy());
e.preventDefault();
} else if (e && (<any>window).clipboardData) {
(<any>window).clipboardData.setData('Text', whatToCopy);
e.preventDefault();
} else {
this.setTextAreaState(new TextAreaState(whatToCopy, 0, whatToCopy.length, 0), true);
}
if (Browser.enableEmptySelectionClipboard) {
if (Browser.isFirefox) {
// When writing "LINE\r\n" to the clipboard and then pasting,
// Firefox pastes "LINE\n", so let's work around this quirk
this.lastCopiedValue = whatToCopy.replace(/\r\n/g, '\n');
} else {
this.lastCopiedValue = whatToCopy;
}
var selections = this.context.model.getSelections();
this.lastCopiedValueIsFromEmptySelection = (selections.length === 1 && selections[0].isEmpty());
}
}
private _getPlainTextToCopy(): string {
var newLineCharacter = (Platform.isWindows ? '\r\n' : '\n');
var eolPref = (Platform.isWindows ? EditorCommon.EndOfLinePreference.CRLF : EditorCommon.EndOfLinePreference.LF);
var selections = this.context.model.getSelections();
if (selections.length === 1) {
var range:EditorCommon.IEditorRange = selections[0];
if (range.isEmpty()) {
if (Browser.enableEmptySelectionClipboard) {
var modelLineNumber = this.context.model.convertViewPositionToModelPosition(range.startLineNumber, 1).lineNumber;
return this.context.model.getModelLineContent(modelLineNumber) + newLineCharacter;
} else {
return '';
}
}
return this.context.model.getValueInRange(range, eolPref);
} else {
selections = selections.slice(0).sort(Range.compareRangesUsingStarts);
var result: string[] = [];
for (var i = 0; i < selections.length; i++) {
result.push(this.context.model.getValueInRange(selections[i], eolPref));
}
return result.join(newLineCharacter);
}
}
// private static _getHTMLLine(model:Editor.IModel, lineNumber:number, startColumn:number, endColumn:number, output:string[]): void {
// var lineText = model.getLineContent(lineNumber);
// var tokens = model.getLineTokens(lineNumber);
//
// if (lineText.length > 0) {
// var charCode:number,
// i:number,
// len = lineText.length,
// tokenIndex = -1,
// nextTokenIndex = (tokens.length > tokenIndex + 1 ? tokens[tokenIndex + 1].startIndex : len);
//
// for (i = 0; i < len; i++) {
// if (i === nextTokenIndex) {
// tokenIndex++;
// nextTokenIndex = (tokens.length > tokenIndex + 1 ? tokens[tokenIndex + 1].startIndex : len);
// if (i > 0) {
// output.push('</span>');
// }
// output.push('<span class="token ');
// output.push(tokens[tokenIndex].type.replace(/[^a-z0-9]/gi, ' '));
// output.push('">');
// }
//
// charCode = lineText.charCodeAt(i);
//
// if (charCode === _lowerThan) {
// output.push('&lt;');
// } else if (charCode === _greaterThan) {
// output.push('&gt;');
// } else if (charCode === _ampersand) {
// output.push('&amp;');
// } else {
// output.push(lineText.charAt(i));
// }
// }
// }
// }
//
// private _getHTMLToCopy(): string {
// var range:Editor.IEditorRange = this.context.cursor.getSelection();
// var append = '';
// if (range.isEmpty()) {
// var lineNumber = range.startLineNumber;
// range = new Range(lineNumber, 1, lineNumber, this.context.model.getLineMaxColumn(lineNumber));
// append = '\n';
// }
//
// var r:string[] = [];
// for (var i = range.startLineNumber; i <= range.endLineNumber; i++) {
// KeyboardHandler._getHTMLLine(this.context.model, i, 1, this.context.model.getLineMaxColumn(i), r);
// }
//
// console.log(r.join('') + append);
//
// return r.join('') + append;
// }
}
}

View file

@ -0,0 +1,723 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import EditorCommon = require('vs/editor/common/editorCommon');
import DomUtils = require('vs/base/browser/dom');
import Platform = require('vs/base/common/platform');
import Browser = require('vs/base/browser/browser');
import EditorBrowser = require('vs/editor/browser/editorBrowser');
import EventEmitter = require('vs/base/common/eventEmitter');
import Schedulers = require('vs/base/common/async');
import * as Lifecycle from 'vs/base/common/lifecycle';
import Strings = require('vs/base/common/strings');
import {Range} from 'vs/editor/common/core/range';
import {Position} from 'vs/editor/common/core/position';
import {CommonKeybindings} from 'vs/base/common/keyCodes';
import Event, {Emitter} from 'vs/base/common/event';
enum ReadFromTextArea {
Type,
Paste
}
export interface ITextAreaWrapper {
onKeyDown: Event<DomUtils.IKeyboardEvent>;
onKeyUp: Event<DomUtils.IKeyboardEvent>;
onKeyPress: Event<DomUtils.IKeyboardEvent>;
onCompositionStart: Event<void>;
onCompositionEnd: Event<void>;
onInput: Event<void>;
onCut: Event<ClipboardEvent>;
onCopy: Event<ClipboardEvent>;
onPaste: Event<ClipboardEvent>;
value: string;
selectionStart: number;
selectionEnd: number;
setSelectionRange(selectionStart:number, selectionEnd:number): void;
setStyle(style:ITextAreaStyle): void;
}
class TextAreaState {
private value:string;
private selectionStart:number;
private selectionEnd:number;
private selectionToken:number;
constructor(value:string, selectionStart:number, selectionEnd:number, selectionToken:number) {
this.value = value;
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
this.selectionToken = selectionToken;
}
public toString(): string {
return '[ <' + this.value + '>, selectionStart: ' + this.selectionStart + ', selectionEnd: ' + this.selectionEnd + ']';
}
public static fromTextArea(textArea:ITextAreaWrapper, selectionToken:number): TextAreaState {
return new TextAreaState(textArea.value, textArea.selectionStart, textArea.selectionEnd, selectionToken);
}
public static fromEditorSelectionAndPreviousState(model:ISimpleModel, selection:EditorCommon.IEditorRange, previousSelectionToken:number): TextAreaState {
if (Browser.isIPad) {
// Do not place anything in the textarea for the iPad
return new TextAreaState('', 0, 0, selectionStartLineNumber);
}
var LIMIT_CHARS = 100;
var PADDING_LINES_COUNT = 0;
var selectionStartLineNumber = selection.startLineNumber,
selectionStartColumn = selection.startColumn,
selectionEndLineNumber = selection.endLineNumber,
selectionEndColumn = selection.endColumn,
selectionEndLineNumberMaxColumn = model.getLineMaxColumn(selectionEndLineNumber);
// If the selection is empty and we have switched line numbers, expand selection to full line (helps Narrator trigger a full line read)
if (selection.isEmpty() && previousSelectionToken !== selectionStartLineNumber) {
selectionStartColumn = 1;
selectionEndColumn = selectionEndLineNumberMaxColumn;
}
// `pretext` contains the text before the selection
var pretext = '';
var startLineNumber = Math.max(1, selectionStartLineNumber - PADDING_LINES_COUNT);
if (startLineNumber < selectionStartLineNumber) {
pretext = model.getValueInRange(new Range(startLineNumber, 1, selectionStartLineNumber, 1), EditorCommon.EndOfLinePreference.LF);
}
pretext += model.getValueInRange(new Range(selectionStartLineNumber, 1, selectionStartLineNumber, selectionStartColumn), EditorCommon.EndOfLinePreference.LF);
if (pretext.length > LIMIT_CHARS) {
pretext = pretext.substring(pretext.length - LIMIT_CHARS, pretext.length);
}
// `posttext` contains the text after the selection
var posttext = '';
var endLineNumber = Math.min(selectionEndLineNumber + PADDING_LINES_COUNT, model.getLineCount());
posttext += model.getValueInRange(new Range(selectionEndLineNumber, selectionEndColumn, selectionEndLineNumber, selectionEndLineNumberMaxColumn), EditorCommon.EndOfLinePreference.LF);
if (endLineNumber > selectionEndLineNumber) {
posttext = '\n' + model.getValueInRange(new Range(selectionEndLineNumber + 1, 1, endLineNumber, model.getLineMaxColumn(endLineNumber)), EditorCommon.EndOfLinePreference.LF);
}
if (posttext.length > LIMIT_CHARS) {
posttext = posttext.substring(0, LIMIT_CHARS);
}
// `text` contains the text of the selection
var text = model.getValueInRange(new Range(selectionStartLineNumber, selectionStartColumn, selectionEndLineNumber, selectionEndColumn), EditorCommon.EndOfLinePreference.LF);
if (text.length > 2 * LIMIT_CHARS) {
text = text.substring(0, LIMIT_CHARS) + String.fromCharCode(8230) + text.substring(text.length - LIMIT_CHARS, text.length);
}
return new TextAreaState(pretext + text + posttext, pretext.length, pretext.length + text.length, selectionStartLineNumber);
}
public getSelectionStart(): number {
return this.selectionStart;
}
public resetSelection(): void {
this.selectionStart = this.value.length;
this.selectionEnd = this.value.length;
}
public getValue(): string {
return this.value;
}
public getSelectionToken(): number {
return this.selectionToken;
}
public applyToTextArea(textArea:ITextAreaWrapper, select:boolean): void {
// console.log('applyToTextArea: ' + this.toString());
if (textArea.value !== this.value) {
textArea.value = this.value;
}
if (select) {
textArea.setSelectionRange(this.selectionStart, this.selectionEnd);
}
}
public extractNewText(previousState:TextAreaState): string {
if (this.selectionStart !== this.selectionEnd) {
// There is a selection in the textarea => ignore input
return '';
}
if (!previousState) {
return this.value;
}
var previousPrefix = previousState.value.substring(0, previousState.selectionStart);
var previousSuffix = previousState.value.substring(previousState.selectionEnd, previousState.value.length);
// In IE, pressing Insert will bring the typing into overwrite mode
if (Browser.isIE11orEarlier && document.queryCommandValue('OverWrite')) {
previousSuffix = previousSuffix.substr(1);
}
var value = this.value;
if (value.substring(0, previousPrefix.length) === previousPrefix) {
value = value.substring(previousPrefix.length);
}
if (value.substring(value.length - previousSuffix.length, value.length) === previousSuffix) {
value = value.substring(0, value.length - previousSuffix.length);
}
return value;
}
}
export interface ITextAreaStyle {
top: string;
left: string;
width: string;
height: string;
}
export interface ITextEditor {
getModel(): ISimpleModel;
emitKeyDown(e:DomUtils.IKeyboardEvent): void;
emitKeyUp(e:DomUtils.IKeyboardEvent): void;
paste(source:string, txt:string, pasteOnNewLine:boolean): void;
type(source:string, txt:string): void;
replacePreviousChar(source:string, txt:string): void;
cut(source:string): void;
visibleRangeForPositionRelativeToEditor(lineNumber:number, column1:number, column2:number): { column1: EditorBrowser.VisibleRange; column2: EditorBrowser.VisibleRange; };
startIME(): void;
stopIME(): void;
}
export interface ISimpleModel {
getLineMaxColumn(lineNumber:number): number;
getValueInRange(range:EditorCommon.IRange, eol:EditorCommon.EndOfLinePreference): string;
getModelLineContent(lineNumber:number): string;
getLineCount(): number;
convertViewPositionToModelPosition(viewLineNumber:number, viewColumn:number): EditorCommon.IEditorPosition;
}
export class TextAreaHandler implements Lifecycle.IDisposable {
private textArea:ITextAreaWrapper;
private editor:ITextEditor;
private configuration:EditorCommon.IConfiguration;
private selection:EditorCommon.IEditorRange;
private selections:EditorCommon.IEditorRange[];
private hasFocus:boolean;
private listenersToRemove:EventEmitter.ListenerUnbind[];
private _toDispose:Lifecycle.IDisposable[];
private asyncReadFromTextArea: Schedulers.RunOnceScheduler;
private asyncSetSelectionToTextArea: Schedulers.RunOnceScheduler;
private asyncTriggerCut: Schedulers.RunOnceScheduler;
// keypress, paste & composition end also trigger an input event
// the popover input method on macs triggers only an input event
// in this case the expectInputTime would be too much in the past
private justHadAPaste:boolean;
private justHadACut:boolean;
private lastKeyPressTime:number;
private lastCompositionEndTime:number;
private lastValueWrittenToTheTextArea:string;
private cursorPosition:EditorCommon.IEditorPosition;
private contentLeft:number;
private contentWidth:number;
private scrollLeft:number;
private previousSetTextAreaState:TextAreaState;
private textareaIsShownAtCursor: boolean;
private lastCopiedValue: string;
private lastCopiedValueIsFromEmptySelection: boolean;
constructor(textArea:ITextAreaWrapper, editor:ITextEditor, configuration:EditorCommon.IConfiguration) {
this.textArea = textArea;
this.editor = editor;
this.configuration = configuration;
this.selection = new Range(1, 1, 1, 1);
this.selections = [new Range(1, 1, 1, 1)];
this.cursorPosition = new Position(1, 1);
this.contentLeft = 0;
this.contentWidth = 0;
this.scrollLeft = 0;
this.asyncReadFromTextArea = new Schedulers.RunOnceScheduler(null, 0);
this.asyncSetSelectionToTextArea = new Schedulers.RunOnceScheduler(() => this._writePlaceholderAndSelectTextArea(), 0);
this.asyncTriggerCut = new Schedulers.RunOnceScheduler(() => this._triggerCut(), 0);
this.lastCopiedValue = null;
this.lastCopiedValueIsFromEmptySelection = false;
this.previousSetTextAreaState = null;
this.hasFocus = false;
this.justHadAPaste = false;
this.justHadACut = false;
this.lastKeyPressTime = 0;
this.lastCompositionEndTime = 0;
this.lastValueWrittenToTheTextArea = '';
this.listenersToRemove = [];
this._toDispose = [];
this._toDispose.push(this.textArea.onKeyDown((e) => this._onKeyDown(e)));
this._toDispose.push(this.textArea.onKeyUp((e) => this._onKeyUp(e)));
this._toDispose.push(this.textArea.onKeyPress((e) => this._onKeyPress(e)));
this.textareaIsShownAtCursor = false;
this._toDispose.push(this.textArea.onCompositionStart(() => {
var timeSinceLastCompositionEnd = (new Date().getTime()) - this.lastCompositionEndTime;
if (!this.textareaIsShownAtCursor) {
this.textareaIsShownAtCursor = true;
this.showTextAreaAtCursor(timeSinceLastCompositionEnd >= 100);
}
this.asyncReadFromTextArea.cancel();
}));
this._toDispose.push(this.textArea.onCompositionEnd(() => {
if (this.textareaIsShownAtCursor) {
this.textareaIsShownAtCursor = false;
this.hideTextArea();
}
this.lastCompositionEndTime = (new Date()).getTime();
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
}));
// on the iPad the text area is not fast enough to get the content of the keypress,
// so we leverage the input event instead
if (Browser.isIPad) {
this._toDispose.push(this.textArea.onInput(() => {
var myTime = (new Date()).getTime();
// A keypress will trigger an input event (very quickly)
var keyPressDeltaTime = myTime - this.lastKeyPressTime;
if (keyPressDeltaTime <= 500) {
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
this.lastKeyPressTime = 0;
}
}));
}
// on the mac the character viewer input generates an input event (no keypress)
// on windows, the Chinese IME, when set to insert wide punctuation generates an input event (no keypress)
this._toDispose.push(this.textArea.onInput(() => {
// Ignore input event if we are in composition mode
if (!this.textareaIsShownAtCursor) {
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
}
}));
if (Platform.isMacintosh) {
this._toDispose.push(this.textArea.onInput(() => {
// We are fishing for the input event that comes in the mac popover input method case
// A paste will trigger an input event, but the event might happen very late
if (this.justHadAPaste) {
this.justHadAPaste = false;
return;
}
// A cut will trigger an input event, but the event might happen very late
if (this.justHadACut) {
this.justHadACut = false;
return;
}
var myTime = (new Date()).getTime();
// A keypress will trigger an input event (very quickly)
var keyPressDeltaTime = myTime - this.lastKeyPressTime;
if (keyPressDeltaTime <= 500) {
return;
}
// A composition end will trigger an input event (very quickly)
var compositionEndDeltaTime = myTime - this.lastCompositionEndTime;
if (compositionEndDeltaTime <= 500) {
return;
}
// Ignore input if we are in the middle of a composition
if (this.textareaIsShownAtCursor) {
return;
}
// Ignore if the textarea has selection
if (this.textArea.selectionStart !== this.textArea.selectionEnd) {
return;
}
// In Chrome, only the first character gets replaced, while in Safari the entire line gets replaced
var typedText:string;
var textAreaValue = this.textArea.value;
if (!Browser.isChrome) {
// TODO: Also check this on Safari & FF before removing this
return;
}
if (this.lastValueWrittenToTheTextArea.length !== textAreaValue.length) {
return;
}
var prefixLength = Strings.commonPrefixLength(this.lastValueWrittenToTheTextArea, textAreaValue);
var suffixLength = Strings.commonSuffixLength(this.lastValueWrittenToTheTextArea, textAreaValue);
if (prefixLength + suffixLength + 1 !== textAreaValue.length) {
return;
}
typedText = textAreaValue.charAt(prefixLength);
this.executeReplacePreviousChar(typedText);
this.previousSetTextAreaState = TextAreaState.fromTextArea(this.textArea, 0);
this.asyncSetSelectionToTextArea.schedule();
}));
}
this._toDispose.push(this.textArea.onCut((e) => this._onCut(e)));
this._toDispose.push(this.textArea.onCopy((e) => this._onCopy(e)));
this._toDispose.push(this.textArea.onPaste((e) => this._onPaste(e)));
this._writePlaceholderAndSelectTextArea();
}
public dispose(): void {
this.listenersToRemove.forEach((element) => {
element();
});
this.listenersToRemove = [];
this._toDispose = Lifecycle.disposeAll(this._toDispose);
this.asyncReadFromTextArea.dispose();
this.asyncSetSelectionToTextArea.dispose();
this.asyncTriggerCut.dispose();
}
private showTextAreaAtCursor(emptyIt:boolean): void {
var interestingLineNumber:number,
interestingColumn1:number,
interestingColumn2:number;
// In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled.
if (Browser.isIE11orEarlier) {
// Ensure selection start is in viewport
interestingLineNumber = this.selection.startLineNumber;
interestingColumn1 = this.selection.startColumn;
interestingColumn2 = this.previousSetTextAreaState.getSelectionStart() + 1;
} else {
// Ensure primary cursor is in viewport
interestingLineNumber = this.cursorPosition.lineNumber;
interestingColumn1 = this.cursorPosition.column;
interestingColumn2 = interestingColumn1;
}
let visibleRanges = this.editor.visibleRangeForPositionRelativeToEditor(interestingLineNumber, interestingColumn1, interestingColumn2);
var visibleRange1 = visibleRanges.column1;
var visibleRange2 = visibleRanges.column2;
let style: ITextAreaStyle = {
top: undefined,
left: undefined,
width: undefined,
height: undefined
};
if (Browser.isIE11orEarlier) {
// Position textarea at the beginning of the line
if (visibleRange1 && visibleRange2) {
style.top = visibleRange1.top + 'px';
style.left = this.contentLeft + visibleRange1.left - visibleRange2.left - this.scrollLeft + 'px';
style.width = this.contentWidth + 'px';
}
} else {
// Position textarea at cursor location
if (visibleRange1) {
style.left = this.contentLeft + visibleRange1.left - this.scrollLeft + 'px';
style.top = visibleRange1.top + 'px';
}
// Empty the textarea
if (emptyIt) {
this.setTextAreaState(new TextAreaState('', 0, 0, 0), false);
}
}
// Show the textarea
style.height = this.configuration.editor.lineHeight + 'px';
this.textArea.setStyle(style);
this.editor.startIME();
}
private hideTextArea(): void {
this.textArea.setStyle({
height: '',
width: '',
left: '0px',
top: '0px'
});
this.editor.stopIME();
}
// --- begin event handlers
public onScrollChanged(e:EditorCommon.IScrollEvent): boolean {
this.scrollLeft = e.scrollLeft;
return false;
}
public onViewFocusChanged(isFocused:boolean): boolean {
this.hasFocus = isFocused;
if (this.hasFocus) {
this.asyncSetSelectionToTextArea.schedule();
}
return false;
}
public onCursorSelectionChanged(e:EditorCommon.IViewCursorSelectionChangedEvent): boolean {
this.selection = e.selection;
this.selections = [e.selection].concat(e.secondarySelections);
this.asyncSetSelectionToTextArea.schedule();
return false;
}
public onCursorPositionChanged(e:EditorCommon.IViewCursorPositionChangedEvent): boolean {
this.cursorPosition = e.position;
return false;
}
public onLayoutChanged(layoutInfo:EditorCommon.IEditorLayoutInfo): boolean {
this.contentLeft = layoutInfo.contentLeft;
this.contentWidth = layoutInfo.contentWidth;
return false;
}
// --- end event handlers
private setTextAreaState(textAreaState:TextAreaState, select:boolean): void {
// IE doesn't like calling select on a hidden textarea and the textarea is hidden during the tests
var shouldSetSelection = select && this.hasFocus;
if (!shouldSetSelection) {
textAreaState.resetSelection();
}
this.lastValueWrittenToTheTextArea = textAreaState.getValue();
textAreaState.applyToTextArea(this.textArea, shouldSetSelection);
this.previousSetTextAreaState = textAreaState;
}
private _onKeyDown(e:DomUtils.IKeyboardEvent): void {
if (e.equals(CommonKeybindings.ESCAPE)) {
// Prevent default always for `Esc`, otherwise it will generate a keypress
// See http://msdn.microsoft.com/en-us/library/ie/ms536939(v=vs.85).aspx
e.preventDefault();
}
this.editor.emitKeyDown(e);
// Work around for issue spotted in electron on the mac
// TODO@alex: check if this issue exists after updating electron
// Steps:
// * enter a line at an offset
// * go down to a line with [
// * go up, go left, go right
// => press ctrl+h => a keypress is generated even though the keydown is prevent defaulted
// Another case would be if focus goes outside the app on keydown (spotted under windows)
// Steps:
// * press Ctrl+K
// * press R
// => focus moves out while keydown is not finished
setTimeout(() => {
// cancel reading if previous keydown was canceled, but a keypress/input were still generated
if (e.browserEvent && e.browserEvent.defaultPrevented) {
// this._scheduleReadFromTextArea
this.asyncReadFromTextArea.cancel();
this.asyncSetSelectionToTextArea.schedule();
}
}, 0);
}
private _onKeyUp(e:DomUtils.IKeyboardEvent): void {
this.editor.emitKeyUp(e);
}
private _onKeyPress(e:DomUtils.IKeyboardEvent): void {
if (!this.hasFocus) {
// Sometimes, when doing Alt-Tab, in FF, a 'keypress' is sent before a 'focus'
return;
}
this.lastKeyPressTime = (new Date()).getTime();
// on the iPad the text area is not fast enough to get the content of the keypress,
// so we leverage the input event instead
if (!Browser.isIPad) {
this._scheduleReadFromTextArea(ReadFromTextArea.Type);
}
}
// ------------- Operations that are always executed asynchronously
private _scheduleReadFromTextArea(command:ReadFromTextArea): void {
this.asyncSetSelectionToTextArea.cancel();
this.asyncReadFromTextArea.setRunner(() => this._readFromTextArea(command));
this.asyncReadFromTextArea.schedule();
}
/**
* Read text from textArea and trigger `command` on the editor
*/
private _readFromTextArea(command:ReadFromTextArea): void {
var previousSelectionToken = this.previousSetTextAreaState ? this.previousSetTextAreaState.getSelectionToken() : 0;
var observedState = TextAreaState.fromTextArea(this.textArea, previousSelectionToken);
var txt = observedState.extractNewText(this.previousSetTextAreaState);
if (txt !== '') {
if (command === ReadFromTextArea.Type) {
// console.log("deduced input:", txt);
this.executeType(txt);
} else {
this.executePaste(txt);
}
}
this.previousSetTextAreaState = observedState;
this.asyncSetSelectionToTextArea.schedule();
}
private executePaste(txt:string): void {
if(txt === '') {
return;
}
var pasteOnNewLine = false;
if (Browser.enableEmptySelectionClipboard) {
pasteOnNewLine = (txt === this.lastCopiedValue && this.lastCopiedValueIsFromEmptySelection);
}
this.editor.paste('keyboard', txt, pasteOnNewLine);
}
private executeType(txt:string): void {
if(txt === '') {
return;
}
this.editor.type('keyboard', txt);
}
private executeReplacePreviousChar(txt: string): void {
this.editor.replacePreviousChar('keyboard', txt);
}
private _writePlaceholderAndSelectTextArea(): void {
if (!this.textareaIsShownAtCursor) {
// Do not write to the textarea if it is visible.
var previousSelectionToken = this.previousSetTextAreaState ? this.previousSetTextAreaState.getSelectionToken() : 0;
var newState = TextAreaState.fromEditorSelectionAndPreviousState(this.editor.getModel(), this.selection, previousSelectionToken);
this.setTextAreaState(newState, true);
}
}
// ------------- Clipboard operations
private _onPaste(e:ClipboardEvent): void {
if (e && (<any>e).clipboardData) {
e.preventDefault();
this.executePaste((<any>e).clipboardData.getData('text/plain'));
} else if (e && (<any>window).clipboardData) {
e.preventDefault();
this.executePaste((<any>window).clipboardData.getData('Text'));
} else {
if (this.textArea.selectionStart !== this.textArea.selectionEnd) {
// Clean up the textarea, to get a clean paste
this.setTextAreaState(new TextAreaState('', 0, 0, 0), false);
}
this._scheduleReadFromTextArea(ReadFromTextArea.Paste);
}
this.justHadAPaste = true;
}
private _onCopy(e:ClipboardEvent): void {
this._ensureClipboardGetsEditorSelection(e);
}
private _triggerCut(): void {
this.editor.cut('keyboard');
}
private _onCut(e:ClipboardEvent): void {
this._ensureClipboardGetsEditorSelection(e);
this.asyncTriggerCut.schedule();
this.justHadACut = true;
}
private _ensureClipboardGetsEditorSelection(e:ClipboardEvent): void {
var whatToCopy = this._getPlainTextToCopy();
if (e && (<any>e).clipboardData) {
(<any>e).clipboardData.setData('text/plain', whatToCopy);
// (<any>e).clipboardData.setData('text/html', this._getHTMLToCopy());
e.preventDefault();
} else if (e && (<any>window).clipboardData) {
(<any>window).clipboardData.setData('Text', whatToCopy);
e.preventDefault();
} else {
this.setTextAreaState(new TextAreaState(whatToCopy, 0, whatToCopy.length, 0), true);
}
if (Browser.enableEmptySelectionClipboard) {
if (Browser.isFirefox) {
// When writing "LINE\r\n" to the clipboard and then pasting,
// Firefox pastes "LINE\n", so let's work around this quirk
this.lastCopiedValue = whatToCopy.replace(/\r\n/g, '\n');
} else {
this.lastCopiedValue = whatToCopy;
}
var selections = this.selections;
this.lastCopiedValueIsFromEmptySelection = (selections.length === 1 && selections[0].isEmpty());
}
}
private _getPlainTextToCopy(): string {
var newLineCharacter = (Platform.isWindows ? '\r\n' : '\n');
var eolPref = (Platform.isWindows ? EditorCommon.EndOfLinePreference.CRLF : EditorCommon.EndOfLinePreference.LF);
var selections = this.selections;
let model = this.editor.getModel();
if (selections.length === 1) {
var range:EditorCommon.IEditorRange = selections[0];
if (range.isEmpty()) {
if (Browser.enableEmptySelectionClipboard) {
var modelLineNumber = model.convertViewPositionToModelPosition(range.startLineNumber, 1).lineNumber;
return model.getModelLineContent(modelLineNumber) + newLineCharacter;
} else {
return '';
}
}
return model.getValueInRange(range, eolPref);
} else {
selections = selections.slice(0).sort(Range.compareRangesUsingStarts);
var result: string[] = [];
for (var i = 0; i < selections.length; i++) {
result.push(model.getValueInRange(selections[i], eolPref));
}
return result.join(newLineCharacter);
}
}
}

View file

@ -44,7 +44,7 @@ export interface ICodeEditorHelper {
export interface IKeyboardHandlerHelper {
viewDomNode:HTMLElement;
textArea:HTMLTextAreaElement;
visibleRangeForPositionRelativeToEditor(lineNumber:number, column:number): IVisibleRange;
visibleRangeForPositionRelativeToEditor(lineNumber:number, column:number): VisibleRange;
}
export interface IPointerHandlerHelper {
@ -70,7 +70,7 @@ export interface IPointerHandlerHelper {
*/
getPositionFromDOMInfo(spanNode:HTMLElement, offset:number): EditorCommon.IPosition;
visibleRangeForPosition2(lineNumber:number, column:number): IVisibleRange;
visibleRangeForPosition2(lineNumber:number, column:number): VisibleRange;
getLineWidth(lineNumber:number): number;
}
@ -159,11 +159,17 @@ export var ClassNames = {
VIEW_ZONES: 'view-zones'
};
export interface IVisibleRange {
top:number;
left:number;
width:number;
height:number;
export class VisibleRange {
public top:number;
public left:number;
public width:number;
constructor(top:number, left:number, width:number) {
this.top = top;
this.left = left;
this.width = width;
}
}
export interface IRestrictedRenderingContext {
@ -187,26 +193,33 @@ export interface IRestrictedRenderingContext {
getDecorationsInViewport(): EditorCommon.IModelDecoration[];
}
export interface IHorizontalRange {
left:number;
width:number;
export class HorizontalRange {
public left: number;
public width: number;
constructor(left:number, width:number) {
this.left = left;
this.width = width;
}
}
export interface ILineVisibleRanges {
lineNumber: number;
ranges: IHorizontalRange[];
export class LineVisibleRanges {
public lineNumber: number;
public ranges: HorizontalRange[];
constructor(lineNumber:number, ranges:HorizontalRange[]) {
this.lineNumber = lineNumber;
this.ranges = ranges;
}
}
export interface IRenderingContext extends IRestrictedRenderingContext {
heightInPxForLine(lineNumber:number): number;
linesVisibleRangesForRange(range:EditorCommon.IRange, includeNewLines:boolean): LineVisibleRanges[];
visibleRangesForRange(range:EditorCommon.IRange, includeNewLines:boolean): IVisibleRange[];
linesVisibleRangesForRange(range:EditorCommon.IRange, includeNewLines:boolean): ILineVisibleRanges[];
visibleRangeForPosition(position:EditorCommon.IPosition): IVisibleRange;
visibleRangeForPosition2(lineNumber:number, column:number): IVisibleRange;
visibleRangeForPosition(position:EditorCommon.IPosition): VisibleRange;
}
export interface IViewEventHandler {

View file

@ -6,10 +6,10 @@
import {TPromise} from 'vs/base/common/winjs.base';
import Schedulers = require('vs/base/common/async');
import ViewLine = require('vs/editor/browser/viewParts/lines/viewLine');
import EditorCommon = require('vs/editor/common/editorCommon');
import Modes = require('vs/editor/common/modes');
import {IModeService} from 'vs/editor/common/services/modeService';
import {IRenderLineOutput, renderLine} from 'vs/editor/common/viewLayout/viewLineRenderer';
export interface IColorizerOptions {
tabSize?: number;
@ -90,7 +90,7 @@ export function colorize(modeService:IModeService, text:string, mimeType:string,
}
export function colorizeLine(line:string, tokens:EditorCommon.ILineToken[], tabSize:number = 4): string {
var renderResult = ViewLine.renderLine({
var renderResult = renderLine({
lineContent: line,
parts: tokens,
stopRenderingLineAfter: -1,
@ -120,7 +120,7 @@ function actualColorize(lines:string[], mode:Modes.IMode, tabSize:number): IActu
length:number,
line: string,
tokenizeResult: Modes.ILineTokens,
renderResult: ViewLine.IRenderLineOutput,
renderResult: IRenderLineOutput,
retokenize: TPromise<void>[] = [];
for (i = 0, length = lines.length; i < length; i++) {
@ -131,7 +131,7 @@ function actualColorize(lines:string[], mode:Modes.IMode, tabSize:number): IActu
retokenize.push(tokenizeResult.retokenize);
}
renderResult = ViewLine.renderLine({
renderResult = renderLine({
lineContent: line,
parts: tokenizeResult.tokens,
stopRenderingLineAfter: -1,

View file

@ -366,12 +366,7 @@ export class View extends ViewEventHandler implements EditorBrowser.IView, Lifec
throw new Error('ViewImpl.pointerHandler.visibleRangeForPosition2: View is disposed');
}
this._flushAccumulatedAndRenderNow();
var correctionTop = 0;
// FF is very weird
// if (Env.browser.canUseTranslate3d && Env.browser.isFirefox) {
// correctionTop = this.layoutProvider.getCurrentViewport().top;
// }
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(lineNumber, column, lineNumber, column), 0, correctionTop, false);
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(lineNumber, column, lineNumber, column), 0);
if (!visibleRanges) {
return null;
}
@ -399,11 +394,7 @@ export class View extends ViewEventHandler implements EditorBrowser.IView, Lifec
this._flushAccumulatedAndRenderNow();
var linesViewPortData = this.layoutProvider.getLinesViewportData();
var correctionTop = 0;
// FF is very weird
// if (Env.browser.canUseTranslate3d && Env.browser.isFirefox) {
// correctionTop = this.layoutProvider.getCurrentViewport().top;
// }
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(lineNumber, column, lineNumber, column), linesViewPortData.visibleRangesDeltaTop, correctionTop, false);
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(lineNumber, column, lineNumber, column), linesViewPortData.visibleRangesDeltaTop);
if (!visibleRanges) {
return null;
}
@ -577,7 +568,7 @@ export class View extends ViewEventHandler implements EditorBrowser.IView, Lifec
});
var viewPosition = this.context.model.convertModelPositionToViewPosition(modelPosition.lineNumber, modelPosition.column);
this._flushAccumulatedAndRenderNow();
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column), 0, 0, false);
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column), 0);
if (!visibleRanges) {
return -1;
}
@ -824,11 +815,6 @@ export class View extends ViewEventHandler implements EditorBrowser.IView, Lifec
var vInfo = this.layoutProvider.getCurrentViewport();
var deltaTop = linesViewportData.visibleRangesDeltaTop;
var correctionTop = 0;
// FF is very weird
// if (Env.browser.canUseTranslate3d && Env.browser.isFirefox) {
// correctionTop = vInfo.top;
// }
var r:EditorBrowser.IRenderingContext = {
linesViewportData: linesViewportData,
@ -853,30 +839,14 @@ export class View extends ViewEventHandler implements EditorBrowser.IView, Lifec
return scrolledTop;
},
heightInPxForLine: (lineNumber:number) => {
return this.layoutProvider.heightInPxForLine(lineNumber);
},
getDecorationsInViewport: () => linesViewportData.getDecorationsInViewport(),
visibleRangesForRange: (range:EditorCommon.IRange, includeNewLines:boolean) => {
return this.viewLines.visibleRangesForRange2(range, deltaTop, correctionTop, includeNewLines);
},
linesVisibleRangesForRange: (range:EditorCommon.IRange, includeNewLines:boolean) => {
return this.viewLines.linesVisibleRangesForRange(range, includeNewLines);
},
visibleRangeForPosition: (position:EditorCommon.IPosition) => {
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(position.lineNumber, position.column, position.lineNumber, position.column), deltaTop, correctionTop, false);
if (!visibleRanges) {
return null;
}
return visibleRanges[0];
},
visibleRangeForPosition2: (lineNumber:number, column:number) => {
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(lineNumber, column, lineNumber, column), deltaTop, correctionTop, false);
var visibleRanges = this.viewLines.visibleRangesForRange2(new Range(position.lineNumber, position.column, position.lineNumber, position.column), deltaTop);
if (!visibleRanges) {
return null;
}

View file

@ -72,7 +72,7 @@ export class ScrollManager implements Lifecycle.IDisposable {
this.toDispose.push(this.scrollable.addInternalSizeChangeListener(() => {
this.scrollbar.onElementInternalDimensions();
}));
this.toDispose.push(this.configuration.addListener2(EditorCommon.EventType.ConfigurationChanged, (e:EditorCommon.IConfigurationChangedEvent) => {
this.toDispose.push(this.configuration.onDidChange((e:EditorCommon.IConfigurationChangedEvent) => {
this.scrollbar.updateClassName(this.configuration.editor.theme);
if (e.scrollbar) {
this.scrollbar.updateOptions(this.configuration.editor.scrollbar);

View file

@ -198,7 +198,7 @@ export class ViewContentWidgets extends ViewPart {
let heightAboveLine = aboveLineTop;
// b) the box under the line
let underLineTop = visibleRange.top + visibleRange.height;
let underLineTop = visibleRange.top + this._context.configuration.editor.lineHeight;
let heightUnderLine = ctx.viewportHeight - underLineTop;
let aboveTop = aboveLineTop - height;
@ -241,7 +241,7 @@ export class ViewContentWidgets extends ViewPart {
}
let aboveTop = visibleRange.top - height,
belowTop = visibleRange.top + visibleRange.height,
belowTop = visibleRange.top + this._context.configuration.editor.lineHeight,
left = left0 + this._contentLeft;
let domNodePosition = DomUtils.getDomNodePosition(this._viewDomNode);

View file

@ -91,9 +91,9 @@ export class DecorationsOverlay extends ViewEventHandler implements EditorBrowse
decorations = ctx.getDecorationsInViewport(),
d:EditorCommon.IModelDecoration,
rng:EditorCommon.IRange,
linesVisibleRanges:EditorBrowser.ILineVisibleRanges[],
lineVisibleRanges:EditorBrowser.ILineVisibleRanges,
visibleRange:EditorBrowser.IHorizontalRange,
linesVisibleRanges:EditorBrowser.LineVisibleRanges[],
lineVisibleRanges:EditorBrowser.LineVisibleRanges,
visibleRange:EditorBrowser.HorizontalRange,
lineHeight = this._context.configuration.editor.lineHeight.toString(),
i:number, lenI:number,
j:number, lenJ:number,

View file

@ -4,59 +4,17 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Browser = require('vs/base/browser/browser');
import DomUtils = require('vs/base/browser/dom');
import * as Browser from 'vs/base/browser/browser';
import {StyleMutator} from 'vs/base/browser/dom';
import {IVisibleLineData} from 'vs/editor/browser/view/viewLayer';
import {ILineParts, createLineParts} from 'vs/editor/common/viewLayout/viewLineParts';
import EditorBrowser = require('vs/editor/browser/editorBrowser');
import EditorCommon = require('vs/editor/common/editorCommon');
import {ClassNames, IViewContext, HorizontalRange} from 'vs/editor/browser/editorBrowser';
import {IModelDecoration, IConfigurationChangedEvent} from 'vs/editor/common/editorCommon';
import {renderLine} from 'vs/editor/common/viewLayout/viewLineRenderer';
export interface IViewLineData extends IVisibleLineData {
/**
* Height of the line in pixels
*/
getHeight(): number;
export class ViewLine implements IVisibleLineData {
/**
* Width of the line in pixels
*/
getWidth(): number;
/**
* Visible ranges for a model range
*/
getVisibleRangesForRange(lineNumber:number, startColumn:number, endColumn:number, deltaTop:number, correctionTop:number, deltaLeft:number, endNode:HTMLElement): EditorBrowser.IVisibleRange[];
/**
* Returns the column for the text found at a specific offset inside a rendered dom node
*/
getColumnOfNodeOffset(lineNumber:number, spanNode:HTMLElement, offset:number): number;
/**
* Let the line know that decorations might have changed
*/
onModelDecorationsChanged(): void;
}
class VisibleRange implements EditorBrowser.IVisibleRange {
public top:number;
public left:number;
public width:number;
public height:number;
constructor(top:number, left:number, width:number, height:number) {
this.top = top;
this.left = left;
this.width = width;
this.height = height;
}
}
class ViewLine implements IViewLineData {
protected _context:EditorBrowser.IViewContext;
protected _context:IViewContext;
private _domNode: HTMLElement;
private _lineParts: ILineParts;
@ -64,33 +22,29 @@ class ViewLine implements IViewLineData {
private _isInvalid: boolean;
private _isMaybeInvalid: boolean;
_charOffsetInPart:number[];
private _hasOverflowed:boolean;
protected _charOffsetInPart:number[];
private _lastRenderedPartIndex:number;
private _cachedWidth: number;
constructor(context:EditorBrowser.IViewContext) {
constructor(context:IViewContext) {
this._context = context;
this._domNode = null;
this._isInvalid = true;
this._isMaybeInvalid = false;
this._lineParts = null;
this._charOffsetInPart = [];
this._hasOverflowed = false;
this._lastRenderedPartIndex = 0;
}
// --- begin IVisibleLineData
public getDomNode(): HTMLElement {
return this._domNode;
}
public setDomNode(domNode:HTMLElement): void {
this._domNode = domNode;
}
// ---- event handlers
public onContentChanged(): void {
this._isInvalid = true;
}
@ -109,17 +63,22 @@ class ViewLine implements IViewLineData {
public onModelDecorationsChanged(): void {
this._isMaybeInvalid = true;
}
public onConfigurationChanged(e:EditorCommon.IConfigurationChangedEvent): void {
public onConfigurationChanged(e:IConfigurationChangedEvent): void {
this._isInvalid = true;
}
// ---- end event handlers
public shouldUpdateHTML(lineNumber:number, inlineDecorations:EditorCommon.IModelDecoration[]): boolean {
var newLineParts:ILineParts = null;
public shouldUpdateHTML(lineNumber:number, inlineDecorations:IModelDecoration[]): boolean {
let newLineParts:ILineParts = null;
if (this._isMaybeInvalid || this._isInvalid) {
// Compute new line parts only if there is some evidence that something might have changed
newLineParts = this._computeLineParts(lineNumber, inlineDecorations);
newLineParts = createLineParts(
lineNumber,
this._context.model.getLineContent(lineNumber),
this._context.model.getLineTokens(lineNumber),
inlineDecorations,
this._context.configuration.editor.renderWhitespace
);
}
// Decide if isMaybeInvalid flips isInvalid to true
@ -147,7 +106,7 @@ class ViewLine implements IViewLineData {
out.push('px;height:');
out.push(this._context.configuration.editor.lineHeight.toString());
out.push('px;" class="');
out.push(EditorBrowser.ClassNames.VIEW_LINE);
out.push(ClassNames.VIEW_LINE);
out.push('">');
out.push(this.getLineInnerHTML(lineNumber));
out.push('</div>');
@ -155,27 +114,26 @@ class ViewLine implements IViewLineData {
public getLineInnerHTML(lineNumber: number): string {
this._isInvalid = false;
return this._renderMyLine(lineNumber, this._lineParts).join('');
return this._render(lineNumber, this._lineParts).join('');
}
public layoutLine(lineNumber:number, deltaTop:number): void {
var currentLineNumber = this._domNode.getAttribute('lineNumber');
if (currentLineNumber !== lineNumber.toString()) {
this._domNode.setAttribute('lineNumber', lineNumber.toString());
let desiredLineNumber = String(lineNumber);
let currentLineNumber = this._domNode.getAttribute('lineNumber');
if (currentLineNumber !== desiredLineNumber) {
this._domNode.setAttribute('lineNumber', desiredLineNumber);
}
DomUtils.StyleMutator.setTop(this._domNode, deltaTop);
DomUtils.StyleMutator.setHeight(this._domNode, this._context.configuration.editor.lineHeight);
StyleMutator.setTop(this._domNode, deltaTop);
StyleMutator.setHeight(this._domNode, this._context.configuration.editor.lineHeight);
}
private _computeLineParts(lineNumber:number, inlineDecorations:EditorCommon.IModelDecoration[]): ILineParts {
return createLineParts(lineNumber, this._context.model.getLineContent(lineNumber), this._context.model.getLineTokens(lineNumber), inlineDecorations, this._context.configuration.editor.renderWhitespace);
}
// --- end IVisibleLineData
private _renderMyLine(lineNumber:number, lineParts:ILineParts): string[] {
private _render(lineNumber:number, lineParts:ILineParts): string[] {
this._bustReadingCache();
this._cachedWidth = -1;
var r = renderLine({
let r = renderLine({
lineContent: this._context.model.getLineContent(lineNumber),
tabSize: this._context.configuration.getIndentationOptions().tabSize,
stopRenderingLineAfter: this._context.configuration.editor.stopRenderingLineAfter,
@ -184,7 +142,6 @@ class ViewLine implements IViewLineData {
});
this._charOffsetInPart = r.charOffsetInPart;
this._hasOverflowed = r.hasOverflowed;
this._lastRenderedPartIndex = r.lastRenderedPartIndex;
return r.output;
@ -196,17 +153,6 @@ class ViewLine implements IViewLineData {
return <HTMLSpanElement>this._domNode.firstChild;
}
private _bustReadingCache(): void {
this._cachedWidth = -1;
}
/**
* Height of the line in pixels
*/
public getHeight(): number {
return this._domNode.offsetHeight;
}
/**
* Width of the line in pixels
*/
@ -220,8 +166,8 @@ class ViewLine implements IViewLineData {
/**
* Visible ranges for a model range
*/
public getVisibleRangesForRange(lineNumber:number, startColumn:number, endColumn:number, deltaTop:number, correctionTop:number, deltaLeft:number, endNode:HTMLElement): EditorBrowser.IVisibleRange[] {
var stopRenderingLineAfter = this._context.configuration.editor.stopRenderingLineAfter;
public getVisibleRangesForRange(startColumn:number, endColumn:number, endNode:HTMLElement): HorizontalRange[] {
let stopRenderingLineAfter = this._context.configuration.editor.stopRenderingLineAfter;
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) {
// This range is obviously not visible
@ -236,16 +182,16 @@ class ViewLine implements IViewLineData {
endColumn = stopRenderingLineAfter;
}
return this._readVisibleRangesForRange(lineNumber, startColumn, endColumn, deltaTop, correctionTop, deltaLeft, endNode);
return this._readVisibleRangesForRange(startColumn, endColumn, endNode);
}
_readVisibleRangesForRange(lineNumber:number, startColumn:number, endColumn:number, deltaTop:number, correctionTop:number, deltaLeft:number, endNode:HTMLElement): EditorBrowser.IVisibleRange[] {
protected _readVisibleRangesForRange(startColumn:number, endColumn:number, endNode:HTMLElement): HorizontalRange[] {
var result:EditorBrowser.IVisibleRange[];
let result: HorizontalRange[];
if (startColumn === endColumn) {
result = this._readRawVisibleRangesForPosition(lineNumber, startColumn, deltaTop, correctionTop, deltaLeft, endNode);
result = this._readRawVisibleRangesForPosition(startColumn, endNode);
} else {
result = this._readRawVisibleRangesForRange(lineNumber, startColumn, endColumn, deltaTop, correctionTop, deltaLeft, endNode);
result = this._readRawVisibleRangesForRange(startColumn, endColumn, endNode);
}
if (!result || result.length <= 1) {
@ -254,12 +200,11 @@ class ViewLine implements IViewLineData {
result.sort(compareVisibleRanges);
var output: EditorBrowser.IVisibleRange[] = [],
prevRange: EditorBrowser.IVisibleRange = result[0],
currRange: EditorBrowser.IVisibleRange;
let output: HorizontalRange[] = [];
let prevRange: HorizontalRange = result[0];
for (var i = 1, len = result.length; i < len; i++) {
currRange = result[i];
for (let i = 1, len = result.length; i < len; i++) {
let currRange = result[i];
if (prevRange.left + prevRange.width + 0.3 /* account for browser's rounding errors*/ >= currRange.left) {
prevRange.width = Math.max(prevRange.width, currRange.left + currRange.width - prevRange.left);
@ -273,55 +218,46 @@ class ViewLine implements IViewLineData {
return output;
}
_readRawVisibleRangesForPosition(lineNumber:number, column:number, deltaTop:number, correctionTop:number, deltaLeft:number, endNode:HTMLElement): EditorBrowser.IVisibleRange[] {
protected _readRawVisibleRangesForPosition(column:number, endNode:HTMLElement): HorizontalRange[] {
if (this._charOffsetInPart.length === 0) {
// This line is empty
var result = this._readRawVisibleRangesForEntireLine(deltaTop, correctionTop, deltaLeft);
// Force width to be 0 (since line is empty)
result[0].width = 0;
return result;
return [new HorizontalRange(0, 0)];
}
var partIndex = findIndexInArrayWithMax(this._lineParts, column - 1, this._lastRenderedPartIndex),
_charOffsetInPart = this._charOffsetInPart[column - 1];
let partIndex = findIndexInArrayWithMax(this._lineParts, column - 1, this._lastRenderedPartIndex);
let charOffsetInPart = this._charOffsetInPart[column - 1];
return this._readRawVisibleRangesFrom(this._getReadingTarget(), partIndex, _charOffsetInPart, partIndex, _charOffsetInPart, deltaTop, correctionTop, deltaLeft, endNode);
return this._readRawVisibleRangesFrom(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, endNode);
}
private _readRawVisibleRangesForRange(lineNumber:number, startColumn:number, endColumn:number, deltaTop:number, correctionTop:number, deltaLeft:number, endNode:HTMLElement): EditorBrowser.IVisibleRange[] {
private _readRawVisibleRangesForRange(startColumn:number, endColumn:number, endNode:HTMLElement): HorizontalRange[] {
if (startColumn === 1 && endColumn === this._charOffsetInPart.length) {
// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line
return this._readRawVisibleRangesForEntireLine(deltaTop, correctionTop, deltaLeft);
return [this._readRawVisibleRangeForEntireLine()];
}
var startPartIndex = findIndexInArrayWithMax(this._lineParts, startColumn - 1, this._lastRenderedPartIndex),
start_charOffsetInPart = this._charOffsetInPart[startColumn - 1],
endPartIndex = findIndexInArrayWithMax(this._lineParts, endColumn - 1, this._lastRenderedPartIndex),
end_charOffsetInPart = this._charOffsetInPart[endColumn - 1];
let startPartIndex = findIndexInArrayWithMax(this._lineParts, startColumn - 1, this._lastRenderedPartIndex);
let startCharOffsetInPart = this._charOffsetInPart[startColumn - 1];
let endPartIndex = findIndexInArrayWithMax(this._lineParts, endColumn - 1, this._lastRenderedPartIndex);
let endCharOffsetInPart = this._charOffsetInPart[endColumn - 1];
return this._readRawVisibleRangesFrom(this._getReadingTarget(), startPartIndex, start_charOffsetInPart, endPartIndex, end_charOffsetInPart, deltaTop, correctionTop, deltaLeft, endNode);
return this._readRawVisibleRangesFrom(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, endNode);
}
private _readRawVisibleRangesForEntireLine(deltaTop:number, correctionTop:number, deltaLeft:number): EditorBrowser.IVisibleRange[] {
// this.getReadingTarget().getBoundingClientRect() reports a slightly wrong top & height bounding box in IE.
// That is why we're using the div's bounding client rect for top & height.
var divBoundingClientRect = this._domNode.getBoundingClientRect();
// Do not apply `correctionTop` here because FF is not weird in this case (just subtracting `deltaTop`)
return [new VisibleRange(divBoundingClientRect.top - deltaTop, 0, this._getReadingTarget().offsetWidth, divBoundingClientRect.height)];
private _readRawVisibleRangeForEntireLine(): HorizontalRange {
return new HorizontalRange(0, this._getReadingTarget().offsetWidth);
}
private _readRawVisibleRangesFrom(domNode:HTMLElement, startChildIndex:number, startOffset:number, endChildIndex:number, endOffset:number, deltaTop:number, correctionTop:number, deltaLeft:number, endNode:HTMLElement): EditorBrowser.IVisibleRange[] {
var range = RangeUtil.createRange();
private _readRawVisibleRangesFrom(domNode:HTMLElement, startChildIndex:number, startOffset:number, endChildIndex:number, endOffset:number, endNode:HTMLElement): HorizontalRange[] {
let range = RangeUtil.createRange();
try {
// Panic check
var min = 0, max = domNode.children.length - 1;
let min = 0;
let max = domNode.children.length - 1;
if (min > max) {
return null;
}
@ -337,8 +273,8 @@ class ViewLine implements IViewLineData {
}
}
var startElement = domNode.children[startChildIndex].firstChild,
endElement = domNode.children[endChildIndex].firstChild;
let startElement = domNode.children[startChildIndex].firstChild;
let endElement = domNode.children[endChildIndex].firstChild;
if (!startElement || !endElement) {
return null;
@ -350,14 +286,12 @@ class ViewLine implements IViewLineData {
range.setStart(startElement, startOffset);
range.setEnd(endElement, endOffset);
var clientRects = range.getClientRects(),
result:EditorBrowser.IVisibleRange[] = null;
if (clientRects.length > 0) {
result = this._createRawVisibleRangesFromClientRects(clientRects, deltaTop, correctionTop, deltaLeft);
let clientRects = range.getClientRects();
if (clientRects.length === 0) {
return null;
}
return result;
return this._createRawVisibleRangesFromClientRects(clientRects);
} catch (e) {
// This is life ...
@ -367,27 +301,25 @@ class ViewLine implements IViewLineData {
}
}
_createRawVisibleRangesFromClientRects(clientRects:ClientRectList, deltaTop:number, correctionTop:number, deltaLeft:number): EditorBrowser.IVisibleRange[] {
var clientRectsLength = clientRects.length,
cR:ClientRect,
i:number,
result:EditorBrowser.IVisibleRange[] = [];
for (i = 0; i < clientRectsLength; i++) {
cR = clientRects[i];
result.push(new VisibleRange(cR.top - deltaTop - correctionTop, Math.max(0, cR.left - deltaLeft), cR.width, cR.height));
protected _createRawVisibleRangesFromClientRects(clientRects:ClientRectList): HorizontalRange[] {
let result:HorizontalRange[] = [];
for (let i = 0, len = clientRects.length; i < len; i++) {
let cR = clientRects[i];
result.push(new HorizontalRange(cR.left, cR.width));
}
return result;
}
/**
* Returns the column for the text found at a specific offset inside a rendered dom node
*/
public getColumnOfNodeOffset(lineNumber:number, spanNode:HTMLElement, offset:number): number {
var spanIndex = -1;
let spanIndex = -1;
while (spanNode) {
spanNode = <HTMLElement>spanNode.previousSibling;
spanIndex++;
}
var lineParts = this._lineParts.getParts();
let lineParts = this._lineParts.getParts();
if (spanIndex >= lineParts.length) {
return this._context.configuration.editor.stopRenderingLineAfter;
@ -397,7 +329,9 @@ class ViewLine implements IViewLineData {
return lineParts[spanIndex].startIndex + 1;
}
var originalMin = lineParts[spanIndex].startIndex, originalMax:number, originalMaxStartOffset:number;
let originalMin = lineParts[spanIndex].startIndex;
let originalMax:number;
let originalMaxStartOffset:number;
if (spanIndex + 1 < lineParts.length) {
// Stop searching characters at the beginning of the next part
@ -408,16 +342,15 @@ class ViewLine implements IViewLineData {
originalMaxStartOffset = this._charOffsetInPart[originalMax];
}
var min = originalMin,
mid:number,
max = originalMax;
let min = originalMin;
let max = originalMax;
if (this._context.configuration.editor.stopRenderingLineAfter !== -1) {
max = Math.min(this._context.configuration.editor.stopRenderingLineAfter - 1, originalMax);
}
var midStartOffset:number, nextStartOffset:number, prevStartOffset:number, a:number, b:number;
let nextStartOffset:number;
let prevStartOffset:number;
// Here are the variables and their relation plotted on an axis
@ -427,9 +360,8 @@ class ViewLine implements IViewLineData {
// Everything in (a;b] will match mid
while (min < max) {
mid = Math.floor( (min + max) / 2 );
midStartOffset = this._charOffsetInPart[mid];
let mid = Math.floor( (min + max) / 2 );
let midStartOffset = this._charOffsetInPart[mid];
if (mid === originalMax) {
// Using Number.MAX_VALUE to ensure that any offset after midStartOffset will match mid
@ -448,8 +380,8 @@ class ViewLine implements IViewLineData {
prevStartOffset = this._charOffsetInPart[mid - 1];
}
a = (prevStartOffset + midStartOffset) / 2;
b = (midStartOffset + nextStartOffset) / 2;
let a = (prevStartOffset + midStartOffset) / 2;
let b = (midStartOffset + nextStartOffset) / 2;
if (a < offset && offset <= b) {
// Hit!
@ -469,22 +401,16 @@ class ViewLine implements IViewLineData {
class IEViewLine extends ViewLine {
constructor(context:EditorBrowser.IViewContext) {
constructor(context:IViewContext) {
super(context);
}
_createRawVisibleRangesFromClientRects(clientRects:ClientRectList, deltaTop:number, correctionTop:number, deltaLeft:number): EditorBrowser.IVisibleRange[] {
var clientRectsLength = clientRects.length,
cR:ClientRect,
i:number,
result:EditorBrowser.IVisibleRange[] = [],
ratioY = screen.logicalYDPI / screen.deviceYDPI,
ratioX = screen.logicalXDPI / screen.deviceXDPI;
result = new Array<EditorBrowser.IVisibleRange>(clientRectsLength);
for (i = 0; i < clientRectsLength; i++) {
cR = clientRects[i];
result[i] = new VisibleRange(cR.top * ratioY - deltaTop - correctionTop, Math.max(0, cR.left * ratioX - deltaLeft), cR.width * ratioX, cR.height * ratioY);
protected _createRawVisibleRangesFromClientRects(clientRects:ClientRectList): HorizontalRange[] {
let ratioX = screen.logicalXDPI / screen.deviceXDPI;
let result:HorizontalRange[] = [];
for (let i = 0, len = clientRects.length; i < len; i++) {
let cR = clientRects[i];
result[i] = new HorizontalRange(cR.left * ratioX, cR.width * ratioX);
}
return result;
@ -493,17 +419,17 @@ class IEViewLine extends ViewLine {
class WebKitViewLine extends ViewLine {
constructor(context:EditorBrowser.IViewContext) {
constructor(context:IViewContext) {
super(context);
}
public _readVisibleRangesForRange(lineNumber:number, startColumn:number, endColumn:number, deltaTop:number, correctionTop:number, deltaLeft:number, endNode:HTMLElement): EditorBrowser.IVisibleRange[] {
var output = super._readVisibleRangesForRange(lineNumber, startColumn, endColumn, deltaTop, correctionTop, deltaLeft, endNode);
protected _readVisibleRangesForRange(startColumn:number, endColumn:number, endNode:HTMLElement): HorizontalRange[] {
let output = super._readVisibleRangesForRange(startColumn, endColumn, endNode);
if (this._context.configuration.editor.fontLigatures && endColumn > 1 && startColumn === endColumn && endColumn === this._charOffsetInPart.length) {
if (output.length === 1) {
let lastSpanBoundingClientRect = (<HTMLElement>this._getReadingTarget().lastChild).getBoundingClientRect();
output[0].left = lastSpanBoundingClientRect.right - deltaLeft;
output[0].left = lastSpanBoundingClientRect.right;
}
}
@ -516,15 +442,15 @@ class WebKitViewLine extends ViewLine {
// This is an attempt to patch things up
// Find position of previous column
var beforeEndVisibleRanges = this._readRawVisibleRangesForPosition(lineNumber, endColumn - 1, deltaTop, correctionTop, deltaLeft, endNode);
let beforeEndVisibleRanges = this._readRawVisibleRangesForPosition(endColumn - 1, endNode);
// Find position of last column
var endVisibleRanges = this._readRawVisibleRangesForPosition(lineNumber, endColumn, deltaTop, correctionTop, deltaLeft, endNode);
let endVisibleRanges = this._readRawVisibleRangesForPosition(endColumn, endNode);
if (beforeEndVisibleRanges && beforeEndVisibleRanges.length > 0 && endVisibleRanges && endVisibleRanges.length > 0) {
var beforeEndVisibleRange = beforeEndVisibleRanges[0];
var endVisibleRange = endVisibleRanges[0];
var isLTR = (beforeEndVisibleRange.left <= endVisibleRange.left);
var lastRange = output[output.length - 1];
let beforeEndVisibleRange = beforeEndVisibleRanges[0];
let endVisibleRange = endVisibleRanges[0];
let isLTR = (beforeEndVisibleRange.left <= endVisibleRange.left);
let lastRange = output[output.length - 1];
if (isLTR && lastRange.left < endVisibleRange.left) {
// Trim down the width of the last visible range to not go after the last column's position
@ -559,16 +485,16 @@ class RangeUtil {
}
}
function compareVisibleRanges(a: EditorBrowser.IVisibleRange, b: EditorBrowser.IVisibleRange): number {
function compareVisibleRanges(a: HorizontalRange, b: HorizontalRange): number {
return a.left - b.left;
}
function findIndexInArrayWithMax(lineParts:ILineParts, desiredIndex: number, maxResult:number): number {
var r = lineParts.findIndexOfOffset(desiredIndex);
let r = lineParts.findIndexOfOffset(desiredIndex);
return r <= maxResult ? r : maxResult;
}
export var createLine: (context: EditorBrowser.IViewContext) => IViewLineData = (function() {
export let createLine: (context: IViewContext) => ViewLine = (function() {
if (window.screen && window.screen.deviceXDPI && (navigator.userAgent.indexOf('Trident/6.0') >= 0 || navigator.userAgent.indexOf('Trident/5.0') >= 0)) {
// IE11 doesn't need the screen.logicalXDPI / screen.deviceXDPI ratio multiplication
// for TextRange.getClientRects() anymore
@ -579,181 +505,15 @@ export var createLine: (context: EditorBrowser.IViewContext) => IViewLineData =
return createNormalLine;
})();
function createIELine(context: EditorBrowser.IViewContext): IViewLineData {
function createIELine(context: IViewContext): ViewLine {
return new IEViewLine(context);
}
function createWebKitLine(context: EditorBrowser.IViewContext): IViewLineData {
function createWebKitLine(context: IViewContext): ViewLine {
return new WebKitViewLine(context);
}
function createNormalLine(context: EditorBrowser.IViewContext): IViewLineData {
function createNormalLine(context: IViewContext): ViewLine {
return new ViewLine(context);
}
export interface IRenderLineInput {
lineContent: string;
tabSize: number;
stopRenderingLineAfter: number;
renderWhitespace: boolean;
parts: EditorCommon.ILineToken[];
}
export interface IRenderLineOutput {
charOffsetInPart: number[];
hasOverflowed: boolean;
lastRenderedPartIndex: number;
partsCount: number;
output: string[];
}
var _space = ' '.charCodeAt(0);
var _tab = '\t'.charCodeAt(0);
var _lowerThan = '<'.charCodeAt(0);
var _greaterThan = '>'.charCodeAt(0);
var _ampersand = '&'.charCodeAt(0);
var _carriageReturn = '\r'.charCodeAt(0);
var _lineSeparator = '\u2028'.charCodeAt(0); //http://www.fileformat.info/info/unicode/char/2028/index.htm
var _bom = 65279;
var _replacementCharacter = '\ufffd';
export function renderLine(input:IRenderLineInput): IRenderLineOutput {
var lineText = input.lineContent;
var result: IRenderLineOutput = {
charOffsetInPart: [],
hasOverflowed: false,
lastRenderedPartIndex: 0,
partsCount: 0,
output: []
};
var partsCount = 0;
result.output.push('<span>');
if (lineText.length > 0) {
var charCode: number,
i: number,
len = lineText.length,
partClassName: string,
partIndex = -1,
nextPartIndex = 0,
tabsCharDelta = 0,
charOffsetInPart = 0,
append = '',
tabSize = input.tabSize,
insertSpacesCount: number,
stopRenderingLineAfter = input.stopRenderingLineAfter,
renderWhitespace = false;
var actualLineParts = input.parts;
if (actualLineParts.length === 0) {
throw new Error('Cannot render non empty line without line parts!');
}
if (stopRenderingLineAfter !== -1 && len > stopRenderingLineAfter - 1) {
append = lineText.substr(stopRenderingLineAfter - 1, 1);
len = stopRenderingLineAfter - 1;
result.hasOverflowed = true;
}
for (i = 0; i < len; i++) {
if (i === nextPartIndex) {
partIndex++;
nextPartIndex = (partIndex + 1 < actualLineParts.length ? actualLineParts[partIndex + 1].startIndex : Number.MAX_VALUE);
if (i > 0) {
result.output.push('</span>');
}
partsCount++;
result.output.push('<span class="');
partClassName = 'token ' + actualLineParts[partIndex].type.replace(/[^a-z0-9\-]/gi, ' ');
if (input.renderWhitespace) {
renderWhitespace = partClassName.indexOf('whitespace') >= 0;
}
result.output.push(partClassName);
result.output.push('">');
charOffsetInPart = 0;
}
result.charOffsetInPart[i] = charOffsetInPart;
charCode = lineText.charCodeAt(i);
switch (charCode) {
case _tab:
insertSpacesCount = tabSize - (i + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
if (insertSpacesCount > 0) {
result.output.push(renderWhitespace ? '&rarr;' : '&nbsp;');
insertSpacesCount--;
}
while (insertSpacesCount > 0) {
result.output.push('&nbsp;');
insertSpacesCount--;
}
break;
case _space:
result.output.push(renderWhitespace ? '&middot;' : '&nbsp;');
break;
case _lowerThan:
result.output.push('&lt;');
break;
case _greaterThan:
result.output.push('&gt;');
break;
case _ampersand:
result.output.push('&amp;');
break;
case 0:
result.output.push('&#00;');
break;
case _bom:
case _lineSeparator:
result.output.push(_replacementCharacter);
break;
case _carriageReturn:
// zero width space, because carriage return would introduce a line break
result.output.push('&#8203');
break;
default:
result.output.push(lineText.charAt(i));
}
charOffsetInPart ++;
}
result.output.push('</span>');
// When getting client rects for the last character, we will position the
// text range at the end of the span, insteaf of at the beginning of next span
result.charOffsetInPart[len] = charOffsetInPart;
// In case we stop rendering, we record here the index of the last span
// that should be used for getting client rects
result.lastRenderedPartIndex = partIndex;
if (append.length > 0) {
result.output.push('<span class="');
result.output.push(partClassName);
result.output.push('" style="color:grey">');
result.output.push(append);
result.output.push('&hellip;</span>');
}
} else {
// This is basically for IE's hit test to work
result.output.push('<span>&nbsp;</span>');
}
result.output.push('</span>');
result.partsCount = partsCount;
return result;
}

View file

@ -8,7 +8,7 @@ import Browser = require('vs/base/browser/browser');
import DomUtils = require('vs/base/browser/dom');
import Schedulers = require('vs/base/common/async');
import {createLine, IViewLineData} from 'vs/editor/browser/viewParts/lines/viewLine';
import {createLine, ViewLine} from 'vs/editor/browser/viewParts/lines/viewLine';
import {IVisibleLineData, ViewLayer} from 'vs/editor/browser/view/viewLayer';
import EditorBrowser = require('vs/editor/browser/editorBrowser');
import EditorCommon = require('vs/editor/common/editorCommon');
@ -27,7 +27,7 @@ export class ViewLines extends ViewLayer {
private static HORIZONTAL_EXTRA_PX = 30;
private _layoutProvider:EditorBrowser.ILayoutProvider;
_lines:IViewLineData[];
_lines:ViewLine[];
public textRangeRestingSpot:HTMLElement;
@ -191,7 +191,7 @@ export class ViewLines extends ViewLayer {
return this._lines[lineIndex].getWidth();
}
public linesVisibleRangesForRange(range:EditorCommon.IRange, includeNewLines:boolean): EditorBrowser.ILineVisibleRanges[] {
public linesVisibleRangesForRange(range:EditorCommon.IRange, includeNewLines:boolean): EditorBrowser.LineVisibleRanges[] {
if (this.shouldRender) {
// Cannot read from the DOM because it is dirty
// i.e. the model & the dom are out of sync, so I'd be reading something stale
@ -204,15 +204,14 @@ export class ViewLines extends ViewLayer {
return null;
}
var visibleRangesForLine:EditorBrowser.IVisibleRange[],
visibleRanges:EditorBrowser.ILineVisibleRanges[] = [],
var visibleRangesForLine:EditorBrowser.HorizontalRange[],
visibleRanges:EditorBrowser.LineVisibleRanges[] = [],
lineNumber:number,
lineIndex:number,
startColumn:number,
endColumn:number;
var boundingClientRect = this.domNode.getBoundingClientRect();
var clientRectDeltaTop = boundingClientRect.top;
var clientRectDeltaLeft = boundingClientRect.left;
var currentLineModelLineNumber:number,
@ -231,23 +230,26 @@ export class ViewLines extends ViewLayer {
startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber);
visibleRangesForLine = this._lines[lineIndex].getVisibleRangesForRange(lineNumber, startColumn, endColumn, clientRectDeltaTop, 0, clientRectDeltaLeft, this.textRangeRestingSpot);
visibleRangesForLine = this._lines[lineIndex].getVisibleRangesForRange(startColumn, endColumn, this.textRangeRestingSpot);
if (visibleRangesForLine && visibleRangesForLine.length > 0) {
if (includeNewLines && lineNumber < originalEndLineNumber) {
currentLineModelLineNumber = nextLineModelLineNumber;
nextLineModelLineNumber = this._context.model.convertViewPositionToModelPosition(lineNumber + 1, 1).lineNumber;
if (currentLineModelLineNumber !== nextLineModelLineNumber) {
visibleRangesForLine[visibleRangesForLine.length - 1].width += ViewLines.LINE_FEED_WIDTH;
}
}
visibleRanges.push({
lineNumber: lineNumber,
ranges: visibleRangesForLine
});
if (!visibleRangesForLine || visibleRangesForLine.length === 0) {
continue;
}
for (let i = 0, len = visibleRangesForLine.length; i < len; i++) {
visibleRangesForLine[i].left = Math.max(0, visibleRangesForLine[i].left - clientRectDeltaLeft);
}
if (includeNewLines && lineNumber < originalEndLineNumber) {
currentLineModelLineNumber = nextLineModelLineNumber;
nextLineModelLineNumber = this._context.model.convertViewPositionToModelPosition(lineNumber + 1, 1).lineNumber;
if (currentLineModelLineNumber !== nextLineModelLineNumber) {
visibleRangesForLine[visibleRangesForLine.length - 1].width += ViewLines.LINE_FEED_WIDTH;
}
}
visibleRanges.push(new EditorBrowser.LineVisibleRanges(lineNumber, visibleRangesForLine));
}
if (visibleRanges.length === 0) {
@ -257,7 +259,7 @@ export class ViewLines extends ViewLayer {
return visibleRanges;
}
public visibleRangesForRange2(range:EditorCommon.IRange, deltaTop:number, correctionTop:number, includeNewLines:boolean): EditorBrowser.IVisibleRange[] {
public visibleRangesForRange2(range:EditorCommon.IRange, deltaTop:number): EditorBrowser.VisibleRange[] {
if (this.shouldRender) {
// Cannot read from the DOM because it is dirty
@ -265,70 +267,41 @@ export class ViewLines extends ViewLayer {
return null;
}
var originalEndLineNumber = range.endLineNumber;
range = Range.intersectRanges(range, this._currentVisibleRange);
if (!range) {
return null;
}
var visibleRangesForLine:EditorBrowser.IVisibleRange[],
visibleRanges:EditorBrowser.IVisibleRange[] = [],
lineNumber:number,
adjustedLineNumberVerticalOffset:number,
lineIndex:number,
startColumn:number,
endColumn:number,
lineHeight = this._context.configuration.editor.lineHeight;
let result:EditorBrowser.VisibleRange[] = [];
let boundingClientRect = this.domNode.getBoundingClientRect();
let clientRectDeltaLeft = boundingClientRect.left;
var boundingClientRect = this.domNode.getBoundingClientRect();
var clientRectDeltaTop = boundingClientRect.top;
var clientRectDeltaLeft = boundingClientRect.left;
var currentLineModelLineNumber:number,
nextLineModelLineNumber:number;
if (includeNewLines) {
nextLineModelLineNumber = this._context.model.convertViewPositionToModelPosition(range.startLineNumber, 1).lineNumber;
}
for (lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
lineIndex = lineNumber - this._rendLineNumberStart;
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
let lineIndex = lineNumber - this._rendLineNumberStart;
if (lineIndex < 0 || lineIndex >= this._lines.length) {
continue;
}
startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber);
visibleRangesForLine = this._lines[lineIndex].getVisibleRangesForRange(lineNumber, startColumn, endColumn, clientRectDeltaTop, correctionTop, clientRectDeltaLeft, this.textRangeRestingSpot);
let startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
let endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber);
let visibleRangesForLine = this._lines[lineIndex].getVisibleRangesForRange(startColumn, endColumn, this.textRangeRestingSpot);
if (!visibleRangesForLine || visibleRangesForLine.length === 0) {
continue;
}
if (visibleRangesForLine && visibleRangesForLine.length > 0) {
adjustedLineNumberVerticalOffset = this._layoutProvider.getVerticalOffsetForLineNumber(lineNumber) - this._bigNumbersDelta + deltaTop;
for (var i = 0, len = visibleRangesForLine.length; i < len; i++) {
// Ranges must be positioned at lineHeight increments
// (overcome WebKit Range.getClientRects() rounding to integers)
visibleRangesForLine[i].top = adjustedLineNumberVerticalOffset;
visibleRangesForLine[i].height = lineHeight;
}
if (includeNewLines && lineNumber < originalEndLineNumber) {
currentLineModelLineNumber = nextLineModelLineNumber;
nextLineModelLineNumber = this._context.model.convertViewPositionToModelPosition(lineNumber + 1, 1).lineNumber;
if (currentLineModelLineNumber !== nextLineModelLineNumber) {
visibleRangesForLine[visibleRangesForLine.length - 1].width += ViewLines.LINE_FEED_WIDTH;
}
}
visibleRanges = visibleRanges.concat(visibleRangesForLine);
let adjustedLineNumberVerticalOffset = this._layoutProvider.getVerticalOffsetForLineNumber(lineNumber) - this._bigNumbersDelta + deltaTop;
for (let i = 0, len = visibleRangesForLine.length; i < len; i++) {
result.push(new EditorBrowser.VisibleRange(adjustedLineNumberVerticalOffset, Math.max(0, visibleRangesForLine[i].left - clientRectDeltaLeft), visibleRangesForLine[i].width));
}
}
if (visibleRanges.length === 0) {
if (result.length === 0) {
return null;
}
return visibleRanges;
return result;
}
// --- implementation
@ -472,7 +445,7 @@ export class ViewLines extends ViewLayer {
viewportStartX = viewport.left,
viewportEndX = viewportStartX + viewport.width;
var visibleRanges = this.visibleRangesForRange2(range, 0, 0, false),
var visibleRanges = this.visibleRangesForRange2(range, 0),
boxStartX = Number.MAX_VALUE,
boxEndX = 0;
@ -485,7 +458,7 @@ export class ViewLines extends ViewLayer {
}
var i:number,
visibleRange:EditorBrowser.IVisibleRange;
visibleRange:EditorBrowser.VisibleRange;
for (i = 0; i < visibleRanges.length; i++) {
visibleRange = visibleRanges[i];

View file

@ -10,6 +10,9 @@ import {ViewEventHandler} from 'vs/editor/common/viewModel/viewEventHandler';
import EditorBrowser = require('vs/editor/browser/editorBrowser');
import EditorCommon = require('vs/editor/common/editorCommon');
type HorizontalRange = EditorBrowser.HorizontalRange;
type LineVisibleRanges = EditorBrowser.LineVisibleRanges;
interface IRenderResult {
[lineNumber:string]:string[];
}
@ -25,13 +28,36 @@ interface IVisibleRangeEndPointStyle {
bottom: CornerStyle;
}
interface IVisibleRangeWithStyle extends EditorBrowser.IHorizontalRange {
startStyle?: IVisibleRangeEndPointStyle;
endStyle?: IVisibleRangeEndPointStyle;
class HorizontalRangeWithStyle {
public left: number;
public width: number;
public startStyle: IVisibleRangeEndPointStyle;
public endStyle: IVisibleRangeEndPointStyle;
constructor(other:HorizontalRange) {
this.left = other.left;
this.width = other.width;
this.startStyle = null;
this.endStyle = null;
}
}
interface ILineVisibleRangesWithStyle extends EditorBrowser.ILineVisibleRanges {
ranges: IVisibleRangeWithStyle[];
class LineVisibleRangesWithStyle {
public lineNumber: number;
public ranges: HorizontalRangeWithStyle[];
constructor(lineNumber:number, ranges:HorizontalRangeWithStyle[]) {
this.lineNumber = lineNumber;
this.ranges = ranges;
}
}
function toStyledRange(item: HorizontalRange): HorizontalRangeWithStyle {
return new HorizontalRangeWithStyle(item);
}
function toStyled(item: LineVisibleRanges): LineVisibleRangesWithStyle {
return new LineVisibleRangesWithStyle(item.lineNumber, item.ranges.map(toStyledRange));
}
// TODO@Alex: Remove this once IE11 fixes Bug #524217
@ -123,11 +149,11 @@ export class SelectionsOverlay extends ViewEventHandler implements EditorBrowser
// --- end event handlers
private _visibleRangesHaveGaps(linesVisibleRanges: EditorBrowser.ILineVisibleRanges[]): boolean {
private _visibleRangesHaveGaps(linesVisibleRanges: LineVisibleRangesWithStyle[]): boolean {
var i:number,
len:number,
lineVisibleRanges:EditorBrowser.ILineVisibleRanges;
lineVisibleRanges:LineVisibleRangesWithStyle;
for (i = 0, len = linesVisibleRanges.length; i < len; i++) {
lineVisibleRanges = linesVisibleRanges[i];
@ -141,8 +167,8 @@ export class SelectionsOverlay extends ViewEventHandler implements EditorBrowser
return false;
}
private _enrichVisibleRangesWithStyle(linesVisibleRanges:ILineVisibleRangesWithStyle[], previousFrame:ILineVisibleRangesWithStyle[]): void {
var curLineRange: IVisibleRangeWithStyle,
private _enrichVisibleRangesWithStyle(linesVisibleRanges:LineVisibleRangesWithStyle[], previousFrame:LineVisibleRangesWithStyle[]): void {
var curLineRange: HorizontalRangeWithStyle,
curLeft: number,
curRight: number,
prevLeft: number,
@ -154,8 +180,8 @@ export class SelectionsOverlay extends ViewEventHandler implements EditorBrowser
i:number,
len:number;
var previousFrameTop: IVisibleRangeWithStyle = null,
previousFrameBottom: IVisibleRangeWithStyle = null;
var previousFrameTop: HorizontalRangeWithStyle = null,
previousFrameBottom: HorizontalRangeWithStyle = null;
if (previousFrame && previousFrame.length > 0 && linesVisibleRanges.length > 0) {
@ -246,10 +272,10 @@ export class SelectionsOverlay extends ViewEventHandler implements EditorBrowser
}
}
private _getVisibleRangesWithStyle(selection: EditorCommon.IEditorRange, ctx: EditorBrowser.IRenderingContext, previousFrame:ILineVisibleRangesWithStyle[]): ILineVisibleRangesWithStyle[] {
var linesVisibleRanges = ctx.linesVisibleRangesForRange(selection, true) || [];
var visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges);
private _getVisibleRangesWithStyle(selection: EditorCommon.IEditorRange, ctx: EditorBrowser.IRenderingContext, previousFrame:LineVisibleRangesWithStyle[]): LineVisibleRangesWithStyle[] {
let _linesVisibleRanges = ctx.linesVisibleRangesForRange(selection, true) || [];
let linesVisibleRanges = _linesVisibleRanges.map(toStyled);
let visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges);
if (!isIEWithZoomingIssuesNearRoundedBorders && !visibleRangesHaveGaps && this._context.configuration.editor.roundedSelection) {
this._enrichVisibleRangesWithStyle(linesVisibleRanges, previousFrame);
@ -271,16 +297,16 @@ export class SelectionsOverlay extends ViewEventHandler implements EditorBrowser
lineOutput.push('px;"></div>');
}
private _actualRenderOneSelection(output:IRenderResult, visibleRanges:ILineVisibleRangesWithStyle[]): number {
private _actualRenderOneSelection(output:IRenderResult, visibleRanges:LineVisibleRangesWithStyle[]): number {
var visibleRangesHaveStyle = (visibleRanges.length > 0 && visibleRanges[0].ranges[0].startStyle),
lineVisibleRanges:ILineVisibleRangesWithStyle,
lineVisibleRanges:LineVisibleRangesWithStyle,
lineOutput: string[],
className:string,
lineHeight = this._context.configuration.editor.lineHeight.toString(),
i:number, len:number,
j:number, lenJ:number,
piecesCount = 0,
visibleRange:IVisibleRangeWithStyle;
visibleRange:HorizontalRangeWithStyle;
for (i = 0, len = visibleRanges.length; i < len; i++) {
lineVisibleRanges = visibleRanges[i];
@ -357,7 +383,7 @@ export class SelectionsOverlay extends ViewEventHandler implements EditorBrowser
return piecesCount;
}
private _previousFrameVisibleRangesWithStyle: ILineVisibleRangesWithStyle[][] = [];
private _previousFrameVisibleRangesWithStyle: LineVisibleRangesWithStyle[][] = [];
public shouldCallRender2(ctx:EditorBrowser.IRenderingContext): boolean {
if (!this.shouldRender) {
return false;
@ -366,10 +392,10 @@ export class SelectionsOverlay extends ViewEventHandler implements EditorBrowser
var output: IRenderResult = {},
selection:EditorCommon.IEditorRange,
visibleRangesWithStyle:ILineVisibleRangesWithStyle[],
visibleRangesWithStyle:LineVisibleRangesWithStyle[],
piecesCount = 0,
i:number,
thisFrameVisibleRangesWithStyle: ILineVisibleRangesWithStyle[][] = [];
thisFrameVisibleRangesWithStyle: LineVisibleRangesWithStyle[][] = [];
for (i = 0; i < this._selections.length; i++) {
selection = this._selections[i];

View file

@ -16,11 +16,11 @@ import EditorCommon = require('vs/editor/common/editorCommon');
import EditorBrowser = require('vs/editor/browser/editorBrowser');
import Actions = require('vs/base/common/actions');
import Sash = require('vs/base/browser/ui/sash/sash');
import ViewLine = require('vs/editor/browser/viewParts/lines/viewLine');
import ViewLineParts = require('vs/editor/common/viewLayout/viewLineParts');
import Schedulers = require('vs/base/common/async');
import {Range} from 'vs/editor/common/core/range';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {renderLine} from 'vs/editor/common/viewLayout/viewLineRenderer';
interface IEditorScrollEvent {
scrollLeft: number;
@ -1772,7 +1772,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
parts = ViewLineParts.createLineParts(lineNumber, lineContent, lineTokens, decorations, config.renderWhitespace);
var r = ViewLine.renderLine({
var r = renderLine({
lineContent: lineContent,
tabSize: indentation.tabSize,
stopRenderingLineAfter: config.stopRenderingLineAfter,

View file

@ -35,7 +35,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget.CodeEditorWidget
// Overwrite parent's options
super.updateOptions(this._overwriteOptions);
this._lifetimeListeners.push(parentEditor.addListener(EditorCommon.EventType.ConfigurationChanged, (e:EditorCommon.IConfigurationChangedEvent) => this._onParentConfigurationChanged(e)));
this._lifetimeDispose.push(parentEditor.addListener2(EditorCommon.EventType.ConfigurationChanged, (e:EditorCommon.IConfigurationChangedEvent) => this._onParentConfigurationChanged(e)));
}
private _onParentConfigurationChanged(e:EditorCommon.IConfigurationChangedEvent): void {

View file

@ -9,9 +9,6 @@
.monaco-editor.vs .token.whitespace { color: rgba(51, 51, 51, 0.2) !important; }
.monaco-editor.vs .token.terminal { color: #e00000; }
.monaco-editor.vs .token.terminal.code1 { font-weight: bold; }
.monaco-editor.vs .token.variable { color: #001080; }
.monaco-editor.vs .token.variable.name { color: #001080; }
.monaco-editor.vs .token.variable.predefined { color: #4864AA; }
.monaco-editor.vs .token.constant { color: #dd0000; }
.monaco-editor.vs .token.string { color: #A31515; }
.monaco-editor.vs .token.string.escape { color: #A31515; }
@ -25,7 +22,6 @@
.monaco-editor.vs .token.delimiter { color: #000000; }
.monaco-editor.vs .token.tag { color: #800000; }
.monaco-editor.vs .token.metatag { color: #e00000; }
.monaco-editor.vs .token.annotation { color: gray; }
.monaco-editor.vs .token.key { color: #863B00; }
.monaco-editor.vs .token.attribute.name { color: red; }
.monaco-editor.vs .token.attribute.value { color: #0451A5; }
@ -96,9 +92,6 @@
.monaco-editor.vs-dark .token.whitespace { color: rgba(227, 228, 226, 0.16) !important; }
.monaco-editor.vs-dark .token.terminal { color: #BD5050; }
.monaco-editor.vs-dark .token.terminal.code1 { font-weight: bold; }
.monaco-editor.vs-dark .token.variable { color: #9CDCFE; }
.monaco-editor.vs-dark .token.variable.name { color: #9CDCFE; }
.monaco-editor.vs-dark .token.variable.predefined { color: #4864AA; }
.monaco-editor.vs-dark .token.constant { color: #dd0000; }
.monaco-editor.vs-dark .token.string { color: #CE9178; }
@ -112,7 +105,6 @@
.monaco-editor.vs-dark .token.delimiter { color: #DCDCDC; }
.monaco-editor.vs-dark .token.tag { color: #569CD6; }
.monaco-editor.vs-dark .token.metatag { color: #DD6A6F; }
.monaco-editor.vs-dark .token.annotation { color: #cc6666; }
.monaco-editor.vs-dark .token.key { color: #9CDCFE; }
.monaco-editor.vs-dark .token.attribute.name { color: #9CDCFE; }
.monaco-editor.vs-dark .token.attribute.value { color: #CE9178; }
@ -185,9 +177,6 @@
.monaco-editor.hc-black .token.terminal { color: #569CD6; }
.monaco-editor.hc-black .token.terminal.code1 { font-weight: bold; }
.monaco-editor.hc-black .token.variable,
.monaco-editor.hc-black .token.variable.name,
.monaco-editor.hc-black .token.variable.predefined { color: #9CDCFE; }
.monaco-editor.hc-black .token.constant { color: #dd0000; }
.monaco-editor.hc-black .token.string { color: #CE9178; }
.monaco-editor.hc-black .token.string.escape { color: #CE9178; }
@ -201,7 +190,6 @@
.monaco-editor.hc-black .token.delimiter { color: #FFFF00; }
.monaco-editor.hc-black .token.tag { color: #569CD6; }
.monaco-editor.hc-black .token.metatag { color: #569CD6; }
.monaco-editor.hc-black .token.annotation { color: #569CD6; }
.monaco-editor.hc-black .token.key { color: #9CDCFE; }
.monaco-editor.hc-black .token.attribute.name { color: #569CD6; }
.monaco-editor.hc-black .token.attribute.value { color: #3FF23F; }
@ -286,9 +274,11 @@
.monaco-editor.vs .token.constant.language { color: #0000FF; }
.monaco-editor.vs .token.constant.entity { color: #A31515; }
.monaco-editor.vs .token.constant.numeric { color: #09885A; }
/*
.monaco-editor.vs .token.constant.numeric.hex { color: #3030c0; }
.monaco-editor.vs .token.constant.numeric.octal { color: #204070; }
.monaco-editor.vs .token.constant.numeric.binary { color: #e07070; }
*/
.monaco-editor.vs .token.constant.regexp { color: #811f3f; }
.monaco-editor.vs .token.constant.rgb-value { color: #0451A5; }
@ -338,7 +328,6 @@
.monaco-editor.vs .token.markup.raw
.monaco-editor.vs .token.markup.other*/
.monaco-editor.vs .token.meta.variable { color: #000000; }
.monaco-editor.vs .token.meta.selector { color: #800000; }
.monaco-editor.vs .token.meta.tag { color: #800000; }
.monaco-editor.vs .token.meta.preprocessor { color: #0000FF; }
@ -371,7 +360,6 @@
.monaco-editor.vs .token.string.quoted.other
.monaco-editor.vs .token.string.unquoted
.monaco-editor.vs .token.string.interpolated*/
.monaco-editor.vs .token.string.quoted.variable { color: #001188; }
.monaco-editor.vs .token.string.regexp { color: #811f3f; }
/*.monaco-editor.vs .token.string.other*/
@ -388,11 +376,6 @@
.monaco-editor.vs .token.support.variable
.monaco-editor.vs .token.support.other*/
.monaco-editor.vs .token.variable.parameter { color: #001188; }
/*.monaco-editor.vs .token.variable*/
.monaco-editor.vs .token.variable.language { color: #4864AA; }
/*.monaco-editor.vs .token.variable.other*/
/* Keywords should come at the end in order to match cases like token.keyword.string */
.monaco-editor.vs .token.keyword { color: #0000FF; }
.monaco-editor.vs .token.keyword.control { color: #0000FF; }
@ -416,9 +399,11 @@
.monaco-editor.vs-dark .token.constant.language { color: #569CD6; }
.monaco-editor.vs-dark .token.constant.entity { color: #CE9178; }
.monaco-editor.vs-dark .token.constant.numeric { color: #B5CEA8; }
/*
.monaco-editor.vs-dark .token.constant.numeric.hex { color: #5BB498; }
.monaco-editor.vs-dark .token.constant.numeric.octal { color: #5BB498; }
.monaco-editor.vs-dark .token.constant.numeric.binary { color: #e07070; }
*/
.monaco-editor.vs-dark .token.constant.regexp { color: #646695; }
.monaco-editor.vs-dark .token.constant.rgb-value { color: #D4D4D4; }
/*.monaco-editor.vs-dark .token.constant.character
@ -467,8 +452,7 @@
.monaco-editor.vs-dark .token.markup.raw
.monaco-editor.vs-dark .token.markup.other*/
/* for backward compatibility, keep variables in js/ts uncolored */
.monaco-editor.vs-dark .token.meta.variable { color: #D4D4D4; }
.monaco-editor.vs-dark .token.meta.selector { color: #D7BA7D; }
@ -504,7 +488,6 @@
.monaco-editor.vs-dark .token.string.quoted.other
.monaco-editor.vs-dark .token.string.unquoted
.monaco-editor.vs-dark .token.string.interpolated*/
.monaco-editor.vs-dark .token.string.quoted.variable { color: #74B0DF; }
.monaco-editor.vs-dark .token.string.regexp { color: #D16969; }
/*.monaco-editor.vs-dark .token.string.other*/
@ -519,10 +502,6 @@
.monaco-editor.vs-dark .token.support.variable
.monaco-editor.vs-dark .token.support.other*/
.monaco-editor.vs-dark .token.variable.parameter { color: #D4D4D4; }
/*.monaco-editor.vs-dark .token.variable*/
.monaco-editor.vs-dark .token.variable.language { color: #569CD6; }
/*.monaco-editor.vs-dark .token.variable.other*/
/* Keywords should come at the end in order to match cases like token.keyword.string */
.monaco-editor.vs-dark .token.keyword { color: #569CD6; }
@ -550,9 +529,11 @@
.monaco-editor.hc-black .token.constant.language { color: #569CD6; }
.monaco-editor.hc-black .token.constant.entity { color: #CE9178; }
.monaco-editor.hc-black .token.constant.numeric { color: #B5CEA8; }
/*
.monaco-editor.hc-black .token.constant.numeric.hex { color: #5BB498; }
.monaco-editor.hc-black .token.constant.numeric.octal { color: #5BB498; }
.monaco-editor.hc-black .token.constant.numeric.binary { color: #e07070; }
*/
.monaco-editor.hc-black .token.constant.regexp { color: #B46695; }
.monaco-editor.hc-black .token.constant.rgb-value { color: #D4D4D4; }
/*.monaco-editor.hc-black .token.constant.character
@ -600,9 +581,6 @@
.monaco-editor.hc-black .token.markup.raw
.monaco-editor.hc-black .token.markup.other*/
/* for backward compatibility, keep variables in js/ts uncolored */
.monaco-editor.hc-black .token.meta.variable { color: #D4D4D4; }
.monaco-editor.hc-black .token.meta.selector { color: #D7BA7D; }
.monaco-editor.hc-black .token.meta.tag { color: #808080; } /* gray for html/xml-tag brackets */
.monaco-editor.hc-black .token.meta.preprocessor { color: #569CD6; }
@ -635,7 +613,6 @@
.monaco-editor.hc-black .token.string.quoted.other
.monaco-editor.hc-black .token.string.unquoted
.monaco-editor.hc-black .token.string.interpolated*/
.monaco-editor.hc-black .token.string.quoted.variable { color: #74B0DF; }
.monaco-editor.hc-black .token.string.regexp { color: #D16969; }
/*.monaco-editor.hc-black .token.string.other*/
@ -650,11 +627,6 @@
.monaco-editor.hc-black .token.support.variable
.monaco-editor.hc-black .token.support.other*/
.monaco-editor.hc-black .token.variable.parameter { color: #D4D4D4; }
/*.monaco-editor.hc-black .token.variable*/
.monaco-editor.hc-black .token.variable.language { color: #569CD6; }
/*.monaco-editor.hc-black .token.variable.other*/
/* Keywords should come at the end in order to match cases like token.keyword.string */
.monaco-editor.hc-black .token.keyword { color: #569CD6; }

View file

@ -38,7 +38,6 @@ export abstract class CommonCodeEditor extends EventEmitter.EventEmitter impleme
protected id:number;
_lifetimeListeners:EventEmitter.ListenerUnbind[];
_lifetimeDispose: IDisposable[];
_configuration:CommonEditorConfiguration;
@ -97,7 +96,6 @@ export abstract class CommonCodeEditor extends EventEmitter.EventEmitter impleme
this._langIdKey = this._keybindingService.createKey<string>(EditorCommon.KEYBINDING_CONTEXT_EDITOR_LANGUAGE_ID, undefined);
// listeners that are kept during the whole editor lifetime
this._lifetimeListeners = [];
this._decorationTypeKeysToIds = {};
options = options || {};
@ -116,7 +114,7 @@ export abstract class CommonCodeEditor extends EventEmitter.EventEmitter impleme
if (this._configuration.editor.tabFocusMode) {
this._editorTabMovesFocusKey.set(true);
}
this._lifetimeListeners.push(this._configuration.addListener(EditorCommon.EventType.ConfigurationChanged, (e) => this.emit(EditorCommon.EventType.ConfigurationChanged, e)));
this._lifetimeDispose.push(this._configuration.onDidChange((e) => this.emit(EditorCommon.EventType.ConfigurationChanged, e)));
this.forcedWidgetFocusCount = 0;
@ -153,10 +151,6 @@ export abstract class CommonCodeEditor extends EventEmitter.EventEmitter impleme
public dispose(): void {
this._codeEditorService.removeCodeEditor(this);
this._lifetimeDispose = disposeAll(this._lifetimeDispose);
// unbind listeners
while(this._lifetimeListeners.length > 0) {
this._lifetimeListeners.pop()();
}
var contributionId:string;
for (contributionId in this.contributions) {

View file

@ -4,16 +4,17 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import Objects = require('vs/base/common/objects');
import {EventEmitter} from 'vs/base/common/eventEmitter';
import Strings = require('vs/base/common/strings');
import * as nls from 'vs/nls';
import * as Objects from 'vs/base/common/objects';
import Event, {Emitter} from 'vs/base/common/event';
import * as Strings from 'vs/base/common/strings';
import {Registry} from 'vs/platform/platform';
import ConfigurationRegistry = require('vs/platform/configuration/common/configurationRegistry');
import EditorCommon = require('vs/editor/common/editorCommon');
import * as EditorCommon from 'vs/editor/common/editorCommon';
import {DefaultConfig} from 'vs/editor/common/config/defaultConfig';
import {HandlerDispatcher} from 'vs/editor/common/controller/handlerDispatcher';
import {EditorLayoutProvider} from 'vs/editor/common/viewLayout/editorLayoutProvider';
import {Disposable} from 'vs/base/common/lifecycle';
export class ConfigurationWithDefaults {
@ -194,8 +195,8 @@ class InternalEditorOptionsHelper {
}
private static _sanitizeScrollbarOpts(raw:EditorCommon.IEditorScrollbarOptions, mouseWheelScrollSensitivity:number): EditorCommon.IInternalEditorScrollbarOptions {
var horizontalScrollbarSize = toIntegerWithDefault(raw.horizontalScrollbarSize, 10);
var verticalScrollbarSize = toIntegerWithDefault(raw.verticalScrollbarSize, 14);
let horizontalScrollbarSize = toIntegerWithDefault(raw.horizontalScrollbarSize, 10);
let verticalScrollbarSize = toIntegerWithDefault(raw.verticalScrollbarSize, 14);
return {
vertical: toStringSet(raw.vertical, ['auto', 'visible', 'hidden'], 'auto'),
horizontal: toStringSet(raw.horizontal, ['auto', 'visible', 'hidden'], 'auto'),
@ -329,7 +330,7 @@ function toBooleanWithDefault(value:any, defaultValue:boolean): boolean {
}
function toFloat(source: any, defaultValue: number): number {
var r = parseFloat(source);
let r = parseFloat(source);
if (isNaN(r)) {
r = defaultValue;
}
@ -337,7 +338,7 @@ function toFloat(source: any, defaultValue: number): number {
}
function toInteger(source:any, minimum?:number, maximum?:number): number {
var r = parseInt(source, 10);
let r = parseInt(source, 10);
if (isNaN(r)) {
r = 0;
}
@ -378,7 +379,7 @@ export interface IIndentationGuesser {
(tabSize:number): EditorCommon.IGuessedIndentation;
}
export abstract class CommonEditorConfiguration extends EventEmitter implements EditorCommon.IConfiguration {
export abstract class CommonEditorConfiguration extends Disposable implements EditorCommon.IConfiguration {
public handlerDispatcher:EditorCommon.IHandlerDispatcher;
public editor:EditorCommon.IInternalEditorOptions;
@ -390,10 +391,11 @@ export abstract class CommonEditorConfiguration extends EventEmitter implements
private _isDominatedByLongLines:boolean;
private _lineCount:number;
private _onDidChange = this._register(new Emitter<EditorCommon.IConfigurationChangedEvent>());
public onDidChange: Event<EditorCommon.IConfigurationChangedEvent> = this._onDidChange.event;
constructor(options:any, indentationGuesser:IIndentationGuesser = null) {
super([
EditorCommon.EventType.ConfigurationChanged
]);
super();
this._configWithDefaults = new ConfigurationWithDefaults(options);
this._indentationGuesser = indentationGuesser;
this._cachedGuessedIndentationTabSize = -1;
@ -427,7 +429,7 @@ export abstract class CommonEditorConfiguration extends EventEmitter implements
}
if (hasChanged) {
this.emit(EditorCommon.EventType.ConfigurationChanged, changeEvent);
this._onDidChange.fire(changeEvent);
}
}
@ -552,7 +554,7 @@ export abstract class CommonEditorConfiguration extends EventEmitter implements
}
private _normalizeIndentationFromWhitespace(str:string): string {
var indentation = this.getIndentationOptions(),
let indentation = this.getIndentationOptions(),
spacesCnt = 0,
i:number;
@ -564,9 +566,9 @@ export abstract class CommonEditorConfiguration extends EventEmitter implements
}
}
var result = '';
let result = '';
if (!indentation.insertSpaces) {
var tabsCnt = Math.floor(spacesCnt / indentation.tabSize);
let tabsCnt = Math.floor(spacesCnt / indentation.tabSize);
spacesCnt = spacesCnt % indentation.tabSize;
for (i = 0; i < tabsCnt; i++) {
result += '\t';
@ -581,7 +583,7 @@ export abstract class CommonEditorConfiguration extends EventEmitter implements
}
public normalizeIndentation(str:string): string {
var firstNonWhitespaceIndex = Strings.firstNonWhitespaceIndex(str);
let firstNonWhitespaceIndex = Strings.firstNonWhitespaceIndex(str);
if (firstNonWhitespaceIndex === -1) {
firstNonWhitespaceIndex = str.length;
}
@ -589,10 +591,10 @@ export abstract class CommonEditorConfiguration extends EventEmitter implements
}
public getOneIndent(): string {
var indentation = this.getIndentationOptions();
let indentation = this.getIndentationOptions();
if (indentation.insertSpaces) {
var result = '';
for (var i = 0; i < indentation.tabSize; i++) {
let result = '';
for (let i = 0; i < indentation.tabSize; i++) {
result += ' ';
}
return result;
@ -627,24 +629,24 @@ export class EditorConfiguration {
return;
}
var editors:EditorCommon.IEditor[] = editorOrArray;
let editors:EditorCommon.IEditor[] = editorOrArray;
if (!Array.isArray(editorOrArray)) {
editors = [editorOrArray];
}
for (var i = 0; i < editors.length; i++) {
var editor = editors[i];
for (let i = 0; i < editors.length; i++) {
let editor = editors[i];
// Editor Settings (Code Editor, Diff, Terminal)
if (editor && typeof editor.updateOptions === 'function') {
var type = editor.getEditorType();
let type = editor.getEditorType();
if (type !== EditorCommon.EditorType.ICodeEditor && type !== EditorCommon.EditorType.IDiffEditor) {
continue;
}
var editorConfig = config[EditorConfiguration.EDITOR_SECTION];
let editorConfig = config[EditorConfiguration.EDITOR_SECTION];
if (type === EditorCommon.EditorType.IDiffEditor) {
var diffEditorConfig = config[EditorConfiguration.DIFF_EDITOR_SECTION];
let diffEditorConfig = config[EditorConfiguration.DIFF_EDITOR_SECTION];
if (diffEditorConfig) {
if (!editorConfig) {
editorConfig = diffEditorConfig;
@ -663,7 +665,7 @@ export class EditorConfiguration {
}
}
var configurationRegistry = <ConfigurationRegistry.IConfigurationRegistry>Registry.as(ConfigurationRegistry.Extensions.Configuration);
let configurationRegistry = <ConfigurationRegistry.IConfigurationRegistry>Registry.as(ConfigurationRegistry.Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'editor',
'order': 5,

View file

@ -2429,7 +2429,9 @@ export interface IEditorStyling {
lineHeight: number;
}
export interface IConfiguration extends IEventEmitter {
export interface IConfiguration {
onDidChange: Event<IConfigurationChangedEvent>;
editor:IInternalEditorOptions;
setLineCount(lineCount:number): void;

View file

@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {ILineToken} from 'vs/editor/common/editorCommon';
export interface IRenderLineInput {
lineContent: string;
tabSize: number;
stopRenderingLineAfter: number;
renderWhitespace: boolean;
parts: ILineToken[];
}
export interface IRenderLineOutput {
charOffsetInPart: number[];
lastRenderedPartIndex: number;
output: string[];
}
const _space = ' '.charCodeAt(0);
const _tab = '\t'.charCodeAt(0);
const _lowerThan = '<'.charCodeAt(0);
const _greaterThan = '>'.charCodeAt(0);
const _ampersand = '&'.charCodeAt(0);
const _carriageReturn = '\r'.charCodeAt(0);
const _lineSeparator = '\u2028'.charCodeAt(0); //http://www.fileformat.info/info/unicode/char/2028/index.htm
const _bom = 65279;
export function renderLine(input:IRenderLineInput): IRenderLineOutput {
const lineText = input.lineContent;
const lineTextLength = lineText.length;
const tabSize = input.tabSize;
const actualLineParts = input.parts;
const renderWhitespace = input.renderWhitespace;
const charBreakIndex = (input.stopRenderingLineAfter === -1 ? lineTextLength : input.stopRenderingLineAfter - 1);
if (lineTextLength === 0) {
return {
charOffsetInPart: [],
lastRenderedPartIndex: 0,
// This is basically for IE's hit test to work
output: ['<span><span>&nbsp;</span></span>']
};
}
if (actualLineParts.length === 0) {
throw new Error('Cannot render non empty line without line parts!');
}
let charIndex = 0;
let out: string[] = [];
let charOffsetInPartArr: number[] = [];
let charOffsetInPart = 0;
out.push('<span>');
for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) {
let part = actualLineParts[partIndex];
out.push('<span class="');
out.push('token ');
out.push(part.type.replace(/[^a-z0-9\-]/gi, ' '));
out.push('">');
let partRendersWhitespace = false;
if (renderWhitespace) {
partRendersWhitespace = (/whitespace$/.test(part.type));
}
let toCharIndex = lineTextLength;
if (partIndex + 1 < partIndexLen) {
toCharIndex = Math.min(lineTextLength, actualLineParts[partIndex + 1].startIndex);
}
charOffsetInPart = 0;
let tabsCharDelta = 0;
for (; charIndex < toCharIndex; charIndex++) {
charOffsetInPartArr[charIndex] = charOffsetInPart;
let charCode = lineText.charCodeAt(charIndex);
switch (charCode) {
case _tab:
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
if (insertSpacesCount > 0) {
out.push(partRendersWhitespace ? '&rarr;' : '&nbsp;');
insertSpacesCount--;
}
while (insertSpacesCount > 0) {
out.push('&nbsp;');
insertSpacesCount--;
}
break;
case _space:
out.push(partRendersWhitespace ? '&middot;' : '&nbsp;');
break;
case _lowerThan:
out.push('&lt;');
break;
case _greaterThan:
out.push('&gt;');
break;
case _ampersand:
out.push('&amp;');
break;
case 0:
out.push('&#00;');
break;
case _bom:
case _lineSeparator:
out.push('\ufffd');
break;
case _carriageReturn:
// zero width space, because carriage return would introduce a line break
out.push('&#8203');
break;
default:
out.push(lineText.charAt(charIndex));
}
charOffsetInPart ++;
if (charIndex >= charBreakIndex) {
out.push('&hellip;</span></span>');
charOffsetInPartArr[charOffsetInPartArr.length - 1]++;
return {
charOffsetInPart: charOffsetInPartArr,
lastRenderedPartIndex: partIndex,
output: out
};
}
}
out.push('</span>');
}
out.push('</span>');
// When getting client rects for the last character, we will position the
// text range at the end of the span, insteaf of at the beginning of next span
charOffsetInPartArr.push(charOffsetInPart);
return {
charOffsetInPart: charOffsetInPartArr,
lastRenderedPartIndex: actualLineParts.length - 1,
output: out
};
}

View file

@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import {EventEmitter, IEventEmitter, IEmitterEvent, ListenerUnbind} from 'vs/base/common/eventEmitter';
import {EventEmitter, IEventEmitter, EmitterEvent, IEmitterEvent, ListenerUnbind} from 'vs/base/common/eventEmitter';
import Strings = require('vs/base/common/strings');
import {Selection} from 'vs/editor/common/core/selection';
import {Range} from 'vs/editor/common/core/range';
import {ViewModelDecorations} from 'vs/editor/common/viewModel/viewModelDecorations';
import {ViewModelCursors} from 'vs/editor/common/viewModel/viewModelCursors';
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
import {Position} from 'vs/editor/common/core/position';
import EditorCommon = require('vs/editor/common/editorCommon');
@ -38,6 +39,7 @@ export class ViewModel extends EventEmitter implements EditorCommon.IViewModel {
private model:EditorCommon.IModel;
private listenersToRemove:ListenerUnbind[];
private _toDispose: IDisposable[];
private lines:ILinesCollection;
private decorations:ViewModelDecorations;
private cursors:ViewModelCursors;
@ -69,14 +71,18 @@ export class ViewModel extends EventEmitter implements EditorCommon.IViewModel {
this._updateShouldForceTokenization();
this.listenersToRemove = [];
this._toDispose = [];
this.listenersToRemove.push(this.model.addBulkListener((events:IEmitterEvent[]) => this.onEvents(events)));
this.listenersToRemove.push(this.configuration.addBulkListener((events:IEmitterEvent[]) => this.onEvents(events)));
this._toDispose.push(this.configuration.onDidChange((e) => {
this.onEvents([new EmitterEvent(EditorCommon.EventType.ConfigurationChanged, e)]);
}));
}
public dispose(): void {
this.listenersToRemove.forEach((element) => {
element();
});
this._toDispose = disposeAll(this._toDispose);
this.listenersToRemove = [];
this.decorations.dispose();
this.decorations = null;

View file

@ -232,12 +232,14 @@ class DefineKeybindingLauncherWidget implements EditorBrowser.IOverlayWidget {
private _domNode: HTMLElement;
private _toDispose: IDisposable[];
private _isVisible: boolean;
constructor(editor:EditorBrowser.ICodeEditor, keybindingService:IKeybindingService, onLaunch:()=>void) {
this._editor = editor;
this._domNode = document.createElement('div');
this._domNode.className = 'defineKeybindingLauncher';
this._domNode.style.display = 'none';
this._isVisible = false;
let keybinding = keybindingService.lookupKeybindings(DefineKeybindingAction.ID);
let extra = '';
if (keybinding.length > 0) {
@ -259,11 +261,21 @@ class DefineKeybindingLauncherWidget implements EditorBrowser.IOverlayWidget {
}
public show(): void {
if (this._isVisible) {
return;
}
this._domNode.style.display = 'block';
this._isVisible = true;
this._editor.layoutOverlayWidget(this);
}
public hide(): void {
if (!this._isVisible) {
return;
}
this._domNode.style.display = 'none';
this._isVisible = false;
this._editor.layoutOverlayWidget(this);
}
// ----- IOverlayWidget API
@ -278,7 +290,7 @@ class DefineKeybindingLauncherWidget implements EditorBrowser.IOverlayWidget {
public getPosition(): EditorBrowser.IOverlayWidgetPosition {
return {
preference: EditorBrowser.OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER
preference: this._isVisible ? EditorBrowser.OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER : null
};
}
}

View file

@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import assert = require('assert');
import ViewLineParts = require('vs/editor/common/viewLayout/viewLineParts');
import {renderLine} from 'vs/editor/common/viewLayout/viewLineRenderer';
import {ILineToken} from 'vs/editor/common/editorCommon';
suite('viewLineRenderer.renderLine', () => {
function createPart(startIndex: number, type:string): ILineToken {
return {
startIndex: startIndex,
type: type
};
}
function assertCharacterReplacement(lineContent:string, tabSize:number, expected:string, expectedCharOffsetInPart: number[]): void {
let _actual = renderLine({
lineContent: lineContent,
tabSize: tabSize,
stopRenderingLineAfter: -1,
renderWhitespace: false,
parts: [createPart(0, '')]
});
assert.equal(_actual.output.join(''), '<span><span class="token ">' + expected + '</span></span>');
assert.deepEqual(_actual.charOffsetInPart, expectedCharOffsetInPart);
}
test('replaces spaces', () => {
assertCharacterReplacement(' ', 4, '&nbsp;', [0, 1]);
assertCharacterReplacement(' ', 4, '&nbsp;&nbsp;', [0, 1, 2]);
assertCharacterReplacement('a b', 4, 'a&nbsp;&nbsp;b', [0, 1, 2, 3, 4]);
});
test('escapes HTML markup', () => {
assertCharacterReplacement('a<b', 4, 'a&lt;b', [0, 1, 2, 3]);
assertCharacterReplacement('a>b', 4, 'a&gt;b', [0, 1, 2, 3]);
assertCharacterReplacement('a&b', 4, 'a&amp;b', [0, 1, 2, 3]);
});
test('replaces some bad characters', () => {
assertCharacterReplacement('a\0b', 4, 'a&#00;b', [0, 1, 2, 3]);
assertCharacterReplacement('a' + String.fromCharCode(65279) + 'b', 4, 'a\ufffdb', [0, 1, 2, 3]);
assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [0, 1, 2, 3]);
assertCharacterReplacement('a\rb', 4, 'a&#8203b', [0, 1, 2, 3]);
});
test('handles tabs', () => {
assertCharacterReplacement('\t', 4, '&nbsp;&nbsp;&nbsp;&nbsp;', [0, 4]);
assertCharacterReplacement('x\t', 4, 'x&nbsp;&nbsp;&nbsp;', [0, 1, 4]);
assertCharacterReplacement('xx\t', 4, 'xx&nbsp;&nbsp;', [0, 1, 2, 4]);
assertCharacterReplacement('xxx\t', 4, 'xxx&nbsp;', [0, 1, 2, 3, 4]);
assertCharacterReplacement('xxxx\t', 4, 'xxxx&nbsp;&nbsp;&nbsp;&nbsp;', [0, 1, 2, 3, 4, 8]);
});
function assertParts(lineContent:string, tabSize:number, parts: ILineToken[], expected:string, expectedCharOffsetInPart:number[]): void {
let _actual = renderLine({
lineContent: lineContent,
tabSize: tabSize,
stopRenderingLineAfter: -1,
renderWhitespace: false,
parts: parts
});
assert.equal(_actual.output.join(''), '<span>' + expected + '</span>');
assert.deepEqual(_actual.charOffsetInPart, expectedCharOffsetInPart);
}
test('empty line', () => {
assertParts('', 4, [], '<span>&nbsp;</span>', []);
});
test('uses part type', () => {
assertParts('x', 4, [createPart(0, 'y')], '<span class="token y">x</span>', [0, 1]);
assertParts('x', 4, [createPart(0, 'aAbBzZ0123456789-cC')], '<span class="token aAbBzZ0123456789-cC">x</span>', [0, 1]);
assertParts('x', 4, [createPart(0, '"~!@#$%^&*()\'')], '<span class="token ">x</span>', [0, 1]);
});
test('two parts', () => {
assertParts('xy', 4, [createPart(0, 'a'), createPart(1, 'b')], '<span class="token a">x</span><span class="token b">y</span>', [0, 0, 1]);
assertParts('xyz', 4, [createPart(0, 'a'), createPart(1, 'b')], '<span class="token a">x</span><span class="token b">yz</span>', [0, 0, 1, 2]);
assertParts('xyz', 4, [createPart(0, 'a'), createPart(2, 'b')], '<span class="token a">xy</span><span class="token b">z</span>', [0, 1, 0, 1]);
});
test('overflow', () => {
let _actual = renderLine({
lineContent: 'Hello world!',
parts: [
createPart( 0, '0'),
createPart( 1, '1'),
createPart( 2, '2'),
createPart( 3, '3'),
createPart( 4, '4'),
createPart( 5, '5'),
createPart( 6, '6'),
createPart( 7, '7'),
createPart( 8, '8'),
createPart( 9, '9'),
createPart(10, '10'),
createPart(11, '11'),
],
tabSize: 4,
stopRenderingLineAfter: 6,
renderWhitespace: true,
});
let expectedOutput = [
'<span class="token 0">H</span>',
'<span class="token 1">e</span>',
'<span class="token 2">l</span>',
'<span class="token 3">l</span>',
'<span class="token 4">o</span>',
'<span class="token 5">&nbsp;&hellip;</span>'
].join('');
assert.equal(_actual.output.join(''), '<span>' + expectedOutput + '</span>');
assert.deepEqual(_actual.charOffsetInPart, [
0,
0,
0,
0,
0,
1
]);
});
test('typical line', () => {
let lineText = '\t export class Game { // http://test.com ';
let lineParts = [
createPart( 0, 'block meta ts leading whitespace'),
createPart( 5, 'block declaration meta modifier object storage ts'),
createPart(11, 'block declaration meta object ts'),
createPart(12, 'block declaration meta object storage type ts'),
createPart(17, 'block declaration meta object ts'),
createPart(18, 'block class declaration entity meta name object ts'),
createPart(22, 'block declaration meta object ts'),
createPart(23, 'delimiter curly typescript'),
createPart(24, 'block body declaration meta object ts'),
createPart(25, 'block body comment declaration line meta object ts'),
createPart(28, 'block body comment declaration line meta object ts detected-link'),
createPart(43, 'block body comment declaration line meta object ts trailing whitespace'),
];
let expectedOutput = [
'<span class="token block meta ts leading whitespace">&rarr;&nbsp;&nbsp;&nbsp;&middot;&middot;&middot;&middot;</span>',
'<span class="token block declaration meta modifier object storage ts">export</span>',
'<span class="token block declaration meta object ts">&nbsp;</span>',
'<span class="token block declaration meta object storage type ts">class</span>',
'<span class="token block declaration meta object ts">&nbsp;</span>',
'<span class="token block class declaration entity meta name object ts">Game</span>',
'<span class="token block declaration meta object ts">&nbsp;</span>',
'<span class="token delimiter curly typescript">{</span>',
'<span class="token block body declaration meta object ts">&nbsp;</span>',
'<span class="token block body comment declaration line meta object ts">//&nbsp;</span>',
'<span class="token block body comment declaration line meta object ts detected-link">http://test.com</span>',
'<span class="token block body comment declaration line meta object ts trailing whitespace">&middot;&middot;&middot;&middot;&middot;</span>'
].join('');
let expectedOffsetsArr = [
[0, 4, 5, 6, 7],
[0, 1, 2, 3, 4, 5],
[0],
[0, 1, 2, 3, 4],
[0],
[0, 1, 2, 3],
[0],
[0],
[0],
[0, 1, 2],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
[0, 1, 2, 3, 4, 5],
];
let expectedOffsets = expectedOffsetsArr.reduce((prev, curr) => prev.concat(curr), []);
let _actual = renderLine({
lineContent: lineText,
tabSize: 4,
stopRenderingLineAfter: -1,
renderWhitespace: true,
parts: lineParts
});
assert.equal(_actual.output.join(''), '<span>' + expectedOutput + '</span>');
assert.deepEqual(_actual.charOffsetInPart, expectedOffsets);
});
});

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

@ -1139,6 +1139,16 @@ declare namespace vscode {
* Set to true to show a password prompt that will not show the typed value.
*/
password?: boolean;
/**
* An optional function that will be called to valide input and to give a hint
* to the user.
*
* @param value The current value of the input box.
* @return A human readable string which is presented as diagnostic message.
* Return `undefined`, `null`, or the empty string when 'value' is valid.
*/
validateInput?: (value: string) => string;
}
/**

View file

@ -23,6 +23,7 @@ import {TPromise} from 'vs/base/common/winjs.base';
import * as vscode from 'vscode';
import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers';
import {IFileService} from 'vs/platform/files/common/files';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
import {ResourceEditorInput} from 'vs/workbench/common/editor/resourceEditorInput';
import {asWinJsPromise} from 'vs/base/common/async';
@ -458,6 +459,7 @@ export class ExtHostDocumentData extends MirrorModel2 {
@Remotable.MainContext('MainThreadDocuments')
export class MainThreadDocuments {
private _modelService: IModelService;
private _modeService: IModeService;
private _textFileService: ITextFileService;
private _editorService: IWorkbenchEditorService;
private _fileService: IFileService;
@ -471,6 +473,7 @@ export class MainThreadDocuments {
constructor(
@IThreadService threadService: IThreadService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@IEventService eventService: IEventService,
@ITextFileService textFileService: ITextFileService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@ -478,6 +481,7 @@ export class MainThreadDocuments {
@IUntitledEditorService untitledEditorService: IUntitledEditorService
) {
this._modelService = modelService;
this._modeService = modeService;
this._textFileService = textFileService;
this._editorService = editorService;
this._fileService = fileService;
@ -628,8 +632,12 @@ export class MainThreadDocuments {
$registerTextContentProvider(scheme: string): void {
this._resourceContentProvider[scheme] = ResourceEditorInput.registerResourceContentProvider(scheme, {
provideTextContent: (uri: URI): TPromise<string> => {
return this._proxy.$provideTextDocumentContent(uri);
provideTextContent: (uri: URI): TPromise<EditorCommon.IModel> => {
return this._proxy.$provideTextDocumentContent(uri).then(value => {
const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
const mode = this._modeService.getOrCreateModeByFilenameOrFirstLine(uri.fsPath, firstLineText);
return this._modelService.createModel(value, mode, uri);
});
}
});
}

View file

@ -20,6 +20,7 @@ import {MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, IT
import * as TypeConverters from './extHostTypeConverters';
import {TextDocument, TextEditorSelectionChangeEvent, TextEditorOptionsChangeEvent, TextEditorOptions, ViewColumn} from 'vscode';
import {EventType} from 'vs/workbench/common/events';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IEventService} from 'vs/platform/event/common/event';
import {equals as arrayEquals} from 'vs/base/common/arrays';
@ -415,6 +416,7 @@ export class MainThreadEditors {
private _proxy: ExtHostEditors;
private _workbenchEditorService: IWorkbenchEditorService;
private _telemetryService: ITelemetryService;
private _editorTracker: MainThreadEditorsTracker;
private _toDispose: IDisposable[];
private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; };
@ -425,12 +427,14 @@ export class MainThreadEditors {
constructor(
@IThreadService threadService: IThreadService,
@IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService,
@ITelemetryService telemetryService: ITelemetryService,
@ICodeEditorService editorService: ICodeEditorService,
@IEventService eventService: IEventService,
@IModelService modelService: IModelService
) {
this._proxy = threadService.getRemotable(ExtHostEditors);
this._workbenchEditorService = workbenchEditorService;
this._telemetryService = telemetryService;
this._toDispose = [];
this._textEditorsListenersMap = Object.create(null);
this._textEditorsMap = Object.create(null);
@ -566,6 +570,9 @@ export class MainThreadEditors {
}
_tryShowEditor(id: string, position: EditorPosition): TPromise<void> {
// check how often this is used
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.show' });
let mainThreadEditor = this._textEditorsMap[id];
if (mainThreadEditor) {
let model = mainThreadEditor.getModel();
@ -577,6 +584,9 @@ export class MainThreadEditors {
}
_tryHideEditor(id: string): TPromise<void> {
// check how often this is used
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.hide' });
let mainThreadEditor = this._textEditorsMap[id];
if (mainThreadEditor) {
let editors = this._workbenchEditorService.getVisibleEditors();

View file

@ -6,7 +6,7 @@
import {TPromise} from 'vs/base/common/winjs.base';
import {Remotable, IThreadService} from 'vs/platform/thread/common/thread';
import {IQuickOpenService, IPickOpenEntry, IPickOptions} from 'vs/workbench/services/quickopen/common/quickOpenService';
import {IQuickOpenService, IPickOpenEntry, IPickOptions, IInputOptions} from 'vs/workbench/services/quickopen/common/quickOpenService';
import {QuickPickOptions, QuickPickItem, InputBoxOptions} from 'vscode';
export interface MyQuickPickItems extends IPickOpenEntry {
@ -20,6 +20,7 @@ export class ExtHostQuickOpen {
private _proxy: MainThreadQuickOpen;
private _onDidSelectItem: (handle: number) => void;
private _validateInput: (input: string) => string;
constructor(@IThreadService threadService: IThreadService) {
this._proxy = threadService.getRemotable(MainThreadQuickOpen);
@ -97,8 +98,17 @@ export class ExtHostQuickOpen {
}
}
// ---- input
input(options?: InputBoxOptions): Thenable<string> {
return this._proxy.$input(options);
this._validateInput = options.validateInput;
return this._proxy.$input(options, typeof options.validateInput === 'function');
}
$validateInput(input: string): TPromise<string> {
if (this._validateInput) {
return TPromise.as(this._validateInput(input));
}
}
}
@ -160,7 +170,25 @@ export class MainThreadQuickOpen {
}
}
$input(options?: InputBoxOptions): Thenable<string> {
return this._quickOpenService.input(options);
// ---- input
$input(options: InputBoxOptions, validateInput: boolean): Thenable<string> {
const inputOptions: IInputOptions = Object.create(null);
if (options) {
inputOptions.password = options.password;
inputOptions.placeHolder = options.placeHolder;
inputOptions.prompt = options.prompt;
inputOptions.value = options.value;
}
if (validateInput) {
inputOptions.validateInput = (value) => {
return this._proxy.$validateInput(value);
}
}
return this._quickOpenService.input(inputOptions);
}
}

View file

@ -108,7 +108,7 @@ export class ActivitybarPart extends Part implements IActivityService {
this.createViewletSwitcher($result.clone());
// Bottom Toolbar with action items for global actions
this.createGlobalToolBarArea($result.clone());
// this.createGlobalToolBarArea($result.clone()); // not used currently
return $result;
}
@ -117,9 +117,9 @@ export class ActivitybarPart extends Part implements IActivityService {
// Viewlet switcher is on top
this.viewletSwitcherBar = new ActionBar(div, {
actionItemProvider: (action: Action) => this.activityActionItems[action.id]
actionItemProvider: (action: Action) => this.activityActionItems[action.id],
disableTabIndex: true // we handle this
});
this.viewletSwitcherBar.getContainer().removeAttribute('tabindex');
this.viewletSwitcherBar.getContainer().addClass('position-top');
// Build Viewlet Actions in correct order

View file

@ -137,7 +137,7 @@ export class IFrameEditor extends BaseEditor {
private enableKeybindings(): string {
return [
'<script>',
'var ignoredKeys = [32 /* space */, 33 /* page up */, 34 /* page down */, 38 /* up */, 40 /* down */];',
'var ignoredKeys = [9 /* tab */, 32 /* space */, 33 /* page up */, 34 /* page down */, 38 /* up */, 40 /* down */];',
'var ignoredCtrlCmdKeys = [67 /* c */];',
'window.document.body.addEventListener("keydown", function(event) {', // Listen to keydown events in the iframe
' try {',

View file

@ -50,11 +50,15 @@ interface IPickOpenEntryItem extends IPickOpenEntry {
render?: (tree: ITree, container: HTMLElement, previousCleanupFn: IElementCallback) => IElementCallback;
}
export interface IInternalPickOptions extends IPickOptions {
inputMode?: boolean;
inputPrompt?: string;
inputValue?: string;
inputPassword?: boolean;
interface IInternalPickOptions {
value?: string;
placeHolder?: string;
password?: boolean;
autoFocus?: IAutoFocus;
matchOnDescription?: boolean;
matchOnDetail?: boolean;
ignoreFocusLost?: boolean;
onDidType?: (value: string) => any;
}
export class QuickOpenController extends WorkbenchComponent implements IQuickOpenService {
@ -175,29 +179,102 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
}
public input(options?: IInputOptions): TPromise<string> {
if (!options) {
options = Object.create(null);
}
return this.pick([], {
inputValue: options.value,
placeHolder: options.placeHolder,
inputPrompt: options.prompt,
inputMode: true,
inputPassword: options.password,
autoFocus: { autoFocusFirstEntry: true }
const defaultMessage = options && options.prompt
? nls.localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", options.prompt)
: nls.localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
let currentPick = defaultMessage;
let currentValidation = TPromise.as(true);
let lastValue = options && options.value;
const init = (resolve: (value: IPickOpenEntry | TPromise<IPickOpenEntry>) => any, reject: (value: any) => any) => {
// open quick pick with just one choise. we will recurse whenever
// the validation/success message changes
this.doPick(TPromise.as([{ label: currentPick }]), {
ignoreFocusLost: true,
autoFocus: { autoFocusFirstEntry: true },
password: options.password,
placeHolder: options.placeHolder,
value: options.value,
onDidType: (value) => {
lastValue = value;
if (options.validateInput) {
if (currentValidation) {
currentValidation.cancel();
}
currentValidation = TPromise.timeout(100).then(() => {
return options.validateInput(value).then(message => {
let newPick = message || defaultMessage;
if (newPick !== currentPick) {
currentPick = newPick;
resolve(new TPromise(init));
}
return !message;
})
}, err => {
// ignore
});
}
}
}).then(resolve, reject);
};
return new TPromise(init).then(item => {
return currentValidation.then(valid => {
if (valid && item) {
return lastValue;
}
});
});
}
public pick(picks: TPromise<string[]>, options?: IInternalPickOptions): TPromise<string>;
public pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IInternalPickOptions): TPromise<string>;
public pick(picks: string[], options?: IInternalPickOptions): TPromise<string>;
public pick<T extends IPickOpenEntry>(picks: T[], options?: IInternalPickOptions): TPromise<T>;
public pick(arg1: any, options?: IInternalPickOptions): TPromise<any> {
public pick(picks: TPromise<string[]>, options?: IPickOptions): TPromise<string>;
public pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions): TPromise<string>;
public pick(picks: string[], options?: IPickOptions): TPromise<string>;
public pick<T extends IPickOpenEntry>(picks: T[], options?: IPickOptions): TPromise<T>;
public pick(arg1: string[] | TPromise<string[]> | IPickOpenEntry[] | TPromise<IPickOpenEntry[]>, options?: IPickOptions): TPromise<string | IPickOpenEntry> {
if (!options) {
options = Object.create(null);
}
let arrayPromise: TPromise<string[] | IPickOpenEntry[]>;
if (Array.isArray(arg1)) {
arrayPromise = TPromise.as(arg1);
} else if (TPromise.is(arg1)) {
arrayPromise = arg1;
} else {
throw new Error('illegal input');
}
let isAboutStrings = false;
let entryPromise = arrayPromise.then(elements => {
return (<Array<string | IPickOpenEntry>>elements).map(element => {
if (typeof element === 'string') {
isAboutStrings = true;
return <IPickOpenEntry>{ label: element };
} else {
return element;
}
});
});
return new TPromise<string | IPickOpenEntry>((resolve, reject, progress) => {
function onItem(item: IPickOpenEntry): string | IPickOpenEntry {
return item && isAboutStrings ? item.label : item;
}
this.doPick(entryPromise, options).then(item => resolve(onItem(item)),
err => reject(err),
item => progress(onItem(item)));
});
}
private doPick(picksPromise: TPromise<IPickOpenEntry[]>, options: IInternalPickOptions): TPromise<IPickOpenEntry> {
let autoFocus = options.autoFocus;
// Use a generated token to avoid race conditions from long running promises
@ -232,27 +309,20 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
}
// Respect input value
if (options.inputMode && options.inputValue) {
this.pickOpenWidget.setValue(options.inputValue);
if (options.value) {
this.pickOpenWidget.setValue(options.value);
}
// Respect password
this.pickOpenWidget.setPassword(options.inputMode && options.inputPassword);
this.pickOpenWidget.setPassword(options.password);
// Layout
if (this.layoutDimensions) {
this.pickOpenWidget.layout(this.layoutDimensions);
}
// Convert arg to promise as needed
let picksPromise: TPromise<any[]> = arg1;
if (!(Promise.is(arg1))) {
picksPromise = Promise.as(arg1);
}
return new TPromise<IPickOpenEntry | string>((complete, error, progress) => {
let picksPromiseDone = false;
let userTypedValue = options.inputValue || '';
// Resolve picks
picksPromise.then((picks) => {
@ -268,9 +338,6 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
// Model
let model = new QuickOpenModel();
let entries = picks.map((e) => {
if (typeof e === 'string') {
return new PickOpenEntry(e, null, null, () => progress(e));
}
let entry = (<IPickOpenEntryItem>e);
@ -282,15 +349,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
});
if (picks.length === 0) {
if (options.inputMode) {
if (options.inputPrompt) {
entries.push(new PickOpenEntry(nls.localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", options.inputPrompt)));
} else {
entries.push(new PickOpenEntry(nls.localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel")));
}
} else {
entries.push(new PickOpenEntry(nls.localize('emptyPicks', "There are no entries to pick from")));
}
entries.push(new PickOpenEntry(nls.localize('emptyPicks', "There are no entries to pick from")));
}
model.setEntries(entries);
@ -298,12 +357,9 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
// Handlers
this.pickOpenWidget.setCallbacks({
onOk: () => {
if (options.inputMode) {
return complete(userTypedValue);
}
if (picks.length === 0) {
return null;
return complete(null);
}
let index = -1;
@ -316,9 +372,14 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
complete(picks[index] || null);
},
onCancel: () => complete(void 0),
onFocusLost: () => !!options.inputMode, // veto close on focus lost if we are in input mode
onFocusLost: () => options.ignoreFocusLost,
onType: (value: string) => {
userTypedValue = value;
// the caller takes care of all input
if (options.onDidType) {
options.onDidType(value);
return;
}
if (picks.length === 0) {
return;

View file

@ -12,15 +12,13 @@ import URI from 'vs/base/common/uri';
import {EventType} from 'vs/base/common/events';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IModelService} from 'vs/editor/common/services/modelService';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IDisposable} from 'vs/base/common/lifecycle';
/**
*
*/
export interface IResourceEditorContentProvider {
provideTextContent(resource: URI): TPromise<string>;
// onDidChange
provideTextContent(resource: URI): TPromise<IModel>;
}
/**
@ -40,7 +38,7 @@ export class ResourceEditorInput extends EditorInput {
return { dispose() { delete ResourceEditorInput.registry[scheme] } };
}
private static getOrCreateModel(modelService: IModelService, modeService: IModeService, resource: URI): TPromise<IModel> {
private static getOrCreateModel(modelService: IModelService, resource: URI): TPromise<IModel> {
const model = modelService.getModel(resource);
if (model) {
return TPromise.as(model);
@ -61,11 +59,7 @@ export class ResourceEditorInput extends EditorInput {
// twice
ResourceEditorInput.loadingModels[resource.toString()] = loadingModel = new TPromise<IModel>((resolve, reject) => {
provider.provideTextContent(resource).then(value => {
const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
const mode = modeService.getOrCreateModeByFilenameOrFirstLine(resource.fsPath, firstLineText);
return modelService.createModel(value, mode, resource);
}).then(resolve, reject);
provider.provideTextContent(resource).then(resolve, reject);
}, function() {
// no cancellation when caching promises
@ -93,7 +87,6 @@ export class ResourceEditorInput extends EditorInput {
description: string,
resource: URI,
@IModelService protected modelService: IModelService,
@IModeService protected modeService: IModeService,
@IInstantiationService protected instantiationService: IInstantiationService
) {
super();
@ -123,7 +116,7 @@ export class ResourceEditorInput extends EditorInput {
}
// Otherwise Create Model and handle dispose event
return ResourceEditorInput.getOrCreateModel(this.modelService, this.modeService, this.resource).then(() => {
return ResourceEditorInput.getOrCreateModel(this.modelService, this.resource).then(() => {
let model = this.instantiationService.createInstance(ResourceEditorModel, this.resource);
const unbind = model.addListener(EventType.DISPOSE, () => {
this.cachedModel = null; // make sure we do not dispose model again

View file

@ -7,10 +7,8 @@
*/
declare module DebugProtocol {
//---- V8 inspired protocol
/** Base class of V8 requests, responses, and events. */
export interface V8Message {
/** Base class of requests, responses, and events. */
export interface ProtocolMessage {
/** Sequence number */
seq: number;
/** One of "request", "response", or "event" */
@ -18,7 +16,7 @@ declare module DebugProtocol {
}
/** Client-initiated request */
export interface Request extends V8Message {
export interface Request extends ProtocolMessage {
/** The command to execute */
command: string;
/** Object containing arguments for the command */
@ -26,7 +24,7 @@ declare module DebugProtocol {
}
/** Server-initiated event */
export interface Event extends V8Message {
export interface Event extends ProtocolMessage {
/** Type of event */
event: string;
/** Event-specific information */
@ -34,7 +32,7 @@ declare module DebugProtocol {
}
/** Server-initiated response to client request */
export interface Response extends V8Message {
export interface Response extends ProtocolMessage {
/** Sequence number of the corresponding request */
request_seq: number;
/** Outcome of the request */
@ -110,10 +108,12 @@ declare module DebugProtocol {
*/
export interface OutputEvent extends Event {
body: {
/** The category of output (such as: 'console', 'stdout', 'stderr'). If not specified, 'console' is assumed. */
/** The category of output (such as: 'console', 'stdout', 'stderr', 'telemetry'). If not specified, 'console' is assumed. */
category?: string;
/** The output */
/** The output to report. */
output: string;
/** Optional data to report. For the 'telemetry' category the data will be sent to telemetry, for the other categories the data is shown in JSON format. */
data?: any;
};
}
@ -144,8 +144,14 @@ declare module DebugProtocol {
/** Determines in what format paths are specified. Possible values are 'path' or 'uri'. The default is 'path', which is the native format. */
pathFormat?: string;
}
/** Response to Initialize request. */
/** Response to Initialize request.
* It contains information about the features implemented by a debug adapter.
*/
export interface InitializeResponse extends Response {
/** The debug adapter supports the configurationDoneRequest. */
supportsConfigurationDoneRequest?: boolean;
/** The debug adapter supports a (side effect free) evaluate request for data hovers. */
supportEvaluateForHovers?: boolean;
}
/** ConfigurationDone request; value of command field is "configurationDone".
@ -213,9 +219,9 @@ declare module DebugProtocol {
/** The source location of the breakpoints; either source.path or source.reference must be specified. */
source: Source;
/** The code locations of the breakpoints. */
breakpoints?: SourceBreakpoint[];
/** Deprecated: The code locations of the breakpoints. */
lines?: number[];
breakpoints?: SourceBreakpoint[];
/** Deprecated: The code locations of the breakpoints. */
lines?: number[];
}
/** Response to "setBreakpoints" request.
Returned is information about each breakpoint created by this request.
@ -427,7 +433,7 @@ declare module DebugProtocol {
expression: string;
/** Evaluate the expression in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. */
frameId?: number;
/** The context in which the evaluate request is run. Possible values are 'watch' if evaluate is run in a watch or 'repl' if run from the REPL console. */
/** The context in which the evaluate request is run. Possible values are 'watch' if evaluate is run in a watch, 'repl' if run from the REPL console, or 'hover' if run from a data hover. */
context?: string;
}
/** Response to "evaluate" request. */
@ -451,9 +457,9 @@ declare module DebugProtocol {
format: string;
/** An object used as a dictionary for looking up the variables in the format string. */
variables?: { [key: string]: string };
/** if true send to telemetry (Experimental) */
/** if true send to telemetry */
sendTelemetry?: boolean;
/** if true show user (Experimental) */
/** if true show user */
showUser?: boolean;
}
@ -465,7 +471,7 @@ declare module DebugProtocol {
name: string;
}
/** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */
/** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */
export interface Source {
/** The short name of the source. Every source returned from the debug adapter has a name. When specifying a source to the debug adapter this name is optional. */
name?: string;
@ -473,7 +479,7 @@ declare module DebugProtocol {
path?: string;
/** If sourceReference > 0 the contents of the source can be retrieved through the SourceRequest. A sourceReference is only valid for a session, so it must not be used to persist a source. */
sourceReference?: number;
/** The (optional) origin of this source: possible values "internal module", "inlined content from source map", etc. */
/** The (optional) origin of this source: possible values "internal module", "inlined content from source map", etc. */
origin?: string;
/** Optional data that a debug adapter might want to loop through the client. The client should leave the data intact and persist it across sessions. The client should not interpret the data. */
adapterData?: any;
@ -515,23 +521,25 @@ declare module DebugProtocol {
variablesReference: number;
}
/** Properties of a breakpoint passed to the setBreakpoints request.
*/
export interface SourceBreakpoint {
/** The source line of the breakpoint. */
line: number;
/** An optional source column of the breakpoint. */
column?: number;
/** An optional expression for conditional breakpoints. */
condition?: string;
}
/** Properties of a breakpoint passed to the setBreakpoints request.
*/
export interface SourceBreakpoint {
/** The source line of the breakpoint. */
line: number;
/** An optional source column of the breakpoint. */
column?: number;
/** An optional expression for conditional breakpoints. */
condition?: string;
}
/** Information about a Breakpoint created in the setBreakpoints request.
*/
export interface Breakpoint {
/** If true breakpoint could be set (but not necessarily at the correct location). */
verified: boolean;
/** The actual location of the breakpoint. */
/** The actual line of the breakpoint. */
line: number;
/** The actual column of the breakpoint. */
column?: number;
}
}

View file

@ -151,14 +151,14 @@ export class FileOnDiskEditorInput extends ResourceEditorInput {
name: string,
description: string,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@IModeService private modeService: IModeService,
@IInstantiationService instantiationService: IInstantiationService,
@IFileService private fileService: IFileService
) {
// We create a new resource URI here that is different from the file resource because we represent the state of
// the file as it is on disk and not as it is (potentially cached) in Code. That allows us to have a different
// model for the left-hand comparision compared to the conflicting one in Code to the right.
super(name, description, URI.create('disk', null, fileResource.fsPath), modelService, modeService, instantiationService);
super(name, description, URI.create('disk', null, fileResource.fsPath), modelService, instantiationService);
this.fileResource = fileResource;
this.mime = mime;

View file

@ -198,7 +198,7 @@ export class CommandsHandler extends QuickOpenHandler {
}
public getResults(searchValue: string): TPromise<QuickOpenModel> {
searchValue = searchValue.trim().replace(/ /g, ''); // since we match fuzzy, remove all whitespaces upfront
searchValue = searchValue.trim();
// Workbench Actions (if prefix asks for all commands)
let workbenchEntries: CommandEntry[] = [];
@ -228,15 +228,8 @@ export class CommandsHandler extends QuickOpenHandler {
// Remove duplicates
entries = arrays.distinct(entries, (entry) => entry.getLabel() + entry.getGroupLabel());
// Sort by name if there is no search running
if (!searchValue) {
entries = entries.sort((elementA, elementB) => strings.localeCompare(elementA.getLabel().toLowerCase(), elementB.getLabel().toLowerCase()));
}
// Otherwise sort by score
else {
entries = entries.sort((elementA, elementB) => QuickOpenEntry.compareByScore(elementA, elementB, searchValue));
}
// Sort by name
entries = entries.sort((elementA, elementB) => strings.localeCompare(elementA.getLabel().toLowerCase(), elementB.getLabel().toLowerCase()));
return TPromise.as(new QuickOpenModel(entries));
}
@ -256,7 +249,7 @@ export class CommandsHandler extends QuickOpenHandler {
label = nls.localize('commandLabel', "{0}: {1}", category, label);
}
let highlights = filters.matchesFuzzy(searchValue, label, true /* separate substring matching */);
let highlights = filters.matchesFuzzy(searchValue, label);
if (highlights) {
entries.push(this.instantiationService.createInstance(CommandEntry, keys.length > 0 ? keys.join(', ') : '', label, highlights, actionDescriptor));
}
@ -281,7 +274,7 @@ export class CommandsHandler extends QuickOpenHandler {
let keys = this.keybindingService.lookupKeybindings(editorAction.id).map(k => this.keybindingService.getLabelFor(k));
if (action.label) {
let highlights = filters.matchesFuzzy(searchValue, action.label, true /* separate substring matching */);
let highlights = filters.matchesFuzzy(searchValue, action.label);
if (highlights) {
entries.push(this.instantiationService.createInstance(EditorActionCommandEntry, keys.length > 0 ? keys.join(', ') : '', action.label, highlights, action));
}
@ -296,7 +289,7 @@ export class CommandsHandler extends QuickOpenHandler {
for (let action of actions) {
let keys = this.keybindingService.lookupKeybindings(action.id).map(k => this.keybindingService.getLabelFor(k));
let highlights = filters.matchesFuzzy(searchValue, action.label, true /* separate substring matching */);
let highlights = filters.matchesFuzzy(searchValue, action.label);
if (highlights) {
entries.push(this.instantiationService.createInstance(ActionCommandEntry, keys.join(', '), action.label, highlights, action));
}

View file

@ -14,6 +14,7 @@ import scorer = require('vs/base/common/scorer');
import paths = require('vs/base/common/paths');
import filters = require('vs/base/common/filters');
import labels = require('vs/base/common/labels');
import strings = require('vs/base/common/strings');
import {IRange} from 'vs/editor/common/editorCommon';
import {ListenerUnbind} from 'vs/base/common/eventEmitter';
import {compareByPrefix} from 'vs/base/common/comparers';
@ -43,7 +44,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
private static SYMBOL_SEARCH_SUBSEQUENT_TIMEOUT = 100;
private static SEARCH_DELAY = 300; // This delay accommodates for the user typing a word and then stops typing to start searching
private static MAX_DISPLAYED_RESULTS = 1024;
private static MAX_DISPLAYED_RESULTS = 512;
private openSymbolHandler: OpenSymbolHandler;
private openFileHandler: OpenFileHandler;
@ -257,6 +258,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
// Pattern match on results and adjust highlights
let results: QuickOpenEntry[] = [];
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
for (let i = 0; i < cachedEntries.length; i++) {
let entry = cachedEntries[i];
@ -268,7 +270,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
// Check if this entry is a match for the search value
const resource = entry.getResource(); // can be null for symbol results!
let targetToMatch = resource ? labels.getPathLabel(resource, this.contextService) : entry.getLabel();
if (!filters.matchesFuzzy(searchValue, targetToMatch, true /* separate substring matching */)) {
if (!scorer.matches(targetToMatch, normalizedSearchValueLowercase)) {
continue;
}

View file

@ -61,6 +61,11 @@ export interface IInputOptions {
* set to true to show a password prompt that will not show the typed value
*/
password?: boolean;
/**
* an optional function that is used to validate user input.
*/
validateInput?: (input: string) => TPromise<string>;
}
export var IQuickOpenService = createDecorator<IQuickOpenService>('quickOpenService');

View file

@ -8,7 +8,7 @@
import fs = require('fs');
import paths = require('path');
import filters = require('vs/base/common/filters');
import scorer = require('vs/base/common/scorer');
import arrays = require('vs/base/common/arrays');
import strings = require('vs/base/common/strings');
import glob = require('vs/base/common/glob');
@ -21,6 +21,7 @@ import {ISerializedFileMatch, IRawSearch, ISearchEngine} from 'vs/workbench/serv
export class FileWalker {
private config: IRawSearch;
private filePattern: string;
private normalizedFilePatternLowercase: string;
private excludePattern: glob.IExpression;
private includePattern: glob.IExpression;
private maxResults: number;
@ -41,10 +42,12 @@ export class FileWalker {
this.resultCount = 0;
this.isLimitHit = false;
// Normalize file patterns to forward slashes
if (this.filePattern && this.filePattern.indexOf(paths.sep) >= 0) {
this.filePattern = strings.replaceAll(this.filePattern, '\\', '/');
this.searchInPath = true;
if (this.filePattern) {
if (this.filePattern.indexOf(paths.sep) >= 0) {
this.filePattern = strings.replaceAll(this.filePattern, '\\', '/'); // Normalize file patterns to forward slashes
}
this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase();
}
}
@ -78,7 +81,7 @@ export class FileWalker {
}
// File: Check for match on file pattern and include pattern
this.matchFile(onResult, paths.basename(extraFilePath), extraFilePath, extraFilePath /* no workspace relative path */);
this.matchFile(onResult, extraFilePath, extraFilePath /* no workspace relative path */);
});
}
@ -120,7 +123,7 @@ export class FileWalker {
}
private checkFilePatternRelativeMatch(basePath: string, clb: (matchPath: string) => void): void {
if (!this.filePattern || paths.isAbsolute(this.filePattern) || !this.searchInPath) {
if (!this.filePattern || paths.isAbsolute(this.filePattern)) {
return clb(null);
}
@ -194,7 +197,7 @@ export class FileWalker {
return clb(null); // ignore file if its path matches with the file pattern because checkFilePatternRelativeMatch() takes care of those
}
this.matchFile(onResult, file, currentPath, relativeFilePath);
this.matchFile(onResult, currentPath, relativeFilePath);
}
// Unwind
@ -209,8 +212,8 @@ export class FileWalker {
});
}
private matchFile(onResult: (result: ISerializedFileMatch) => void, basename: string, absolutePath: string, relativePath: string): void {
if (this.isFilePatternMatch(basename, relativePath) && (!this.includePattern || glob.match(this.includePattern, relativePath))) {
private matchFile(onResult: (result: ISerializedFileMatch) => void, absolutePath: string, relativePath: string): void {
if (this.isFilePatternMatch(relativePath) && (!this.includePattern || glob.match(this.includePattern, relativePath))) {
this.resultCount++;
if (this.maxResults && this.resultCount > this.maxResults) {
@ -225,13 +228,11 @@ export class FileWalker {
}
}
private isFilePatternMatch(name: string, path: string): boolean {
private isFilePatternMatch(path: string): boolean {
// Check for search pattern
if (this.filePattern) {
const res = filters.matchesFuzzy(this.filePattern, path, true /* separate substring matching */);
return !!res && res.length > 0;
return scorer.matches(path, this.normalizedFilePatternLowercase);
}
// No patterns means we match all

View file

@ -8,7 +8,8 @@ import {PPromise} from 'vs/base/common/winjs.base';
import uri from 'vs/base/common/uri';
import glob = require('vs/base/common/glob');
import objects = require('vs/base/common/objects');
import filters = require('vs/base/common/filters');
import scorer = require('vs/base/common/scorer');
import strings = require('vs/base/common/strings');
import {Client} from 'vs/base/node/service.cp';
import {IProgress, LineMatch, FileMatch, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchQuery, ISearchConfiguration, ISearchService} from 'vs/platform/search/common/search';
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
@ -154,8 +155,7 @@ export class SearchService implements ISearchService {
return false; // if we match on file pattern, we have to ignore non file resources
}
const res = filters.matchesFuzzy(filePattern, resource.fsPath);
if (!res || res.length === 0) {
if (!scorer.matches(resource.fsPath, strings.stripWildcards(filePattern).toLowerCase())) {
return false;
}
}