Merge remote-tracking branch 'origin/master' into enable-tslint
This commit is contained in:
commit
ef3f9d9d88
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"account": "monacobuild",
|
||||
"container": "debuggers",
|
||||
"zip": "5fdb54d/node-debug.zip",
|
||||
"zip": "17ccd91/node-debug.zip",
|
||||
"output": ""
|
||||
}
|
||||
|
|
|
@ -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
6
npm-shrinkwrap.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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'));
|
||||
});
|
||||
});
|
|
@ -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 === ' ' ? ' ' : character);
|
||||
let testString = (character === ' ' ? ' ' : 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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('<');
|
||||
// } else if (charCode === _greaterThan) {
|
||||
// output.push('>');
|
||||
// } else if (charCode === _ampersand) {
|
||||
// output.push('&');
|
||||
// } 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;
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
723
src/vs/editor/browser/controller/textAreaHandler.ts
Normal file
723
src/vs/editor/browser/controller/textAreaHandler.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 ? '→' : ' ');
|
||||
insertSpacesCount--;
|
||||
}
|
||||
while (insertSpacesCount > 0) {
|
||||
result.output.push(' ');
|
||||
insertSpacesCount--;
|
||||
}
|
||||
break;
|
||||
|
||||
case _space:
|
||||
result.output.push(renderWhitespace ? '·' : ' ');
|
||||
break;
|
||||
|
||||
case _lowerThan:
|
||||
result.output.push('<');
|
||||
break;
|
||||
|
||||
case _greaterThan:
|
||||
result.output.push('>');
|
||||
break;
|
||||
|
||||
case _ampersand:
|
||||
result.output.push('&');
|
||||
break;
|
||||
|
||||
case 0:
|
||||
result.output.push('�');
|
||||
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('​');
|
||||
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('…</span>');
|
||||
}
|
||||
} else {
|
||||
// This is basically for IE's hit test to work
|
||||
result.output.push('<span> </span>');
|
||||
}
|
||||
result.output.push('</span>');
|
||||
|
||||
result.partsCount = partsCount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
157
src/vs/editor/common/viewLayout/viewLineRenderer.ts
Normal file
157
src/vs/editor/common/viewLayout/viewLineRenderer.ts
Normal 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> </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 ? '→' : ' ');
|
||||
insertSpacesCount--;
|
||||
}
|
||||
while (insertSpacesCount > 0) {
|
||||
out.push(' ');
|
||||
insertSpacesCount--;
|
||||
}
|
||||
break;
|
||||
|
||||
case _space:
|
||||
out.push(partRendersWhitespace ? '·' : ' ');
|
||||
break;
|
||||
|
||||
case _lowerThan:
|
||||
out.push('<');
|
||||
break;
|
||||
|
||||
case _greaterThan:
|
||||
out.push('>');
|
||||
break;
|
||||
|
||||
case _ampersand:
|
||||
out.push('&');
|
||||
break;
|
||||
|
||||
case 0:
|
||||
out.push('�');
|
||||
break;
|
||||
|
||||
case _bom:
|
||||
case _lineSeparator:
|
||||
out.push('\ufffd');
|
||||
break;
|
||||
|
||||
case _carriageReturn:
|
||||
// zero width space, because carriage return would introduce a line break
|
||||
out.push('​');
|
||||
break;
|
||||
|
||||
default:
|
||||
out.push(lineText.charAt(charIndex));
|
||||
}
|
||||
|
||||
charOffsetInPart ++;
|
||||
|
||||
if (charIndex >= charBreakIndex) {
|
||||
out.push('…</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
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
189
src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts
Normal file
189
src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts
Normal 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, ' ', [0, 1]);
|
||||
assertCharacterReplacement(' ', 4, ' ', [0, 1, 2]);
|
||||
assertCharacterReplacement('a b', 4, 'a b', [0, 1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test('escapes HTML markup', () => {
|
||||
assertCharacterReplacement('a<b', 4, 'a<b', [0, 1, 2, 3]);
|
||||
assertCharacterReplacement('a>b', 4, 'a>b', [0, 1, 2, 3]);
|
||||
assertCharacterReplacement('a&b', 4, 'a&b', [0, 1, 2, 3]);
|
||||
});
|
||||
|
||||
test('replaces some bad characters', () => {
|
||||
assertCharacterReplacement('a\0b', 4, 'a�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​b', [0, 1, 2, 3]);
|
||||
});
|
||||
|
||||
test('handles tabs', () => {
|
||||
assertCharacterReplacement('\t', 4, ' ', [0, 4]);
|
||||
assertCharacterReplacement('x\t', 4, 'x ', [0, 1, 4]);
|
||||
assertCharacterReplacement('xx\t', 4, 'xx ', [0, 1, 2, 4]);
|
||||
assertCharacterReplacement('xxx\t', 4, 'xxx ', [0, 1, 2, 3, 4]);
|
||||
assertCharacterReplacement('xxxx\t', 4, 'xxxx ', [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> </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"> …</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">→ ····</span>',
|
||||
'<span class="token block declaration meta modifier object storage ts">export</span>',
|
||||
'<span class="token block declaration meta object ts"> </span>',
|
||||
'<span class="token block declaration meta object storage type ts">class</span>',
|
||||
'<span class="token block declaration meta object ts"> </span>',
|
||||
'<span class="token block class declaration entity meta name object ts">Game</span>',
|
||||
'<span class="token block declaration meta object ts"> </span>',
|
||||
'<span class="token delimiter curly typescript">{</span>',
|
||||
'<span class="token block body declaration meta object ts"> </span>',
|
||||
'<span class="token block body comment declaration line meta object ts">// </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">·····</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
10
src/vs/vscode.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue