Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -12,6 +12,7 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorAction, EditorExtensionsRegistry, IEditorContributionCtor } from 'vs/editor/browser/editorExtensions';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INotificationService } from 'vs/platform/notification/common/notification';
export class CodeEditor extends CodeEditorWidget {
@@ -22,9 +23,10 @@ export class CodeEditor extends CodeEditorWidget {
@ICodeEditorService codeEditorService: ICodeEditorService,
@ICommandService commandService: ICommandService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@INotificationService notificationService: INotificationService
) {
super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, themeService);
super(domElement, options, false, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService);
}
protected _getContributions(): IEditorContributionCtor[] {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import * as browser from 'vs/base/browser/browser';
@@ -102,7 +102,7 @@ class CSSBasedConfiguration extends Disposable {
private _evictUntrustedReadingsTimeout: number;
private _onDidChange = this._register(new Emitter<void>());
public onDidChange: Event<void> = this._onDidChange.event;
public readonly onDidChange: Event<void> = this._onDidChange.event;
constructor() {
super();
@@ -276,8 +276,21 @@ class CSSBasedConfiguration extends Disposable {
export class Configuration extends CommonEditorConfiguration {
private static _massageFontFamily(fontFamily: string): string {
if (/[,"']/.test(fontFamily)) {
// Looks like the font family might be already escaped
return fontFamily;
}
if (/[+ ]/.test(fontFamily)) {
// Wrap a font family using + or <space> with quotes
return `"${fontFamily}"`;
}
return fontFamily;
}
public static applyFontInfoSlow(domNode: HTMLElement, fontInfo: BareFontInfo): void {
domNode.style.fontFamily = fontInfo.fontFamily;
domNode.style.fontFamily = Configuration._massageFontFamily(fontInfo.fontFamily);
domNode.style.fontWeight = fontInfo.fontWeight;
domNode.style.fontSize = fontInfo.fontSize + 'px';
domNode.style.lineHeight = fontInfo.lineHeight + 'px';
@@ -285,7 +298,7 @@ export class Configuration extends CommonEditorConfiguration {
}
public static applyFontInfo(domNode: FastDomNode<HTMLElement>, fontInfo: BareFontInfo): void {
domNode.setFontFamily(fontInfo.fontFamily);
domNode.setFontFamily(Configuration._massageFontFamily(fontInfo.fontFamily));
domNode.setFontWeight(fontInfo.fontWeight);
domNode.setFontSize(fontInfo.fontSize);
domNode.setLineHeight(fontInfo.lineHeight);
@@ -349,7 +362,7 @@ export class Configuration extends CommonEditorConfiguration {
extraEditorClassName: this._getExtraEditorClassName(),
outerWidth: this._elementSizeObserver.getWidth(),
outerHeight: this._elementSizeObserver.getHeight(),
emptySelectionClipboard: browser.isWebKit,
emptySelectionClipboard: browser.isWebKit || browser.isFirefox,
pixelRatio: browser.getPixelRatio(),
zoomLevel: browser.getZoomLevel(),
accessibilitySupport: browser.getAccessibilitySupport()

View File

@@ -325,7 +325,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.LeftArrow,
linux: { primary: 0 }
}
@@ -344,7 +344,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.RightArrow,
linux: { primary: 0 }
}
@@ -376,7 +376,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.UpArrow,
linux: { primary: 0 }
}
@@ -388,7 +388,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.PageUp,
linux: { primary: 0 }
}
@@ -414,7 +414,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.DownArrow,
linux: { primary: 0 }
}
@@ -426,7 +426,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.PageDown,
linux: { primary: 0 }
}
@@ -502,7 +502,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.LeftArrow,
mac: { primary: KeyCode.LeftArrow, secondary: [KeyMod.WinCtrl | KeyCode.KEY_B] }
}
@@ -519,7 +519,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.LeftArrow
}
}));
@@ -535,7 +535,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.RightArrow,
mac: { primary: KeyCode.RightArrow, secondary: [KeyMod.WinCtrl | KeyCode.KEY_F] }
}
@@ -552,7 +552,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.RightArrow
}
}));
@@ -568,7 +568,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.UpArrow,
mac: { primary: KeyCode.UpArrow, secondary: [KeyMod.WinCtrl | KeyCode.KEY_P] }
}
@@ -585,7 +585,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.UpArrow,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow],
mac: { primary: KeyMod.Shift | KeyCode.UpArrow },
@@ -604,7 +604,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.PageUp
}
}));
@@ -620,7 +620,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.PageUp
}
}));
@@ -636,7 +636,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.DownArrow,
mac: { primary: KeyCode.DownArrow, secondary: [KeyMod.WinCtrl | KeyCode.KEY_N] }
}
@@ -653,7 +653,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.DownArrow,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow],
mac: { primary: KeyMod.Shift | KeyCode.DownArrow },
@@ -672,7 +672,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.PageDown
}
}));
@@ -688,7 +688,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.PageDown
}
}));
@@ -813,7 +813,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Home,
mac: { primary: KeyCode.Home, secondary: [KeyMod.CtrlCmd | KeyCode.LeftArrow] }
}
@@ -825,7 +825,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.Home,
mac: { primary: KeyMod.Shift | KeyCode.Home, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow] }
}
@@ -838,7 +838,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_A }
}
@@ -892,7 +892,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.End,
mac: { primary: KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyCode.RightArrow] }
}
@@ -904,7 +904,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.End,
mac: { primary: KeyMod.Shift | KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow] }
}
@@ -917,7 +917,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_E }
}
@@ -972,7 +972,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.Home,
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }
}
@@ -984,7 +984,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Home,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }
}
@@ -1016,7 +1016,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.End,
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }
}
@@ -1028,7 +1028,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.End,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }
}
@@ -1112,7 +1112,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
mac: { primary: KeyMod.WinCtrl | KeyCode.PageUp }
}
@@ -1137,7 +1137,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.PageUp,
win: { primary: KeyMod.Alt | KeyCode.PageUp },
linux: { primary: KeyMod.Alt | KeyCode.PageUp }
@@ -1163,7 +1163,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
mac: { primary: KeyMod.WinCtrl | KeyCode.PageDown }
}
@@ -1188,7 +1188,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.PageDown,
win: { primary: KeyMod.Alt | KeyCode.PageDown },
linux: { primary: KeyMod.Alt | KeyCode.PageDown }
@@ -1351,7 +1351,7 @@ export namespace CoreNavigationCommands {
precondition: null,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_I
}
});
@@ -1376,7 +1376,7 @@ export namespace CoreNavigationCommands {
precondition: EditorContextKeys.hasNonEmptySelection,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape]
}
@@ -1403,7 +1403,7 @@ export namespace CoreNavigationCommands {
precondition: EditorContextKeys.hasMultipleSelections,
kbOpts: {
weight: CORE_WEIGHT + 1,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape]
}
@@ -1521,7 +1521,7 @@ export namespace CoreEditingCommands {
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: null,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_O }
}
@@ -1542,7 +1542,7 @@ export namespace CoreEditingCommands {
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: ContextKeyExpr.and(
EditorContextKeys.textFocus,
EditorContextKeys.editorTextFocus,
EditorContextKeys.tabDoesNotMoveFocus
),
primary: KeyMod.Shift | KeyCode.Tab
@@ -1565,7 +1565,7 @@ export namespace CoreEditingCommands {
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: ContextKeyExpr.and(
EditorContextKeys.textFocus,
EditorContextKeys.editorTextFocus,
EditorContextKeys.tabDoesNotMoveFocus
),
primary: KeyCode.Tab
@@ -1587,7 +1587,7 @@ export namespace CoreEditingCommands {
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Backspace,
secondary: [KeyMod.Shift | KeyCode.Backspace],
mac: { primary: KeyCode.Backspace, secondary: [KeyMod.Shift | KeyCode.Backspace, KeyMod.WinCtrl | KeyCode.KEY_H, KeyMod.WinCtrl | KeyCode.Backspace] }
@@ -1613,7 +1613,7 @@ export namespace CoreEditingCommands {
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Delete,
mac: { primary: KeyCode.Delete, secondary: [KeyMod.WinCtrl | KeyCode.KEY_D, KeyMod.WinCtrl | KeyCode.Delete] }
}
@@ -1743,7 +1743,7 @@ registerCommand(new EditorOrNativeTextInputCommand({
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z
}
}));
@@ -1756,7 +1756,7 @@ registerCommand(new EditorOrNativeTextInputCommand({
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Y,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z],
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }

View File

@@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler';
import { MouseTarget, MouseTargetFactory, IViewZoneData } from 'vs/editor/browser/controller/mouseTarget';
import { MouseTarget, MouseTargetFactory, IViewZoneData, HitTestContext } from 'vs/editor/browser/controller/mouseTarget';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { TimeoutTimer, RunOnceScheduler } from 'vs/base/common/async';
import { ViewContext } from 'vs/editor/common/view/viewContext';
@@ -220,8 +220,8 @@ export class MouseHandler extends ViewEventHandler {
let targetIsViewZone = (t.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || t.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE);
let targetIsWidget = (t.type === editorBrowser.MouseTargetType.CONTENT_WIDGET);
let shouldHandle = e.leftButton;
if (platform.isMacintosh && e.ctrlKey) {
let shouldHandle = e.leftButton || e.middleButton;
if (platform.isMacintosh && e.leftButton && e.ctrlKey) {
shouldHandle = false;
}
@@ -334,6 +334,7 @@ class MouseDownOperation extends Disposable {
this._lastMouseEvent = e;
this._mouseState.setStartedOnLineNumbers(targetType === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS);
this._mouseState.setStartButtons(e);
this._mouseState.setModifiers(e);
let position = this._findMousePosition(e, true);
if (!position) {
@@ -423,12 +424,30 @@ class MouseDownOperation extends Disposable {
const mouseColumn = this._getMouseColumn(e);
if (e.posy < editorContent.y) {
let aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(Math.max(viewLayout.getCurrentScrollTop() - (editorContent.y - e.posy), 0));
const verticalOffset = Math.max(viewLayout.getCurrentScrollTop() - (editorContent.y - e.posy), 0);
const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);
if (viewZoneData) {
const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);
if (newPosition) {
return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition);
}
}
let aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);
return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(aboveLineNumber, 1));
}
if (e.posy > editorContent.y + editorContent.height) {
let belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y));
const verticalOffset = viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y);
const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);
if (viewZoneData) {
const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);
if (newPosition) {
return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition);
}
}
let belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);
return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber)));
}
@@ -458,24 +477,31 @@ class MouseDownOperation extends Disposable {
}
if (t.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || t.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE) {
// Force position on view zones to go above or below depending on where selection started from
let selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn);
let viewZoneData = <IViewZoneData>t.detail;
let positionBefore = viewZoneData.positionBefore;
let positionAfter = viewZoneData.positionAfter;
if (positionBefore && positionAfter) {
if (positionBefore.isBefore(selectionStart)) {
return new MouseTarget(t.element, t.type, t.mouseColumn, positionBefore, null, t.detail);
} else {
return new MouseTarget(t.element, t.type, t.mouseColumn, positionAfter, null, t.detail);
}
const newPosition = this._helpPositionJumpOverViewZone(<IViewZoneData>t.detail);
if (newPosition) {
return new MouseTarget(t.element, t.type, t.mouseColumn, newPosition, null, t.detail);
}
}
return t;
}
private _helpPositionJumpOverViewZone(viewZoneData: IViewZoneData): Position {
// Force position on view zones to go above or below depending on where selection started from
let selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn);
let positionBefore = viewZoneData.positionBefore;
let positionAfter = viewZoneData.positionAfter;
if (positionBefore && positionAfter) {
if (positionBefore.isBefore(selectionStart)) {
return positionBefore;
} else {
return positionAfter;
}
}
return null;
}
private _dispatchMouse(position: MouseTarget, inSelectionMode: boolean): void {
this._viewController.dispatchMouse({
position: position.position,
@@ -488,6 +514,9 @@ class MouseDownOperation extends Disposable {
ctrlKey: this._mouseState.ctrlKey,
metaKey: this._mouseState.metaKey,
shiftKey: this._mouseState.shiftKey,
leftButton: this._mouseState.leftButton,
middleButton: this._mouseState.middleButton,
});
}
}
@@ -508,6 +537,12 @@ class MouseDownState {
private _shiftKey: boolean;
public get shiftKey(): boolean { return this._shiftKey; }
private _leftButton: boolean;
public get leftButton(): boolean { return this._leftButton; }
private _middleButton: boolean;
public get middleButton(): boolean { return this._middleButton; }
private _startedOnLineNumbers: boolean;
public get startedOnLineNumbers(): boolean { return this._startedOnLineNumbers; }
@@ -522,6 +557,8 @@ class MouseDownState {
this._ctrlKey = false;
this._metaKey = false;
this._shiftKey = false;
this._leftButton = false;
this._middleButton = false;
this._startedOnLineNumbers = false;
this._lastMouseDownPosition = null;
this._lastMouseDownPositionEqualCount = 0;
@@ -541,6 +578,11 @@ class MouseDownState {
this._shiftKey = source.shiftKey;
}
public setStartButtons(source: EditorMouseEvent) {
this._leftButton = source.leftButton;
this._middleButton = source.middleButton;
}
public setStartedOnLineNumbers(startedOnLineNumbers: boolean): void {
this._startedOnLineNumbers = startedOnLineNumbers;
}

View File

@@ -16,6 +16,7 @@ import { PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPa
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions';
import { ViewLine } from 'vs/editor/browser/viewParts/lines/viewLine';
import { HorizontalRange } from 'vs/editor/common/view/renderingContext';
export interface IViewZoneData {
viewZoneId: number;
@@ -34,6 +35,7 @@ export interface IMarginData {
export interface IEmptyContentData {
isAfterLines: boolean;
horizontalDistanceToText?: number;
}
interface IETextRange {
@@ -174,6 +176,14 @@ class ElementPath {
);
}
public static isStrictChildOfViewLines(path: Uint8Array): boolean {
return (
path.length > 4
&& path[0] === PartFingerprint.OverflowGuard
&& path[3] === PartFingerprint.ViewLines
);
}
public static isChildOfScrollableElement(path: Uint8Array): boolean {
return (
path.length >= 2
@@ -214,7 +224,7 @@ class ElementPath {
}
}
class HitTestContext {
export class HitTestContext {
public readonly model: IViewModel;
public readonly layoutInfo: EditorLayoutInfo;
@@ -238,12 +248,16 @@ class HitTestContext {
}
public getZoneAtCoord(mouseVerticalOffset: number): IViewZoneData {
return HitTestContext.getZoneAtCoord(this._context, mouseVerticalOffset);
}
public static getZoneAtCoord(context: ViewContext, mouseVerticalOffset: number): IViewZoneData {
// The target is either a view zone or the empty space after the last view-line
let viewZoneWhitespace = this._context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset);
let viewZoneWhitespace = context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset);
if (viewZoneWhitespace) {
let viewZoneMiddle = viewZoneWhitespace.verticalOffset + viewZoneWhitespace.height / 2,
lineCount = this._context.model.getLineCount(),
lineCount = context.model.getLineCount(),
positionBefore: Position = null,
position: Position,
positionAfter: Position = null;
@@ -254,7 +268,7 @@ class HitTestContext {
}
if (viewZoneWhitespace.afterLineNumber > 0) {
// There are more lines above this view zone
positionBefore = new Position(viewZoneWhitespace.afterLineNumber, this._context.model.getLineMaxColumn(viewZoneWhitespace.afterLineNumber));
positionBefore = new Position(viewZoneWhitespace.afterLineNumber, context.model.getLineMaxColumn(viewZoneWhitespace.afterLineNumber));
}
if (positionAfter === null) {
@@ -330,7 +344,7 @@ class HitTestContext {
return this._viewHelper.getLineWidth(lineNumber);
}
public visibleRangeForPosition2(lineNumber: number, column: number) {
public visibleRangeForPosition2(lineNumber: number, column: number): HorizontalRange {
return this._viewHelper.visibleRangeForPosition2(lineNumber, column);
}
@@ -402,7 +416,13 @@ class HitTestRequest extends BareHitTestRequest {
}
const EMPTY_CONTENT_AFTER_LINES: IEmptyContentData = { isAfterLines: true };
const EMPTY_CONTENT_IN_LINES: IEmptyContentData = { isAfterLines: false };
function createEmptyContentDataInLines(horizontalDistanceToText: number): IEmptyContentData {
return {
isAfterLines: false,
horizontalDistanceToText: horizontalDistanceToText
};
}
export class MouseTargetFactory {
@@ -621,6 +641,17 @@ export class MouseTargetFactory {
}
if (domHitTestExecuted) {
// Check if we are hitting a view-line (can happen in the case of inline decorations on empty lines)
// See https://github.com/Microsoft/vscode/issues/46942
if (ElementPath.isStrictChildOfViewLines(request.targetPath)) {
const lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
if (ctx.model.getLineLength(lineNumber) === 0) {
const lineWidth = ctx.getLineWidth(lineNumber);
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, 1), void 0, detail);
}
}
// We have already executed hit test...
return request.fulfill(MouseTargetType.UNKNOWN);
}
@@ -691,9 +722,11 @@ export class MouseTargetFactory {
if (request.mouseContentHorizontalOffset > lineWidth) {
if (browser.isEdge && pos.column === 1) {
// See https://github.com/Microsoft/vscode/issues/10875
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber)), void 0, EMPTY_CONTENT_IN_LINES);
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber)), void 0, detail);
}
return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, void 0, EMPTY_CONTENT_IN_LINES);
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, void 0, detail);
}
const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column);

View File

@@ -228,7 +228,7 @@ export class PointerHandler implements IDisposable {
this.handler = new MsPointerHandler(context, viewController, viewHelper);
} else if ((<any>window).TouchEvent) {
this.handler = new TouchHandler(context, viewController, viewHelper);
} else if (window.navigator.pointerEnabled) {
} else if (window.navigator.pointerEnabled || (<any>window).PointerEvent) {
this.handler = new StandardPointerHandler(context, viewController, viewHelper);
} else {
this.handler = new MouseHandler(context, viewController, viewHelper);

View File

@@ -27,6 +27,7 @@ import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/line
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { getMapForWordSeparators, WordCharacterClass } from 'vs/editor/common/controller/wordCharacterClassifier';
export interface ITextAreaHandlerHelper {
visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalRange;
@@ -163,7 +164,7 @@ export class TextAreaHandler extends ViewPart {
const textAreaInputHost: ITextAreaInputHost = {
getPlainTextToCopy: (): string => {
const rawWhatToCopy = this._context.model.getPlainTextToCopy(this._selections, this._emptySelectionClipboard);
const rawWhatToCopy = this._context.model.getPlainTextToCopy(this._selections, this._emptySelectionClipboard, platform.isWindows);
const newLineCharacter = this._context.model.getEOL();
const isFromEmptySelection = (this._emptySelectionClipboard && this._selections.length === 1 && this._selections[0].isEmpty());
@@ -203,16 +204,19 @@ export class TextAreaHandler extends ViewPart {
if (this._accessibilitySupport === platform.AccessibilitySupport.Disabled) {
// We know for a fact that a screen reader is not attached
// On OSX, we write the character before the cursor to allow for "long-press" composition
// Also on OSX, we write the word before the cursor to allow for the Accessibility Keyboard to give good hints
if (platform.isMacintosh) {
const selection = this._selections[0];
if (selection.isEmpty()) {
const position = selection.getStartPosition();
if (position.column > 1) {
const lineContent = this._context.model.getLineContent(position.lineNumber);
const charBefore = lineContent.charAt(position.column - 2);
if (!strings.isHighSurrogate(charBefore.charCodeAt(0))) {
return new TextAreaState(charBefore, 1, 1, position, position);
}
let textBefore = this._getWordBeforePosition(position);
if (textBefore.length === 0) {
textBefore = this._getCharacterBeforePosition(position);
}
if (textBefore.length > 0) {
return new TextAreaState(textBefore, textBefore.length, textBefore.length, position, position);
}
}
}
@@ -328,6 +332,35 @@ export class TextAreaHandler extends ViewPart {
super.dispose();
}
private _getWordBeforePosition(position: Position): string {
const lineContent = this._context.model.getLineContent(position.lineNumber);
const wordSeparators = getMapForWordSeparators(this._context.configuration.editor.wordSeparators);
let column = position.column;
let distance = 0;
while (column > 1) {
const charCode = lineContent.charCodeAt(column - 2);
const charClass = wordSeparators.get(charCode);
if (charClass !== WordCharacterClass.Regular || distance > 50) {
return lineContent.substring(column - 1, position.column - 1);
}
distance++;
column--;
}
return lineContent.substring(0, position.column - 1);
}
private _getCharacterBeforePosition(position: Position): string {
if (position.column > 1) {
const lineContent = this._context.model.getLineContent(position.lineNumber);
const charBefore = lineContent.charAt(position.column - 2);
if (!strings.isHighSurrogate(charBefore.charCodeAt(0))) {
return charBefore;
}
}
return '';
}
// --- begin event handlers
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
@@ -509,7 +542,7 @@ export class TextAreaHandler extends ViewPart {
tac.setHeight(1);
if (this._context.configuration.editor.viewInfo.glyphMargin) {
tac.setClassName('monaco-editor-background textAreaCover ' + Margin.CLASS_NAME);
tac.setClassName('monaco-editor-background textAreaCover ' + Margin.OUTER_CLASS_NAME);
} else {
if (this._context.configuration.editor.viewInfo.renderLineNumbers !== RenderLineNumbersType.Off) {
tac.setClassName('monaco-editor-background textAreaCover ' + LineNumbersOverlay.CLASS_NAME);

View File

@@ -8,7 +8,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import * as strings from 'vs/base/common/strings';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
import { ITypeData, TextAreaState, ITextAreaWrapper } from 'vs/editor/browser/controller/textAreaState';
@@ -42,6 +42,19 @@ export interface ITextAreaInputHost {
deduceModelPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position;
}
const enum TextAreaInputEventType {
none,
compositionstart,
compositionupdate,
compositionend,
input,
cut,
copy,
paste,
focus,
blur
}
/**
* Writes screen reader content to the textarea and is able to analyze its input events to generate:
* - onCut
@@ -53,42 +66,43 @@ export interface ITextAreaInputHost {
export class TextAreaInput extends Disposable {
private _onFocus = this._register(new Emitter<void>());
public onFocus: Event<void> = this._onFocus.event;
public readonly onFocus: Event<void> = this._onFocus.event;
private _onBlur = this._register(new Emitter<void>());
public onBlur: Event<void> = this._onBlur.event;
public readonly onBlur: Event<void> = this._onBlur.event;
private _onKeyDown = this._register(new Emitter<IKeyboardEvent>());
public onKeyDown: Event<IKeyboardEvent> = this._onKeyDown.event;
public readonly onKeyDown: Event<IKeyboardEvent> = this._onKeyDown.event;
private _onKeyUp = this._register(new Emitter<IKeyboardEvent>());
public onKeyUp: Event<IKeyboardEvent> = this._onKeyUp.event;
public readonly onKeyUp: Event<IKeyboardEvent> = this._onKeyUp.event;
private _onCut = this._register(new Emitter<void>());
public onCut: Event<void> = this._onCut.event;
public readonly onCut: Event<void> = this._onCut.event;
private _onPaste = this._register(new Emitter<IPasteData>());
public onPaste: Event<IPasteData> = this._onPaste.event;
public readonly onPaste: Event<IPasteData> = this._onPaste.event;
private _onType = this._register(new Emitter<ITypeData>());
public onType: Event<ITypeData> = this._onType.event;
public readonly onType: Event<ITypeData> = this._onType.event;
private _onCompositionStart = this._register(new Emitter<void>());
public onCompositionStart: Event<void> = this._onCompositionStart.event;
public readonly onCompositionStart: Event<void> = this._onCompositionStart.event;
private _onCompositionUpdate = this._register(new Emitter<ICompositionData>());
public onCompositionUpdate: Event<ICompositionData> = this._onCompositionUpdate.event;
public readonly onCompositionUpdate: Event<ICompositionData> = this._onCompositionUpdate.event;
private _onCompositionEnd = this._register(new Emitter<void>());
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
public readonly onCompositionEnd: Event<void> = this._onCompositionEnd.event;
private _onSelectionChangeRequest = this._register(new Emitter<Selection>());
public onSelectionChangeRequest: Event<Selection> = this._onSelectionChangeRequest.event;
public readonly onSelectionChangeRequest: Event<Selection> = this._onSelectionChangeRequest.event;
// ---
private readonly _host: ITextAreaInputHost;
private readonly _textArea: TextAreaWrapper;
private _lastTextAreaEvent: TextAreaInputEventType;
private readonly _asyncTriggerCut: RunOnceScheduler;
private _textAreaState: TextAreaState;
@@ -101,6 +115,7 @@ export class TextAreaInput extends Disposable {
super();
this._host = host;
this._textArea = this._register(new TextAreaWrapper(textArea));
this._lastTextAreaEvent = TextAreaInputEventType.none;
this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0));
this._textAreaState = TextAreaState.EMPTY;
@@ -129,6 +144,8 @@ export class TextAreaInput extends Disposable {
}));
this._register(dom.addDisposableListener(textArea.domNode, 'compositionstart', (e: CompositionEvent) => {
this._lastTextAreaEvent = TextAreaInputEventType.compositionstart;
if (this._isDoingComposition) {
return;
}
@@ -145,10 +162,10 @@ export class TextAreaInput extends Disposable {
/**
* Deduce the typed input from a text area's value and the last observed state.
*/
const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean): [TextAreaState, ITypeData] => {
const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean): [TextAreaState, ITypeData] => {
const oldState = this._textAreaState;
const newState = this._textAreaState.readFromTextArea(this._textArea);
return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput)];
const newState = TextAreaState.readFromTextArea(this._textArea);
return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput, couldBeTypingAtOffset0)];
};
/**
@@ -164,7 +181,29 @@ export class TextAreaInput extends Disposable {
return [newState, typeInput];
};
const compositionDataInValid = (locale: string): boolean => {
// https://github.com/Microsoft/monaco-editor/issues/339
// Multi-part Japanese compositions reset cursor in Edge/IE, Chinese and Korean IME don't have this issue.
// The reason that we can't use this path for all CJK IME is IE and Edge behave differently when handling Korean IME,
// which breaks this path of code.
if (browser.isEdgeOrIE && locale === 'ja') {
return true;
}
// https://github.com/Microsoft/monaco-editor/issues/545
// On IE11, we can't trust composition data when typing Chinese as IE11 doesn't emit correct
// events when users type numbers in IME.
// Chinese: zh-Hans-CN, zh-Hans-SG, zh-Hant-TW, zh-Hant-HK
if (browser.isIE && locale.indexOf('zh-Han') === 0) {
return true;
}
return false;
};
this._register(dom.addDisposableListener(textArea.domNode, 'compositionupdate', (e: CompositionEvent) => {
this._lastTextAreaEvent = TextAreaInputEventType.compositionupdate;
if (browser.isChromev56) {
// See https://github.com/Microsoft/monaco-editor/issues/320
// where compositionupdate .data is broken in Chrome v55 and v56
@@ -174,12 +213,8 @@ export class TextAreaInput extends Disposable {
return;
}
if (browser.isEdgeOrIE && e.locale === 'ja') {
// https://github.com/Microsoft/monaco-editor/issues/339
// Multi-part Japanese compositions reset cursor in Edge/IE, Chinese and Korean IME don't have this issue.
// The reason that we can't use this path for all CJK IME is IE and Edge behave differently when handling Korean IME,
// which breaks this path of code.
const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false);
if (compositionDataInValid(e.locale)) {
const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false);
this._textAreaState = newState;
this._onType.fire(typeInput);
this._onCompositionUpdate.fire(e);
@@ -193,13 +228,14 @@ export class TextAreaInput extends Disposable {
}));
this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => {
if (browser.isEdgeOrIE && e.locale === 'ja') {
this._lastTextAreaEvent = TextAreaInputEventType.compositionend;
if (compositionDataInValid(e.locale)) {
// https://github.com/Microsoft/monaco-editor/issues/339
const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false);
const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false);
this._textAreaState = newState;
this._onType.fire(typeInput);
}
else {
} else {
const [newState, typeInput] = deduceComposition(e.data);
this._textAreaState = newState;
this._onType.fire(typeInput);
@@ -208,7 +244,7 @@ export class TextAreaInput extends Disposable {
// Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome (the textarea is not updated correctly when composition ends)
// we cannot assume the text at the end consists only of the composited text
if (browser.isEdgeOrIE || browser.isChrome) {
this._textAreaState = this._textAreaState.readFromTextArea(this._textArea);
this._textAreaState = TextAreaState.readFromTextArea(this._textArea);
}
if (!this._isDoingComposition) {
@@ -220,6 +256,10 @@ export class TextAreaInput extends Disposable {
}));
this._register(dom.addDisposableListener(textArea.domNode, 'input', () => {
// We want to find out if this is the first `input` after a `focus`.
const previousEventWasFocus = (this._lastTextAreaEvent === TextAreaInputEventType.focus);
this._lastTextAreaEvent = TextAreaInputEventType.input;
// Pretend here we touched the text area, as the `input` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received input event');
@@ -239,7 +279,7 @@ export class TextAreaInput extends Disposable {
return;
}
const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/platform.isMacintosh);
const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/platform.isMacintosh, /*couldBeTypingAtOffset0*/previousEventWasFocus && platform.isMacintosh);
if (typeInput.replaceCharCnt === 0 && typeInput.text.length === 1 && strings.isHighSurrogate(typeInput.text.charCodeAt(0))) {
// Ignore invalid input but keep it around for next time
return;
@@ -264,6 +304,8 @@ export class TextAreaInput extends Disposable {
// --- Clipboard operations
this._register(dom.addDisposableListener(textArea.domNode, 'cut', (e: ClipboardEvent) => {
this._lastTextAreaEvent = TextAreaInputEventType.cut;
// Pretend here we touched the text area, as the `cut` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received cut event');
@@ -273,10 +315,14 @@ export class TextAreaInput extends Disposable {
}));
this._register(dom.addDisposableListener(textArea.domNode, 'copy', (e: ClipboardEvent) => {
this._lastTextAreaEvent = TextAreaInputEventType.copy;
this._ensureClipboardGetsEditorSelection(e);
}));
this._register(dom.addDisposableListener(textArea.domNode, 'paste', (e: ClipboardEvent) => {
this._lastTextAreaEvent = TextAreaInputEventType.paste;
// Pretend here we touched the text area, as the `paste` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received paste event');
@@ -297,8 +343,14 @@ export class TextAreaInput extends Disposable {
}
}));
this._register(dom.addDisposableListener(textArea.domNode, 'focus', () => this._setHasFocus(true)));
this._register(dom.addDisposableListener(textArea.domNode, 'blur', () => this._setHasFocus(false)));
this._register(dom.addDisposableListener(textArea.domNode, 'focus', () => {
this._lastTextAreaEvent = TextAreaInputEventType.focus;
this._setHasFocus(true);
}));
this._register(dom.addDisposableListener(textArea.domNode, 'blur', () => {
this._lastTextAreaEvent = TextAreaInputEventType.blur;
this._setHasFocus(false);
}));
// See https://github.com/Microsoft/vscode/issues/27216

View File

@@ -51,7 +51,7 @@ export class TextAreaState {
return '[ <' + this.value + '>, selectionStart: ' + this.selectionStart + ', selectionEnd: ' + this.selectionEnd + ']';
}
public readFromTextArea(textArea: ITextAreaWrapper): TextAreaState {
public static readFromTextArea(textArea: ITextAreaWrapper): TextAreaState {
return new TextAreaState(textArea.getValue(), textArea.getSelectionStart(), textArea.getSelectionEnd(), null, null);
}
@@ -60,7 +60,7 @@ export class TextAreaState {
}
public writeToTextArea(reason: string, textArea: ITextAreaWrapper, select: boolean): void {
// console.log(Date.now() + ': applyToTextArea ' + reason + ': ' + this.toString());
// console.log(Date.now() + ': writeToTextArea ' + reason + ': ' + this.toString());
textArea.setValue(reason, this.value);
if (select) {
textArea.setSelectionRange(reason, this.selectionStart, this.selectionEnd);
@@ -97,7 +97,7 @@ export class TextAreaState {
return new TextAreaState(text, 0, text.length, null, null);
}
public static deduceInput(previousState: TextAreaState, currentState: TextAreaState, couldBeEmojiInput: boolean): ITypeData {
public static deduceInput(previousState: TextAreaState, currentState: TextAreaState, couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean): ITypeData {
if (!previousState) {
// This is the EMPTY state
return {
@@ -117,6 +117,18 @@ export class TextAreaState {
let currentSelectionStart = currentState.selectionStart;
let currentSelectionEnd = currentState.selectionEnd;
if (couldBeTypingAtOffset0 && previousValue.length > 0 && previousSelectionStart === previousSelectionEnd && currentSelectionStart === currentSelectionEnd) {
// See https://github.com/Microsoft/vscode/issues/42251
// where typing always happens at offset 0 in the textarea
// when using a custom title area in OSX and moving the window
if (strings.endsWith(currentValue, previousValue)) {
// Looks like something was typed at offset 0
// ==> pretend we placed the cursor at offset 0 to begin with...
previousSelectionStart = 0;
previousSelectionEnd = 0;
}
}
// Strip the previous suffix from the value (without interfering with the current selection)
const previousSuffix = previousValue.substring(previousSelectionEnd);
const currentSuffix = currentValue.substring(currentSelectionEnd);

View File

@@ -71,3 +71,33 @@ export class EditorState {
return this._equals(new EditorState(editor, this.flags));
}
}
export class StableEditorScrollState {
public static capture(editor: ICodeEditor): StableEditorScrollState {
let visiblePosition: Position = null;
let visiblePositionScrollDelta = 0;
if (editor.getScrollTop() !== 0) {
const visibleRanges = editor.getVisibleRanges();
if (visibleRanges.length > 0) {
visiblePosition = visibleRanges[0].getStartPosition();
const visiblePositionScrollTop = editor.getTopForPosition(visiblePosition.lineNumber, visiblePosition.column);
visiblePositionScrollDelta = editor.getScrollTop() - visiblePositionScrollTop;
}
}
return new StableEditorScrollState(visiblePosition, visiblePositionScrollDelta);
}
constructor(
private readonly _visiblePosition: Position,
private readonly _visiblePositionScrollDelta: number
) {
}
public restore(editor: ICodeEditor): void {
if (this._visiblePosition) {
const visiblePositionScrollTop = editor.getTopForPosition(this._visiblePosition.lineNumber, this._visiblePosition.column);
editor.setScrollTop(visiblePositionScrollTop + this._visiblePositionScrollDelta);
}
}
}

View File

@@ -309,6 +309,11 @@ export interface IOverviewRuler {
* A rich code editor.
*/
export interface ICodeEditor extends editorCommon.IEditor {
/**
* This editor is used as an alternative to an <input> box, i.e. as a simple widget.
* @internal
*/
readonly isSimpleWidget: boolean;
/**
* An event emitted when the content of the current model has changed.
* @event
@@ -386,6 +391,12 @@ export interface ICodeEditor extends editorCommon.IEditor {
* @internal
*/
onDidType(listener: (text: string) => void): IDisposable;
/**
* An event emitted when editing failed because the editor is read-only.
* @event
* @internal
*/
onDidAttemptReadOnlyEdit(listener: () => void): IDisposable;
/**
* An event emitted when users paste text in the editor.
* @event
@@ -614,11 +625,6 @@ export interface ICodeEditor extends editorCommon.IEditor {
*/
getLayoutInfo(): editorOptions.EditorLayoutInfo;
/**
* Returns the range that is currently centered in the view port.
*/
getCenteredRangeInViewport(): Range;
/**
* Returns the ranges that are currently visible.
* Does not account for horizontal scrolling.

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
@@ -14,12 +14,12 @@ export abstract class AbstractCodeEditorService implements ICodeEditorService {
_serviceBrand: any;
private _onCodeEditorAdd: Emitter<ICodeEditor>;
private _onCodeEditorRemove: Emitter<ICodeEditor>;
private readonly _onCodeEditorAdd: Emitter<ICodeEditor>;
private readonly _onCodeEditorRemove: Emitter<ICodeEditor>;
private _codeEditors: { [editorId: string]: ICodeEditor; };
private _onDiffEditorAdd: Emitter<IDiffEditor>;
private _onDiffEditorRemove: Emitter<IDiffEditor>;
private readonly _onDiffEditorAdd: Emitter<IDiffEditor>;
private readonly _onDiffEditorRemove: Emitter<IDiffEditor>;
private _diffEditors: { [editorId: string]: IDiffEditor; };
constructor() {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';

View File

@@ -19,7 +19,7 @@ export class CodeEditorServiceImpl extends AbstractCodeEditorService {
private _decorationOptionProviders: { [key: string]: IModelDecorationOptionsProvider };
private _themeService: IThemeService;
constructor( @IThemeService themeService: IThemeService, styleSheet = dom.createStyleSheet()) {
constructor(@IThemeService themeService: IThemeService, styleSheet = dom.createStyleSheet()) {
super();
this._styleSheet = styleSheet;
this._decorationOptionProviders = Object.create(null);
@@ -126,6 +126,7 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
public className: string;
public inlineClassName: string;
public inlineClassNameAffectsLetterSpacing: boolean;
public beforeContentClassName: string;
public afterContentClassName: string;
public glyphMarginClassName: string;
@@ -145,9 +146,21 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
}
return void 0;
};
let createInlineCSSRules = (type: ModelDecorationCSSRuleType) => {
let rules = new DecorationCSSRules(type, providerArgs, themeService);
if (rules.hasContent) {
this._disposables.push(rules);
return { className: rules.className, hasLetterSpacing: rules.hasLetterSpacing };
}
return null;
};
this.className = createCSSRules(ModelDecorationCSSRuleType.ClassName);
this.inlineClassName = createCSSRules(ModelDecorationCSSRuleType.InlineClassName);
const inlineData = createInlineCSSRules(ModelDecorationCSSRuleType.InlineClassName);
if (inlineData) {
this.inlineClassName = inlineData.className;
this.inlineClassNameAffectsLetterSpacing = inlineData.hasLetterSpacing;
}
this.beforeContentClassName = createCSSRules(ModelDecorationCSSRuleType.BeforeContentClassName);
this.afterContentClassName = createCSSRules(ModelDecorationCSSRuleType.AfterContentClassName);
this.glyphMarginClassName = createCSSRules(ModelDecorationCSSRuleType.GlyphMarginClassName);
@@ -194,6 +207,7 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
const _CSS_MAP = {
color: 'color:{0} !important;',
opacity: 'opacity:{0};',
backgroundColor: 'background-color:{0};',
outline: 'outline:{0};',
@@ -231,6 +245,7 @@ class DecorationCSSRules {
private _className: string;
private _unThemedSelector: string;
private _hasContent: boolean;
private _hasLetterSpacing: boolean;
private _ruleType: ModelDecorationCSSRuleType;
private _themeListener: IDisposable;
private _providerArgs: ProviderArguments;
@@ -242,6 +257,7 @@ class DecorationCSSRules {
this._providerArgs = providerArgs;
this._usesThemeColors = false;
this._hasContent = false;
this._hasLetterSpacing = false;
let className = CSSNameHelper.getClassName(this._providerArgs.key, ruleType);
if (this._providerArgs.parentTypeKey) {
@@ -277,6 +293,10 @@ class DecorationCSSRules {
return this._hasContent;
}
public get hasLetterSpacing(): boolean {
return this._hasLetterSpacing;
}
public get className(): string {
return this._className;
}
@@ -357,7 +377,10 @@ class DecorationCSSRules {
return '';
}
let cssTextArr: string[] = [];
this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'cursor', 'color', 'letterSpacing'], cssTextArr);
this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'cursor', 'color', 'opacity', 'letterSpacing'], cssTextArr);
if (opts.letterSpacing) {
this._hasLetterSpacing = true;
}
return cssTextArr.join('');
}
@@ -385,7 +408,7 @@ class DecorationCSSRules {
cssTextArr.push(strings.format(_CSS_MAP.contentText, escaped));
}
this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'color', 'backgroundColor', 'margin'], cssTextArr);
this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'color', 'opacity', 'backgroundColor', 'margin'], cssTextArr);
if (this.collectCSSText(opts, ['width', 'height'], cssTextArr)) {
cssTextArr.push('display:inline-block;');
}

View File

@@ -7,9 +7,7 @@
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents';
import { CoreNavigationCommands, CoreEditorCommand } from 'vs/editor/browser/controller/coreCommands';
@@ -33,6 +31,18 @@ export interface IMouseDispatchData {
ctrlKey: boolean;
metaKey: boolean;
shiftKey: boolean;
leftButton: boolean;
middleButton: boolean;
}
export interface ICommandDelegate {
paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[]): void;
type(source: string, text: string): void;
replacePreviousChar(source: string, text: string, replaceCharCnt: number): void;
compositionStart(source: string): void;
compositionEnd(source: string): void;
cut(source: string): void;
}
export class ViewController {
@@ -41,20 +51,20 @@ export class ViewController {
private readonly viewModel: IViewModel;
private readonly _execCoreEditorCommandFunc: ExecCoreEditorCommandFunc;
private readonly outgoingEvents: ViewOutgoingEvents;
private readonly commandService: ICommandService;
private readonly commandDelegate: ICommandDelegate;
constructor(
configuration: Configuration,
viewModel: IViewModel,
execCommandFunc: ExecCoreEditorCommandFunc,
outgoingEvents: ViewOutgoingEvents,
commandService: ICommandService
commandDelegate: ICommandDelegate
) {
this.configuration = configuration;
this.viewModel = viewModel;
this._execCoreEditorCommandFunc = execCommandFunc;
this.outgoingEvents = outgoingEvents;
this.commandService = commandService;
this.commandDelegate = commandDelegate;
}
private _execMouseCommand(editorCommand: CoreEditorCommand, args: any): void {
@@ -63,36 +73,27 @@ export class ViewController {
}
public paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[]): void {
this.commandService.executeCommand(editorCommon.Handler.Paste, {
text: text,
pasteOnNewLine: pasteOnNewLine,
multicursorText: multicursorText
});
this.commandDelegate.paste(source, text, pasteOnNewLine, multicursorText);
}
public type(source: string, text: string): void {
this.commandService.executeCommand(editorCommon.Handler.Type, {
text: text
});
this.commandDelegate.type(source, text);
}
public replacePreviousChar(source: string, text: string, replaceCharCnt: number): void {
this.commandService.executeCommand(editorCommon.Handler.ReplacePreviousChar, {
text: text,
replaceCharCnt: replaceCharCnt
});
this.commandDelegate.replacePreviousChar(source, text, replaceCharCnt);
}
public compositionStart(source: string): void {
this.commandService.executeCommand(editorCommon.Handler.CompositionStart, {});
this.commandDelegate.compositionStart(source);
}
public compositionEnd(source: string): void {
this.commandService.executeCommand(editorCommon.Handler.CompositionEnd, {});
this.commandDelegate.compositionEnd(source);
}
public cut(source: string): void {
this.commandService.executeCommand(editorCommon.Handler.Cut, {});
this.commandDelegate.cut(source);
}
public setSelection(source: string, modelSelection: Selection): void {
@@ -135,7 +136,13 @@ export class ViewController {
}
public dispatchMouse(data: IMouseDispatchData): void {
if (data.startedOnLineNumbers) {
if (data.middleButton) {
if (data.inSelectionMode) {
this.columnSelect(data.position, data.mouseColumn);
} else {
this.moveTo(data.position);
}
} else if (data.startedOnLineNumbers) {
// If the dragging started on the gutter, then have operations work on the entire line
if (this._hasMulticursorModifier(data)) {
if (data.inSelectionMode) {

View File

@@ -8,14 +8,13 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Range } from 'vs/editor/common/core/range';
import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler';
import { Configuration } from 'vs/editor/browser/config/configuration';
import { TextAreaHandler, ITextAreaHandlerHelper } from 'vs/editor/browser/controller/textAreaHandler';
import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { ViewController, ExecCoreEditorCommandFunc } from 'vs/editor/browser/view/viewController';
import { ViewController, ExecCoreEditorCommandFunc, ICommandDelegate } from 'vs/editor/browser/view/viewController';
import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher';
import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays';
import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/contentWidgets';
@@ -93,7 +92,7 @@ export class View extends ViewEventHandler {
private _renderAnimationFrame: IDisposable;
constructor(
commandService: ICommandService,
commandDelegate: ICommandDelegate,
configuration: Configuration,
themeService: IThemeService,
model: IViewModel,
@@ -105,7 +104,7 @@ export class View extends ViewEventHandler {
this._renderAnimationFrame = null;
this.outgoingEvents = new ViewOutgoingEvents(model);
let viewController = new ViewController(configuration, model, execCoreEditorCommandFunc, this.outgoingEvents, commandService);
let viewController = new ViewController(configuration, model, execCoreEditorCommandFunc, this.outgoingEvents, commandDelegate);
// The event dispatcher will always go through _renderOnce before dispatching any events
this.eventDispatcher = new ViewEventDispatcher((callback: () => void) => this._renderOnce(callback));
@@ -178,8 +177,8 @@ export class View extends ViewEventHandler {
this.viewParts.push(contentViewOverlays);
contentViewOverlays.addDynamicOverlay(new CurrentLineHighlightOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new SelectionsOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new DecorationsOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new IndentGuidesOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new DecorationsOverlay(this._context));
let marginViewOverlays = new MarginViewOverlays(this._context);
this.viewParts.push(marginViewOverlays);
@@ -322,6 +321,7 @@ export class View extends ViewEventHandler {
}
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
this.domNode.setClassName(this.getEditorClassName());
this._context.model.setHasFocus(e.isFocused);
if (e.isFocused) {
this.outgoingEvents.emitViewFocusGained();
} else {

View File

@@ -308,7 +308,8 @@ class Widget {
private _layoutBoxInPage(topLeft: Coordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult {
let left0 = topLeft.left - ctx.scrollLeft;
if (left0 + width < 0 || left0 > this._contentWidth) {
if (left0 < 0 || left0 > this._contentWidth) {
// Don't render if position is scrolled outside viewport
return null;
}

View File

@@ -85,8 +85,14 @@ export class DecorationsOverlay extends DynamicViewOverlay {
// Sort decorations for consistent render output
decorations = decorations.sort((a, b) => {
let aClassName = a.options.className;
let bClassName = b.options.className;
if (a.options.zIndex < b.options.zIndex) {
return -1;
}
if (a.options.zIndex > b.options.zIndex) {
return 1;
}
const aClassName = a.options.className;
const bClassName = b.options.className;
if (aClassName < bClassName) {
return -1;
@@ -142,8 +148,12 @@ export class DecorationsOverlay extends DynamicViewOverlay {
}
private _renderNormalDecorations(ctx: RenderingContext, decorations: ViewModelDecoration[], output: string[]): void {
let lineHeight = String(this._lineHeight);
let visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const lineHeight = String(this._lineHeight);
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let prevClassName: string = null;
let prevShowIfCollapsed: boolean = false;
let prevRange: Range = null;
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
const d = decorations[i];
@@ -160,39 +170,60 @@ export class DecorationsOverlay extends DynamicViewOverlay {
range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber - 1, this._context.model.getLineMaxColumn(range.endLineNumber - 1));
}
let linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/className === 'findMatch');
if (!linesVisibleRanges) {
if (prevClassName === className && prevShowIfCollapsed === showIfCollapsed && Range.areIntersectingOrTouching(prevRange, range)) {
// merge into previous decoration
prevRange = Range.plusRange(prevRange, range);
continue;
}
for (let j = 0, lenJ = linesVisibleRanges.length; j < lenJ; j++) {
let lineVisibleRanges = linesVisibleRanges[j];
const lineIndex = lineVisibleRanges.lineNumber - visibleStartLineNumber;
// flush previous decoration
if (prevClassName !== null) {
this._renderNormalDecoration(ctx, prevRange, prevClassName, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output);
}
if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) {
const singleVisibleRange = lineVisibleRanges.ranges[0];
if (singleVisibleRange.width === 0) {
// collapsed range case => make the decoration visible by faking its width
lineVisibleRanges.ranges[0] = new HorizontalRange(singleVisibleRange.left, this._typicalHalfwidthCharacterWidth);
}
}
prevClassName = className;
prevShowIfCollapsed = showIfCollapsed;
prevRange = range;
}
for (let k = 0, lenK = lineVisibleRanges.ranges.length; k < lenK; k++) {
const visibleRange = lineVisibleRanges.ranges[k];
const decorationOutput = (
'<div class="cdr '
+ className
+ '" style="left:'
+ String(visibleRange.left)
+ 'px;width:'
+ String(visibleRange.width)
+ 'px;height:'
+ lineHeight
+ 'px;"></div>'
);
output[lineIndex] += decorationOutput;
if (prevClassName !== null) {
this._renderNormalDecoration(ctx, prevRange, prevClassName, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output);
}
}
private _renderNormalDecoration(ctx: RenderingContext, range: Range, className: string, showIfCollapsed: boolean, lineHeight: string, visibleStartLineNumber: number, output: string[]): void {
let linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/className === 'findMatch');
if (!linesVisibleRanges) {
return;
}
for (let j = 0, lenJ = linesVisibleRanges.length; j < lenJ; j++) {
let lineVisibleRanges = linesVisibleRanges[j];
const lineIndex = lineVisibleRanges.lineNumber - visibleStartLineNumber;
if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) {
const singleVisibleRange = lineVisibleRanges.ranges[0];
if (singleVisibleRange.width === 0) {
// collapsed range case => make the decoration visible by faking its width
lineVisibleRanges.ranges[0] = new HorizontalRange(singleVisibleRange.left, this._typicalHalfwidthCharacterWidth);
}
}
for (let k = 0, lenK = lineVisibleRanges.ranges.length; k < lenK; k++) {
const visibleRange = lineVisibleRanges.ranges[k];
const decorationOutput = (
'<div class="cdr '
+ className
+ '" style="left:'
+ String(visibleRange.left)
+ 'px;width:'
+ String(visibleRange.width)
+ 'px;height:'
+ lineHeight
+ 'px;"></div>'
);
output[lineIndex] += decorationOutput;
}
}
}

View File

@@ -10,3 +10,6 @@
.monaco-editor .lines-content .cigr {
position: absolute;
}
.monaco-editor .lines-content .cigra {
position: absolute;
}

View File

@@ -11,13 +11,13 @@ import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorIndentGuides } from 'vs/editor/common/view/editorColorRegistry';
import * as dom from 'vs/base/browser/dom';
import { editorIndentGuides, editorActiveIndentGuides } from 'vs/editor/common/view/editorColorRegistry';
import { Position } from 'vs/editor/common/core/position';
export class IndentGuidesOverlay extends DynamicViewOverlay {
private _context: ViewContext;
private _primaryLineNumber: number;
private _lineHeight: number;
private _spaceWidth: number;
private _renderResult: string[];
@@ -26,6 +26,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
constructor(context: ViewContext) {
super();
this._context = context;
this._primaryLineNumber = 0;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._spaceWidth = this._context.configuration.editor.fontInfo.spaceWidth;
this._enabled = this._context.configuration.editor.viewInfo.renderIndentGuides;
@@ -55,6 +56,17 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
}
return true;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
const selection = e.selections[0];
const newPrimaryLineNumber = selection.isEmpty() ? selection.positionLineNumber : 0;
if (this._primaryLineNumber !== newPrimaryLineNumber) {
this._primaryLineNumber = newPrimaryLineNumber;
return true;
}
return false;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
// true for inline decorations
return true;
@@ -94,20 +106,32 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
const tabSize = this._context.model.getTabSize();
const tabWidth = tabSize * this._spaceWidth;
const lineHeight = this._lineHeight;
const indentGuideWidth = dom.computeScreenAwareSize(1);
const indentGuideWidth = tabWidth;
const indents = this._context.model.getLinesIndentGuides(visibleStartLineNumber, visibleEndLineNumber);
let activeIndentStartLineNumber = 0;
let activeIndentEndLineNumber = 0;
let activeIndentLevel = 0;
if (this._primaryLineNumber) {
const activeIndentInfo = this._context.model.getActiveIndentGuide(this._primaryLineNumber, visibleStartLineNumber, visibleEndLineNumber);
activeIndentStartLineNumber = activeIndentInfo.startLineNumber;
activeIndentEndLineNumber = activeIndentInfo.endLineNumber;
activeIndentLevel = activeIndentInfo.indent;
}
let output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const containsActiveIndentGuide = (activeIndentStartLineNumber <= lineNumber && lineNumber <= activeIndentEndLineNumber);
const lineIndex = lineNumber - visibleStartLineNumber;
const indent = indents[lineIndex];
let result = '';
let leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1));
let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0;
for (let i = 0; i < indent; i++) {
result += `<div class="cigr" style="left:${left}px;height:${lineHeight}px;width:${indentGuideWidth}px"></div>`;
for (let i = 1; i <= indent; i++) {
let className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr');
result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentGuideWidth}px"></div>`;
left += tabWidth;
}
@@ -129,8 +153,12 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
}
registerThemingParticipant((theme, collector) => {
let editorGuideColor = theme.getColor(editorIndentGuides);
if (editorGuideColor) {
collector.addRule(`.monaco-editor .lines-content .cigr { background-color: ${editorGuideColor}; }`);
let editorIndentGuidesColor = theme.getColor(editorIndentGuides);
if (editorIndentGuidesColor) {
collector.addRule(`.monaco-editor .lines-content .cigr { box-shadow: 1px 0 0 0 ${editorIndentGuidesColor} inset; }`);
}
let editorActiveIndentGuidesColor = theme.getColor(editorActiveIndentGuides) || editorIndentGuidesColor;
if (editorActiveIndentGuidesColor) {
collector.addRule(`.monaco-editor .lines-content .cigra { box-shadow: 1px 0 0 0 ${editorActiveIndentGuidesColor} inset; }`);
}
});

View File

@@ -6,11 +6,10 @@
import * as browser from 'vs/base/browser/browser';
import * as platform from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IConfiguration } from 'vs/editor/common/editorCommon';
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
import { renderViewLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { renderViewLine, RenderLineInput, CharacterMapping, ForeignElementType } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { IVisibleLine } from 'vs/editor/browser/view/viewLayer';
import { RangeUtil } from 'vs/editor/browser/viewParts/lines/rangeUtil';
import { HorizontalRange } from 'vs/editor/common/view/renderingContext';
@@ -192,7 +191,8 @@ export class ViewLine implements IVisibleLine {
let renderLineInput = new RenderLineInput(
options.useMonospaceOptimizations,
lineData.content,
lineData.mightContainRTL,
lineData.isBasicASCII,
lineData.containsRTL,
lineData.minColumn - 1,
lineData.tokens,
actualInlineDecorations,
@@ -222,18 +222,16 @@ export class ViewLine implements IVisibleLine {
sb.appendASCIIString('</div>');
let renderedViewLine: IRenderedViewLine = null;
if (canUseFastRenderedViewLine && options.useMonospaceOptimizations && !output.containsForeignElements) {
let isRegularASCII = true;
if (lineData.mightContainNonBasicASCII) {
isRegularASCII = strings.isBasicASCII(lineData.content);
}
if (isRegularASCII && lineData.content.length < 1000 && renderLineInput.lineTokens.getCount() < 100) {
if (canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) {
if (lineData.content.length < 300 && renderLineInput.lineTokens.getCount() < 100) {
// Browser rounding errors have been observed in Chrome and IE, so using the fast
// view line only for short lines. Please test before removing the length check...
// ---
// Another rounding error has been observed on Linux in VSCode, where <span> width
// rounding errors add up to an observable large number...
// ---
// Also see another example of rounding errors on Windows in
// https://github.com/Microsoft/vscode/issues/33178
renderedViewLine = new FastRenderedViewLine(
this._renderedViewLine ? this._renderedViewLine.domNode : null,
renderLineInput,
@@ -385,7 +383,7 @@ class RenderedViewLine implements IRenderedViewLine {
protected readonly _characterMapping: CharacterMapping;
private readonly _isWhitespaceOnly: boolean;
private readonly _containsForeignElements: boolean;
private readonly _containsForeignElements: ForeignElementType;
private _cachedWidth: number;
/**
@@ -393,7 +391,7 @@ class RenderedViewLine implements IRenderedViewLine {
*/
private _pixelOffsetCache: Int32Array;
constructor(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) {
constructor(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) {
this.domNode = domNode;
this.input = renderLineInput;
this._characterMapping = characterMapping;
@@ -471,10 +469,18 @@ class RenderedViewLine implements IRenderedViewLine {
protected _readPixelOffset(column: number, context: DomReadingContext): number {
if (this._characterMapping.length === 0) {
// This line has no content
if (!this._containsForeignElements) {
if (this._containsForeignElements === ForeignElementType.None) {
// We can assume the line is really empty
return 0;
}
if (this._containsForeignElements === ForeignElementType.After) {
// We have foreign elements after the (empty) line
return 0;
}
if (this._containsForeignElements === ForeignElementType.Before) {
// We have foreign element before the (empty) line
return this.getWidth();
}
}
if (this._pixelOffsetCache !== null) {
@@ -503,7 +509,7 @@ class RenderedViewLine implements IRenderedViewLine {
return r[0].left;
}
if (column === this._characterMapping.length && this._isWhitespaceOnly && !this._containsForeignElements) {
if (column === this._characterMapping.length && this._isWhitespaceOnly && this._containsForeignElements === ForeignElementType.None) {
// This branch helps in the case of whitespace only lines which have a width set
return this.getWidth();
}
@@ -586,17 +592,17 @@ class WebKitRenderedViewLine extends RenderedViewLine {
}
}
const createRenderedLine: (domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) => RenderedViewLine = (function () {
const createRenderedLine: (domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) => RenderedViewLine = (function () {
if (browser.isWebKit) {
return createWebKitRenderedLine;
}
return createNormalRenderedLine;
})();
function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean): RenderedViewLine {
function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine {
return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}
function createNormalRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean): RenderedViewLine {
function createNormalRenderedLine(domNode: FastDomNode<HTMLElement>, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine {
return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}

View File

@@ -14,6 +14,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents';
export class Margin extends ViewPart {
public static readonly CLASS_NAME = 'glyph-margin';
public static readonly OUTER_CLASS_NAME = 'margin';
private _domNode: FastDomNode<HTMLElement>;
private _canUseLayerHinting: boolean;
@@ -42,7 +43,7 @@ export class Margin extends ViewPart {
private _createDomNode(): FastDomNode<HTMLElement> {
let domNode = createFastDomNode(document.createElement('div'));
domNode.setClassName('margin');
domNode.setClassName(Margin.OUTER_CLASS_NAME);
domNode.setPosition('absolute');
domNode.setAttribute('role', 'presentation');
domNode.setAttribute('aria-hidden', 'true');

View File

@@ -7,6 +7,7 @@
import 'vs/css!./minimap';
import { ViewPart, PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart';
import * as strings from 'vs/base/common/strings';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
@@ -897,17 +898,22 @@ export class Minimap extends ViewPart {
// No need to render anything since space is invisible
dx += charWidth;
} else {
if (renderMinimap === RenderMinimap.Large) {
minimapCharRenderer.x2RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont);
} else if (renderMinimap === RenderMinimap.Small) {
minimapCharRenderer.x1RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont);
} else if (renderMinimap === RenderMinimap.LargeBlocks) {
minimapCharRenderer.x2BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont);
} else {
// RenderMinimap.SmallBlocks
minimapCharRenderer.x1BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont);
// Render twice for a full width character
let count = strings.isFullWidthCharacter(charCode) ? 2 : 1;
for (let i = 0; i < count; i++) {
if (renderMinimap === RenderMinimap.Large) {
minimapCharRenderer.x2RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont);
} else if (renderMinimap === RenderMinimap.Small) {
minimapCharRenderer.x1RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont);
} else if (renderMinimap === RenderMinimap.LargeBlocks) {
minimapCharRenderer.x2BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont);
} else {
// RenderMinimap.SmallBlocks
minimapCharRenderer.x1BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont);
}
dx += charWidth;
}
dx += charWidth;
}
}
}

View File

@@ -58,8 +58,13 @@ class Settings {
this.themeType = theme.type;
const minimapEnabled = config.editor.viewInfo.minimap.enabled;
const minimapSide = config.editor.viewInfo.minimap.side;
const backgroundColor = (minimapEnabled ? TokenizationRegistry.getDefaultBackground() : null);
this.backgroundColor = (backgroundColor ? Color.Format.CSS.formatHex(backgroundColor) : null);
if (backgroundColor === null || minimapSide === 'left') {
this.backgroundColor = null;
} else {
this.backgroundColor = Color.Format.CSS.formatHex(backgroundColor);
}
const position = config.editor.layoutInfo.overviewRuler;
this.top = position.top;

View File

@@ -13,7 +13,6 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorRuler } from 'vs/editor/common/view/editorColorRegistry';
import * as dom from 'vs/base/browser/dom';
export class Rulers extends ViewPart {
@@ -67,7 +66,7 @@ export class Rulers extends ViewPart {
}
if (currentCount < desiredCount) {
const rulerWidth = dom.computeScreenAwareSize(1);
const rulerWidth = this._context.model.getTabSize();
let addCount = desiredCount - currentCount;
while (addCount > 0) {
let node = createFastDomNode(document.createElement('div'));
@@ -104,6 +103,6 @@ export class Rulers extends ViewPart {
registerThemingParticipant((theme, collector) => {
let rulerColor = theme.getColor(editorRuler);
if (rulerColor) {
collector.addRule(`.monaco-editor .view-ruler { background-color: ${rulerColor}; }`);
collector.addRule(`.monaco-editor .view-ruler { box-shadow: 1px 0 0 0 ${rulerColor} inset; }`);
}
});

View File

@@ -54,7 +54,12 @@ export class ScrollDecorationViewPart extends ViewPart {
private _updateWidth(): boolean {
const layoutInfo = this._context.configuration.editor.layoutInfo;
let newWidth = layoutInfo.width - layoutInfo.minimapWidth;
let newWidth = 0;
if (layoutInfo.renderMinimap === 0 || (layoutInfo.minimapWidth > 0 && layoutInfo.minimapLeft === 0)) {
newWidth = layoutInfo.width;
} else {
newWidth = layoutInfo.width - layoutInfo.minimapWidth - layoutInfo.verticalScrollbarWidth;
}
if (this._width !== newWidth) {
this._width = newWidth;
return true;

View File

@@ -78,6 +78,7 @@ export class SelectionsOverlay extends DynamicViewOverlay {
private _context: ViewContext;
private _lineHeight: number;
private _roundedSelection: boolean;
private _typicalHalfwidthCharacterWidth: number;
private _selections: Range[];
private _renderResult: string[];
@@ -86,6 +87,7 @@ export class SelectionsOverlay extends DynamicViewOverlay {
this._context = context;
this._lineHeight = this._context.configuration.editor.lineHeight;
this._roundedSelection = this._context.configuration.editor.viewInfo.roundedSelection;
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
this._selections = [];
this._renderResult = null;
this._context.addEventHandler(this);
@@ -108,6 +110,9 @@ export class SelectionsOverlay extends DynamicViewOverlay {
if (e.viewInfo) {
this._roundedSelection = this._context.configuration.editor.viewInfo.roundedSelection;
}
if (e.fontInfo) {
this._typicalHalfwidthCharacterWidth = this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
}
return true;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
@@ -153,23 +158,28 @@ export class SelectionsOverlay extends DynamicViewOverlay {
return false;
}
private _enrichVisibleRangesWithStyle(linesVisibleRanges: LineVisibleRangesWithStyle[], previousFrame: LineVisibleRangesWithStyle[]): void {
private _enrichVisibleRangesWithStyle(viewport: Range, linesVisibleRanges: LineVisibleRangesWithStyle[], previousFrame: LineVisibleRangesWithStyle[]): void {
const epsilon = this._typicalHalfwidthCharacterWidth / 4;
let previousFrameTop: HorizontalRangeWithStyle = null;
let previousFrameBottom: HorizontalRangeWithStyle = null;
if (previousFrame && previousFrame.length > 0 && linesVisibleRanges.length > 0) {
let topLineNumber = linesVisibleRanges[0].lineNumber;
for (let i = 0; !previousFrameTop && i < previousFrame.length; i++) {
if (previousFrame[i].lineNumber === topLineNumber) {
previousFrameTop = previousFrame[i].ranges[0];
if (topLineNumber === viewport.startLineNumber) {
for (let i = 0; !previousFrameTop && i < previousFrame.length; i++) {
if (previousFrame[i].lineNumber === topLineNumber) {
previousFrameTop = previousFrame[i].ranges[0];
}
}
}
let bottomLineNumber = linesVisibleRanges[linesVisibleRanges.length - 1].lineNumber;
for (let i = previousFrame.length - 1; !previousFrameBottom && i >= 0; i--) {
if (previousFrame[i].lineNumber === bottomLineNumber) {
previousFrameBottom = previousFrame[i].ranges[0];
if (bottomLineNumber === viewport.endLineNumber) {
for (let i = previousFrame.length - 1; !previousFrameBottom && i >= 0; i--) {
if (previousFrame[i].lineNumber === bottomLineNumber) {
previousFrameBottom = previousFrame[i].ranges[0];
}
}
}
@@ -202,13 +212,13 @@ export class SelectionsOverlay extends DynamicViewOverlay {
let prevLeft = linesVisibleRanges[i - 1].ranges[0].left;
let prevRight = linesVisibleRanges[i - 1].ranges[0].left + linesVisibleRanges[i - 1].ranges[0].width;
if (curLeft === prevLeft) {
if (abs(curLeft - prevLeft) < epsilon) {
startStyle.top = CornerStyle.FLAT;
} else if (curLeft > prevLeft) {
startStyle.top = CornerStyle.INTERN;
}
if (curRight === prevRight) {
if (abs(curRight - prevRight) < epsilon) {
endStyle.top = CornerStyle.FLAT;
} else if (prevLeft < curRight && curRight < prevRight) {
endStyle.top = CornerStyle.INTERN;
@@ -224,13 +234,13 @@ export class SelectionsOverlay extends DynamicViewOverlay {
let nextLeft = linesVisibleRanges[i + 1].ranges[0].left;
let nextRight = linesVisibleRanges[i + 1].ranges[0].left + linesVisibleRanges[i + 1].ranges[0].width;
if (curLeft === nextLeft) {
if (abs(curLeft - nextLeft) < epsilon) {
startStyle.bottom = CornerStyle.FLAT;
} else if (nextLeft < curLeft && curLeft < nextRight) {
startStyle.bottom = CornerStyle.INTERN;
}
if (curRight === nextRight) {
if (abs(curRight - nextRight) < epsilon) {
endStyle.bottom = CornerStyle.FLAT;
} else if (curRight < nextRight) {
endStyle.bottom = CornerStyle.INTERN;
@@ -252,7 +262,7 @@ export class SelectionsOverlay extends DynamicViewOverlay {
let visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges);
if (!isIEWithZoomingIssuesNearRoundedBorders && !visibleRangesHaveGaps && this._roundedSelection) {
this._enrichVisibleRangesWithStyle(linesVisibleRanges, previousFrame);
this._enrichVisibleRangesWithStyle(ctx.visibleRange, linesVisibleRanges, previousFrame);
}
// The visible ranges are sorted TOP-BOTTOM and LEFT-RIGHT
@@ -407,3 +417,7 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`.monaco-editor .view-line span.inline-selected-text { color: ${editorSelectionForegroundColor}; }`);
}
});
function abs(n: number): number {
return n < 0 ? -n : n;
}

View File

@@ -13,6 +13,7 @@ import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import * as dom from 'vs/base/browser/dom';
import * as strings from 'vs/base/common/strings';
export interface IViewCursorRenderData {
domNode: HTMLElement;
@@ -28,7 +29,8 @@ class ViewCursorRenderData {
public readonly left: number,
public readonly width: number,
public readonly height: number,
public readonly textContent: string
public readonly textContent: string,
public readonly textContentClassName: string
) { }
}
@@ -118,6 +120,7 @@ export class ViewCursor {
private _prepareRender(ctx: RenderingContext): ViewCursorRenderData {
let textContent = '';
let textContentClassName = '';
if (this._cursorStyle === TextEditorCursorStyle.Line || this._cursorStyle === TextEditorCursorStyle.LineThin) {
const visibleRange = ctx.visibleRangeForPosition(this._position);
@@ -136,7 +139,7 @@ export class ViewCursor {
width = dom.computeScreenAwareSize(1);
}
const top = ctx.getVerticalOffsetForLineNumber(this._position.lineNumber) - ctx.bigNumbersDelta;
return new ViewCursorRenderData(top, visibleRange.left, width, this._lineHeight, textContent);
return new ViewCursorRenderData(top, visibleRange.left, width, this._lineHeight, textContent, textContentClassName);
}
const visibleRangeForCharacter = ctx.linesVisibleRangesForRange(new Range(this._position.lineNumber, this._position.column, this._position.lineNumber, this._position.column + 1), false);
@@ -150,8 +153,13 @@ export class ViewCursor {
const width = range.width < 1 ? this._typicalHalfwidthCharacterWidth : range.width;
if (this._cursorStyle === TextEditorCursorStyle.Block) {
const lineContent = this._context.model.getLineContent(this._position.lineNumber);
textContent = lineContent.charAt(this._position.column - 1);
const lineData = this._context.model.getViewLineData(this._position.lineNumber);
textContent = lineData.content.charAt(this._position.column - 1);
if (strings.isHighSurrogate(lineData.content.charCodeAt(this._position.column - 1))) {
textContent += lineData.content.charAt(this._position.column);
}
const tokenIndex = lineData.tokens.findTokenIndexAtOffset(this._position.column - 1);
textContentClassName = lineData.tokens.getClassName(tokenIndex);
}
let top = ctx.getVerticalOffsetForLineNumber(this._position.lineNumber) - ctx.bigNumbersDelta;
@@ -163,7 +171,7 @@ export class ViewCursor {
height = 2;
}
return new ViewCursorRenderData(top, range.left, width, height, textContent);
return new ViewCursorRenderData(top, range.left, width, height, textContent, textContentClassName);
}
public prepareRender(ctx: RenderingContext): void {
@@ -181,6 +189,8 @@ export class ViewCursor {
this._domNode.domNode.textContent = this._lastRenderedContent;
}
this._domNode.setClassName('cursor ' + this._renderData.textContentClassName);
this._domNode.setDisplay('block');
this._domNode.setTop(this._renderData.top);
this._domNode.setLeft(this._renderData.left);

View File

@@ -99,7 +99,11 @@ export class ViewZones extends ViewPart {
}
public onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean {
return this._recomputeWhitespacesProps();
const hadAChange = this._recomputeWhitespacesProps();
if (hadAChange) {
this._context.viewLayout.onHeightMaybeChanged();
}
return hadAChange;
}
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {

View File

@@ -21,18 +21,20 @@ import { Configuration } from 'vs/editor/browser/config/configuration';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { View, IOverlayWidgetData, IContentWidgetData } from 'vs/editor/browser/view/viewImpl';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { InternalEditorAction } from 'vs/editor/common/editorAction';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IPosition } from 'vs/editor/common/core/position';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { CoreEditorCommand } from 'vs/editor/browser/controller/coreCommands';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoBorder, editorInfoForeground } from 'vs/editor/common/view/editorColorRegistry';
import { editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoBorder, editorInfoForeground, editorHintForeground, editorHintBorder } from 'vs/editor/common/view/editorColorRegistry';
import { Color } from 'vs/base/common/color';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { ClassName } from 'vs/editor/common/model/intervalTree';
import { ITextModel, IModelDecorationOptions } from 'vs/editor/common/model';
import { ICommandDelegate } from 'vs/editor/browser/view/viewController';
import { INotificationService } from 'vs/platform/notification/common/notification';
export abstract class CodeEditorWidget extends CommonCodeEditor implements editorBrowser.ICodeEditor {
@@ -86,26 +88,22 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
constructor(
domElement: HTMLElement,
options: IEditorOptions,
isSimpleWidget: boolean,
@IInstantiationService instantiationService: IInstantiationService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@ICommandService commandService: ICommandService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@INotificationService notificationService: INotificationService
) {
super(domElement, options, instantiationService, contextKeyService);
super(domElement, options, isSimpleWidget, instantiationService, contextKeyService, notificationService);
this._codeEditorService = codeEditorService;
this._commandService = commandService;
this._themeService = themeService;
this._focusTracker = new CodeEditorWidgetFocusTracker(domElement);
this._focusTracker.onChange(() => {
let hasFocus = this._focusTracker.hasFocus();
if (hasFocus) {
this._onDidFocusEditor.fire();
} else {
this._onDidBlurEditor.fire();
}
this._editorFocus.setValue(this._focusTracker.hasFocus());
});
this.contentWidgets = {};
@@ -358,8 +356,62 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
}
protected _createView(): void {
let commandDelegate: ICommandDelegate;
if (this.isSimpleWidget) {
commandDelegate = {
paste: (source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[]) => {
this.cursor.trigger(source, editorCommon.Handler.Paste, { text, pasteOnNewLine, multicursorText });
},
type: (source: string, text: string) => {
this.cursor.trigger(source, editorCommon.Handler.Type, { text });
},
replacePreviousChar: (source: string, text: string, replaceCharCnt: number) => {
this.cursor.trigger(source, editorCommon.Handler.ReplacePreviousChar, { text, replaceCharCnt });
},
compositionStart: (source: string) => {
this.cursor.trigger(source, editorCommon.Handler.CompositionStart, undefined);
},
compositionEnd: (source: string) => {
this.cursor.trigger(source, editorCommon.Handler.CompositionEnd, undefined);
},
cut: (source: string) => {
this.cursor.trigger(source, editorCommon.Handler.Cut, undefined);
}
};
} else {
commandDelegate = {
paste: (source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[]) => {
this._commandService.executeCommand(editorCommon.Handler.Paste, {
text: text,
pasteOnNewLine: pasteOnNewLine,
multicursorText: multicursorText
});
},
type: (source: string, text: string) => {
this._commandService.executeCommand(editorCommon.Handler.Type, {
text: text
});
},
replacePreviousChar: (source: string, text: string, replaceCharCnt: number) => {
this._commandService.executeCommand(editorCommon.Handler.ReplacePreviousChar, {
text: text,
replaceCharCnt: replaceCharCnt
});
},
compositionStart: (source: string) => {
this._commandService.executeCommand(editorCommon.Handler.CompositionStart, {});
},
compositionEnd: (source: string) => {
this._commandService.executeCommand(editorCommon.Handler.CompositionEnd, {});
},
cut: (source: string) => {
this._commandService.executeCommand(editorCommon.Handler.Cut, {});
}
};
}
this._view = new View(
this._commandService,
commandDelegate,
this._configuration,
this._themeService,
this.viewModel,
@@ -375,13 +427,13 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
const viewEventBus = this._view.getInternalEventBus();
viewEventBus.onDidGainFocus = () => {
this._onDidFocusEditorText.fire();
this._editorTextFocus.setValue(true);
// In IE, the focus is not synchronous, so we give it a little help
this._onDidFocusEditor.fire();
this._editorFocus.setValue(true);
};
viewEventBus.onDidScroll = (e) => this._onDidScrollChange.fire(e);
viewEventBus.onDidLoseFocus = () => this._onDidBlurEditorText.fire();
viewEventBus.onDidLoseFocus = () => this._editorTextFocus.setValue(false);
viewEventBus.onContextMenu = (e) => this._onContextMenu.fire(e);
viewEventBus.onMouseDown = (e) => this._onMouseDown.fire(e);
viewEventBus.onMouseUp = (e) => this._onMouseUp.fire(e);
@@ -399,7 +451,12 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
return;
}
if (s && s.cursorState && s.viewState) {
this._view.restoreState(this.viewModel.viewLayout.reduceRestoreState(s.viewState));
const reducedState = this.viewModel.reduceRestoreState(s.viewState);
const linesViewportData = this.viewModel.viewLayout.getLinesViewportDataAtScrollTop(reducedState.scrollTop);
const startPosition = this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(linesViewportData.startLineNumber, 1));
const endPosition = this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(linesViewportData.endLineNumber, 1));
this.model.tokenizeViewport(startPosition.lineNumber, endPosition.lineNumber);
this._view.restoreState(reducedState);
}
}
@@ -455,8 +512,8 @@ class CodeEditorWidgetFocusTracker extends Disposable {
private _hasFocus: boolean;
private _domFocusTracker: dom.IFocusTracker;
private _onChange: Emitter<void> = this._register(new Emitter<void>());
public onChange: Event<void> = this._onChange.event;
private readonly _onChange: Emitter<void> = this._register(new Emitter<void>());
public readonly onChange: Event<void> = this._onChange.event;
constructor(domElement: HTMLElement) {
super();
@@ -486,6 +543,13 @@ function getSquigglySVGData(color: Color) {
return squigglyStart + encodeURIComponent(color.toString()) + squigglyEnd;
}
const dotdotdotStart = encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" height="3" width="12"><g fill="`);
const dotdotdotEnd = encodeURIComponent(`"><circle cx="1" cy="1" r="1"/><circle cx="5" cy="1" r="1"/><circle cx="9" cy="1" r="1"/></g></svg>`);
function getDotDotDotSVGData(color: Color) {
return dotdotdotStart + encodeURIComponent(color.toString()) + dotdotdotEnd;
}
registerThemingParticipant((theme, collector) => {
let errorBorderColor = theme.getColor(editorErrorBorder);
if (errorBorderColor) {
@@ -513,4 +577,13 @@ registerThemingParticipant((theme, collector) => {
if (infoForeground) {
collector.addRule(`.monaco-editor .${ClassName.EditorInfoDecoration} { background: url("data:image/svg+xml;utf8,${getSquigglySVGData(infoForeground)}") repeat-x bottom left; }`);
}
let hintBorderColor = theme.getColor(editorHintBorder);
if (hintBorderColor) {
collector.addRule(`.monaco-editor .${ClassName.EditorHintDecoration} { border-bottom: 2px dotted ${hintBorderColor}; }`);
}
let hintForeground = theme.getColor(editorHintForeground);
if (hintForeground) {
collector.addRule(`.monaco-editor .${ClassName.EditorHintDecoration} { background: url("data:image/svg+xml;utf8,${getDotDotDotSVGData(hintForeground)}") no-repeat bottom left; }`);
}
});

View File

@@ -27,10 +27,10 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { Configuration } from 'vs/editor/browser/config/configuration';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { Selection, ISelection } from 'vs/editor/common/core/selection';
import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel';
import { InlineDecoration, InlineDecorationType, ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ColorId, MetadataConsts, FontStyle } from 'vs/editor/common/modes';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import * as editorOptions from 'vs/editor/common/config/editorOptions';
import { registerThemingParticipant, IThemeService, ITheme, getThemeTypeSelector } from 'vs/platform/theme/common/themeService';
import { scrollbarShadow, diffInserted, diffRemoved, defaultInsertColor, defaultRemoveColor, diffInsertedOutline, diffRemovedOutline } from 'vs/platform/theme/common/colorRegistry';
@@ -43,6 +43,7 @@ import URI from 'vs/base/common/uri';
import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/stringBuilder';
import { IModelDeltaDecoration, IModelDecorationsChangeAccessor, ITextModel } from 'vs/editor/common/model';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { StableEditorScrollState } from 'vs/editor/browser/core/editorState';
interface IEditorDiffDecorations {
decorations: IModelDeltaDecoration[];
@@ -99,15 +100,13 @@ class VisualEditorState {
this._zonesMap = {};
// (2) Model decorations
if (this._decorations.length > 0) {
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
changeAccessor.deltaDecorations(this._decorations, []);
});
}
this._decorations = [];
this._decorations = editor.deltaDecorations(this._decorations, []);
}
public apply(editor: CodeEditor, overviewRuler: editorBrowser.IOverviewRuler, newDecorations: IEditorDiffDecorationsWithZones): void {
public apply(editor: CodeEditor, overviewRuler: editorBrowser.IOverviewRuler, newDecorations: IEditorDiffDecorationsWithZones, restoreScrollState: boolean): void {
const scrollState = restoreScrollState ? StableEditorScrollState.capture(editor) : null;
// view zones
editor.changeViewZones((viewChangeAccessor: editorBrowser.IViewZoneChangeAccessor) => {
for (let i = 0, length = this._zones.length; i < length; i++) {
@@ -123,6 +122,10 @@ class VisualEditorState {
}
});
if (scrollState) {
scrollState.restore(editor);
}
// decorations
this._decorations = editor.deltaDecorations(this._decorations, newDecorations.decorations);
@@ -284,13 +287,22 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this._lineChanges = null;
const services = new ServiceCollection();
services.set(IContextKeyService, this._contextKeyService);
const leftContextKeyService = this._contextKeyService.createScoped();
leftContextKeyService.createKey('isInDiffLeftEditor', true);
const scopedInstantiationService = instantiationService.createChild(services);
const leftServices = new ServiceCollection();
leftServices.set(IContextKeyService, leftContextKeyService);
const leftScopedInstantiationService = instantiationService.createChild(leftServices);
this._createLeftHandSideEditor(options, scopedInstantiationService);
this._createRightHandSideEditor(options, scopedInstantiationService);
const rightContextKeyService = this._contextKeyService.createScoped();
rightContextKeyService.createKey('isInDiffRightEditor', true);
const rightServices = new ServiceCollection();
rightServices.set(IContextKeyService, rightContextKeyService);
const rightScopedInstantiationService = instantiationService.createChild(rightServices);
this._createLeftHandSideEditor(options, leftScopedInstantiationService);
this._createRightHandSideEditor(options, rightScopedInstantiationService);
this._reviewPane = new DiffReview(this);
this._containerDomElement.appendChild(this._reviewPane.domNode.domNode);
@@ -912,8 +924,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
try {
this._currentlyChangingViewZones = true;
this._originalEditorState.apply(this.originalEditor, this._originalOverviewRuler, diffDecorations.original);
this._modifiedEditorState.apply(this.modifiedEditor, this._modifiedOverviewRuler, diffDecorations.modified);
this._originalEditorState.apply(this.originalEditor, this._originalOverviewRuler, diffDecorations.original, false);
this._modifiedEditorState.apply(this.modifiedEditor, this._modifiedOverviewRuler, diffDecorations.modified, true);
} finally {
this._currentlyChangingViewZones = false;
}
@@ -1989,10 +2001,13 @@ class InlineViewZonesComputer extends ViewZonesComputer {
sb.appendASCIIString(String(count * config.lineHeight));
sb.appendASCIIString('px;width:1000000px;">');
const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, originalModel.mightContainNonBasicASCII());
const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, originalModel.mightContainRTL());
renderViewLine(new RenderLineInput(
(config.fontInfo.isMonospace && !config.viewInfo.disableMonospaceOptimizations),
lineContent,
originalModel.mightContainRTL(),
isBasicASCII,
containsRTL,
0,
lineTokens,
actualDecorations,
@@ -2023,27 +2038,31 @@ function createFakeLinesDiv(): HTMLElement {
}
registerThemingParticipant((theme, collector) => {
let added = theme.getColor(diffInserted);
const added = theme.getColor(diffInserted);
if (added) {
collector.addRule(`.monaco-editor .line-insert, .monaco-editor .char-insert { background-color: ${added}; }`);
collector.addRule(`.monaco-diff-editor .line-insert, .monaco-diff-editor .char-insert { background-color: ${added}; }`);
collector.addRule(`.monaco-editor .inline-added-margin-view-zone { background-color: ${added}; }`);
}
let removed = theme.getColor(diffRemoved);
const removed = theme.getColor(diffRemoved);
if (removed) {
collector.addRule(`.monaco-editor .line-delete, .monaco-editor .char-delete { background-color: ${removed}; }`);
collector.addRule(`.monaco-diff-editor .line-delete, .monaco-diff-editor .char-delete { background-color: ${removed}; }`);
collector.addRule(`.monaco-editor .inline-deleted-margin-view-zone { background-color: ${removed}; }`);
}
let addedOutline = theme.getColor(diffInsertedOutline);
const addedOutline = theme.getColor(diffInsertedOutline);
if (addedOutline) {
collector.addRule(`.monaco-editor .line-insert, .monaco-editor .char-insert { border: 1px dashed ${addedOutline}; }`);
collector.addRule(`.monaco-editor .line-insert, .monaco-editor .char-insert { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${addedOutline}; }`);
}
let removedOutline = theme.getColor(diffRemovedOutline);
const removedOutline = theme.getColor(diffRemovedOutline);
if (removedOutline) {
collector.addRule(`.monaco-editor .line-delete, .monaco-editor .char-delete { border: 1px dashed ${removedOutline}; }`);
collector.addRule(`.monaco-editor .line-delete, .monaco-editor .char-delete { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${removedOutline}; }`);
}
let shadow = theme.getColor(scrollbarShadow);
const shadow = theme.getColor(scrollbarShadow);
if (shadow) {
collector.addRule(`.monaco-diff-editor.side-by-side .editor.modified { box-shadow: -6px 0 5px -5px ${shadow}; }`);
}

View File

@@ -11,7 +11,7 @@ import { ILineChange, ScrollType } from 'vs/editor/common/editorCommon';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
interface IDiffRange {

View File

@@ -29,6 +29,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model';
import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel';
const DIFF_LINES_PADDING = 3;
@@ -583,9 +584,34 @@ export class DiffReview extends Disposable {
let cell = document.createElement('div');
cell.className = 'diff-review-cell diff-review-summary';
cell.appendChild(document.createTextNode(`${diffIndex + 1}/${this._diffs.length}: @@ -${minOriginalLine},${maxOriginalLine - minOriginalLine + 1} +${minModifiedLine},${maxModifiedLine - minModifiedLine + 1} @@`));
const originalChangedLinesCnt = maxOriginalLine - minOriginalLine + 1;
const modifiedChangedLinesCnt = maxModifiedLine - minModifiedLine + 1;
cell.appendChild(document.createTextNode(`${diffIndex + 1}/${this._diffs.length}: @@ -${minOriginalLine},${originalChangedLinesCnt} +${minModifiedLine},${modifiedChangedLinesCnt} @@`));
header.setAttribute('data-line', String(minModifiedLine));
header.setAttribute('aria-label', nls.localize('header', "Difference {0} of {1}: original {2}, {3} lines, modified {4}, {5} lines", (diffIndex + 1), this._diffs.length, minOriginalLine, maxOriginalLine - minOriginalLine + 1, minModifiedLine, maxModifiedLine - minModifiedLine + 1));
const getAriaLines = (lines: number) => {
if (lines === 0) {
return nls.localize('no_lines', "no lines");
} else if (lines === 1) {
return nls.localize('one_line', "1 line");
} else {
return nls.localize('more_lines', "{0} lines", lines);
}
};
const originalChangedLinesCntAria = getAriaLines(originalChangedLinesCnt);
const modifiedChangedLinesCntAria = getAriaLines(modifiedChangedLinesCnt);
header.setAttribute('aria-label', nls.localize({
key: 'header',
comment: [
'This is the ARIA label for a git diff header.',
'A git diff header looks like this: @@ -154,12 +159,39 @@.',
'That encodes that at original line 154 (which is now line 159), 12 lines were removed/changed with 39 lines.',
'Variables 0 and 1 refer to the diff index out of total number of diffs.',
'Variables 2 and 4 will be numbers (a line number).',
'Variables 3 and 5 will be "no lines", "1 line" or "X lines", localized separately.'
]
}, "Difference {0} of {1}: original {2}, {3}, modified {4}, {5}", (diffIndex + 1), this._diffs.length, minOriginalLine, originalChangedLinesCntAria, minModifiedLine, modifiedChangedLinesCntAria));
header.appendChild(cell);
// @@ -504,7 +517,7 @@
@@ -738,10 +764,13 @@ export class DiffReview extends Disposable {
const lineTokens = new LineTokens(tokens, lineContent);
const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, model.mightContainNonBasicASCII());
const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, model.mightContainRTL());
const r = renderViewLine(new RenderLineInput(
(config.fontInfo.isMonospace && !config.viewInfo.disableMonospaceOptimizations),
lineContent,
model.mightContainRTL(),
isBasicASCII,
containsRTL,
0,
lineTokens,
[],

View File

@@ -30,9 +30,10 @@ export class EmbeddedCodeEditorWidget extends CodeEditor {
@ICodeEditorService codeEditorService: ICodeEditorService,
@ICommandService commandService: ICommandService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@INotificationService notificationService: INotificationService
) {
super(domElement, parentEditor.getRawConfiguration(), instantiationService, codeEditorService, commandService, contextKeyService, themeService);
super(domElement, parentEditor.getRawConfiguration(), instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService);
this._parentEditor = parentEditor;
this._overwriteOptions = options;

View File

@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { onUnexpectedError } from 'vs/base/common/errors';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -30,6 +31,7 @@ import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceCompute
import * as modes from 'vs/editor/common/modes';
import { Schemas } from 'vs/base/common/network';
import { ITextModel, EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, IModelDecoration, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
import { INotificationService } from 'vs/platform/notification/common/notification';
let EDITOR_ID = 0;
@@ -65,20 +67,19 @@ export abstract class CommonCodeEditor extends Disposable {
private readonly _onDidChangeCursorSelection: Emitter<ICursorSelectionChangedEvent> = this._register(new Emitter<ICursorSelectionChangedEvent>());
public readonly onDidChangeCursorSelection: Event<ICursorSelectionChangedEvent> = this._onDidChangeCursorSelection.event;
private readonly _onDidAttemptReadOnlyEdit: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidAttemptReadOnlyEdit: Event<void> = this._onDidAttemptReadOnlyEdit.event;
private readonly _onDidLayoutChange: Emitter<editorOptions.EditorLayoutInfo> = this._register(new Emitter<editorOptions.EditorLayoutInfo>());
public readonly onDidLayoutChange: Event<editorOptions.EditorLayoutInfo> = this._onDidLayoutChange.event;
protected readonly _onDidFocusEditorText: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidFocusEditorText: Event<void> = this._onDidFocusEditorText.event;
protected _editorTextFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter());
public readonly onDidFocusEditorText: Event<void> = this._editorTextFocus.onDidChangeToTrue;
public readonly onDidBlurEditorText: Event<void> = this._editorTextFocus.onDidChangeToFalse;
protected readonly _onDidBlurEditorText: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidBlurEditorText: Event<void> = this._onDidBlurEditorText.event;
protected readonly _onDidFocusEditor: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidFocusEditor: Event<void> = this._onDidFocusEditor.event;
protected readonly _onDidBlurEditor: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidBlurEditor: Event<void> = this._onDidBlurEditor.event;
protected _editorFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter());
public readonly onDidFocusEditor: Event<void> = this._editorFocus.onDidChangeToTrue;
public readonly onDidBlurEditor: Event<void> = this._editorFocus.onDidChangeToFalse;
private readonly _onWillType: Emitter<string> = this._register(new Emitter<string>());
public readonly onWillType = this._onWillType.event;
@@ -89,6 +90,7 @@ export abstract class CommonCodeEditor extends Disposable {
private readonly _onDidPaste: Emitter<Range> = this._register(new Emitter<Range>());
public readonly onDidPaste = this._onDidPaste.event;
public readonly isSimpleWidget: boolean;
protected readonly domElement: IContextKeyServiceTarget;
protected readonly id: number;
@@ -107,6 +109,7 @@ export abstract class CommonCodeEditor extends Disposable {
protected readonly _instantiationService: IInstantiationService;
protected readonly _contextKeyService: IContextKeyService;
protected readonly _notificationService: INotificationService;
/**
* map from "parent" decoration type to live decoration ids.
@@ -118,14 +121,17 @@ export abstract class CommonCodeEditor extends Disposable {
constructor(
domElement: IContextKeyServiceTarget,
options: editorOptions.IEditorOptions,
isSimpleWidget: boolean,
instantiationService: IInstantiationService,
contextKeyService: IContextKeyService
contextKeyService: IContextKeyService,
notificationService: INotificationService,
) {
super();
this.domElement = domElement;
this.id = (++EDITOR_ID);
this._decorationTypeKeysToIds = {};
this._decorationTypeSubtypes = {};
this.isSimpleWidget = isSimpleWidget;
options = options || {};
this._configuration = this._register(this._createConfiguration(options));
@@ -138,6 +144,7 @@ export abstract class CommonCodeEditor extends Disposable {
}));
this._contextKeyService = this._register(contextKeyService.createScoped(this.domElement));
this._notificationService = notificationService;
this._register(new EditorContextKeysManager(this, this._contextKeyService));
this._register(new EditorModeContext(this, this._contextKeyService));
@@ -249,13 +256,6 @@ export abstract class CommonCodeEditor extends Disposable {
}
}
public getCenteredRangeInViewport(): Range {
if (!this.hasView) {
return null;
}
return this.viewModel.getCenteredRangeInViewport();
}
public getVisibleRanges(): Range[] {
if (!this.hasView) {
return [];
@@ -626,19 +626,19 @@ export abstract class CommonCodeEditor extends Disposable {
if (!this.cursor || !this.hasView) {
return null;
}
let contributionsState: { [key: string]: any } = {};
const contributionsState: { [key: string]: any } = {};
let keys = Object.keys(this._contributions);
const keys = Object.keys(this._contributions);
for (let i = 0, len = keys.length; i < len; i++) {
let id = keys[i];
let contribution = this._contributions[id];
const id = keys[i];
const contribution = this._contributions[id];
if (typeof contribution.saveViewState === 'function') {
contributionsState[id] = contribution.saveViewState();
}
}
let cursorState = this.cursor.saveState();
let viewState = this.viewModel.viewLayout.saveState();
const cursorState = this.cursor.saveState();
const viewState = this.viewModel.saveState();
return {
cursorState: cursorState,
viewState: viewState,
@@ -798,7 +798,7 @@ export abstract class CommonCodeEditor extends Disposable {
}
this.model.pushEditOperations(this.cursor.getSelections(), edits, () => {
return endCursorState ? endCursorState : this.cursor.getSelections();
return endCursorState ? endCursorState : null;
});
if (endCursorState) {
@@ -967,6 +967,14 @@ export abstract class CommonCodeEditor extends Disposable {
this._createView();
this.listenersToRemove.push(this.cursor.onDidReachMaxCursorCount(() => {
this._notificationService.warn(nls.localize('cursors.maximum', "The number of cursors has been limited to {0}.", Cursor.MAX_CURSOR_COUNT));
}));
this.listenersToRemove.push(this.cursor.onDidAttemptReadOnlyEdit(() => {
this._onDidAttemptReadOnlyEdit.fire(void 0);
}));
this.listenersToRemove.push(this.cursor.onDidChange((e: CursorStateChangedEvent) => {
let positions: Position[] = [];
@@ -1044,10 +1052,45 @@ export abstract class CommonCodeEditor extends Disposable {
}
}
const enum BooleanEventValue {
NotSet,
False,
True
}
export class BooleanEventEmitter extends Disposable {
private readonly _onDidChangeToTrue: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeToTrue: Event<void> = this._onDidChangeToTrue.event;
private readonly _onDidChangeToFalse: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeToFalse: Event<void> = this._onDidChangeToFalse.event;
private _value: BooleanEventValue;
constructor() {
super();
this._value = BooleanEventValue.NotSet;
}
public setValue(_value: boolean) {
let value = (_value ? BooleanEventValue.True : BooleanEventValue.False);
if (this._value === value) {
return;
}
this._value = value;
if (this._value === BooleanEventValue.True) {
this._onDidChangeToTrue.fire();
} else if (this._value === BooleanEventValue.False) {
this._onDidChangeToFalse.fire();
}
}
}
class EditorContextKeysManager extends Disposable {
private _editor: CommonCodeEditor;
private _editorFocus: IContextKey<boolean>;
private _textInputFocus: IContextKey<boolean>;
private _editorTextFocus: IContextKey<boolean>;
private _editorTabMovesFocus: IContextKey<boolean>;
private _editorReadonly: IContextKey<boolean>;
@@ -1064,7 +1107,8 @@ class EditorContextKeysManager extends Disposable {
contextKeyService.createKey('editorId', editor.getId());
this._editorFocus = EditorContextKeys.focus.bindTo(contextKeyService);
this._editorTextFocus = EditorContextKeys.textFocus.bindTo(contextKeyService);
this._textInputFocus = EditorContextKeys.textInputFocus.bindTo(contextKeyService);
this._editorTextFocus = EditorContextKeys.editorTextFocus.bindTo(contextKeyService);
this._editorTabMovesFocus = EditorContextKeys.tabMovesFocus.bindTo(contextKeyService);
this._editorReadonly = EditorContextKeys.readOnly.bindTo(contextKeyService);
this._hasMultipleSelections = EditorContextKeys.hasMultipleSelections.bindTo(contextKeyService);
@@ -1101,8 +1145,9 @@ class EditorContextKeysManager extends Disposable {
}
private _updateFromFocus(): void {
this._editorFocus.set(this._editor.hasWidgetFocus());
this._editorTextFocus.set(this._editor.isFocused());
this._editorFocus.set(this._editor.hasWidgetFocus() && !this._editor.isSimpleWidget);
this._editorTextFocus.set(this._editor.isFocused() && !this._editor.isSimpleWidget);
this._textInputFocus.set(this._editor.isFocused());
}
}

View File

@@ -5,7 +5,7 @@
'use strict';
import * as nls from 'vs/nls';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
@@ -34,8 +34,8 @@ export interface ITabFocus {
export const TabFocus: ITabFocus = new class implements ITabFocus {
private _tabFocus: boolean = false;
private _onDidChangeTabFocus: Emitter<boolean> = new Emitter<boolean>();
public onDidChangeTabFocus: Event<boolean> = this._onDidChangeTabFocus.event;
private readonly _onDidChangeTabFocus: Emitter<boolean> = new Emitter<boolean>();
public readonly onDidChangeTabFocus: Event<boolean> = this._onDidChangeTabFocus.event;
public getTabFocusMode(): boolean {
return this._tabFocus;
@@ -70,7 +70,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
private _lineNumbersDigitCount: number;
private _onDidChange = this._register(new Emitter<editorOptions.IConfigurationChangedEvent>());
public onDidChange: Event<editorOptions.IConfigurationChangedEvent> = this._onDidChange.event;
public readonly onDidChange: Event<editorOptions.IConfigurationChangedEvent> = this._onDidChange.event;
constructor(options: editorOptions.IEditorOptions) {
super();
@@ -166,7 +166,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
}
const configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
const editorConfiguration: IConfigurationNode = {
'id': 'editor',
'order': 5,
@@ -211,7 +211,7 @@ const editorConfiguration: IConfigurationNode = {
nls.localize('lineNumbers.interval', "Line numbers are rendered every 10 lines.")
],
'default': 'on',
'description': nls.localize('lineNumbers', "Controls the display of line numbers. Possible values are 'on', 'off', 'relative' and 'interval'.")
'description': nls.localize('lineNumbers', "Controls the display of line numbers.")
},
'editor.rulers': {
'type': 'array',
@@ -268,13 +268,13 @@ const editorConfiguration: IConfigurationNode = {
'type': 'string',
'enum': ['left', 'right'],
'default': EDITOR_DEFAULTS.viewInfo.minimap.side,
'description': nls.localize('minimap.side', "Controls the side where to render the minimap. Possible values are \'right\' and \'left\'")
'description': nls.localize('minimap.side', "Controls the side where to render the minimap.")
},
'editor.minimap.showSlider': {
'type': 'string',
'enum': ['always', 'mouseover'],
'default': EDITOR_DEFAULTS.viewInfo.minimap.showSlider,
'description': nls.localize('minimap.showSlider', "Controls whether the minimap slider is automatically hidden. Possible values are \'always\' and \'mouseover\'")
'description': nls.localize('minimap.showSlider', "Controls whether the minimap slider is automatically hidden.")
},
'editor.minimap.renderCharacters': {
'type': 'boolean',
@@ -370,6 +370,11 @@ const editorConfiguration: IConfigurationNode = {
]
}, "The modifier to be used to add multiple cursors with the mouse. `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The Go To Definition and Open Link mouse gestures will adapt such that they do not conflict with the multicursor modifier.")
},
'editor.multiCursorMergeOverlapping': {
'type': 'boolean',
'default': EDITOR_DEFAULTS.multiCursorMergeOverlapping,
'description': nls.localize('multiCursorMergeOverlapping', "Merge multiple cursors when they are overlapping.")
},
'editor.quickSuggestions': {
'anyOf': [
{
@@ -515,7 +520,7 @@ const editorConfiguration: IConfigurationNode = {
'type': 'string',
'enum': ['blink', 'smooth', 'phase', 'expand', 'solid'],
'default': editorOptions.blinkingStyleToString(EDITOR_DEFAULTS.viewInfo.cursorBlinking),
'description': nls.localize('cursorBlinking', "Control the cursor animation style, possible values are 'blink', 'smooth', 'phase', 'expand' and 'solid'")
'description': nls.localize('cursorBlinking', "Control the cursor animation style.")
},
'editor.mouseWheelZoom': {
'type': 'boolean',
@@ -568,13 +573,23 @@ const editorConfiguration: IConfigurationNode = {
'editor.codeLens': {
'type': 'boolean',
'default': EDITOR_DEFAULTS.contribInfo.codeLens,
'description': nls.localize('codeLens', "Controls if the editor shows code lenses")
'description': nls.localize('codeLens', "Controls if the editor shows CodeLens")
},
'editor.folding': {
'type': 'boolean',
'default': EDITOR_DEFAULTS.contribInfo.folding,
'description': nls.localize('folding', "Controls whether the editor has code folding enabled")
},
'editor.foldingStrategy': {
'type': 'string',
'enum': ['auto', 'indentation'],
'enumDescriptions': [
nls.localize('foldingStrategyAuto', 'If available, use a language specific folding strategy, otherwise falls back to the indentation based strategy.'),
nls.localize('foldingStrategyIndentation', 'Always use the indentation based folding strategy')
],
'default': EDITOR_DEFAULTS.contribInfo.foldingStrategy,
'description': nls.localize('foldingStrategy', "Controls the way folding ranges are computed. 'auto' picks uses a language specific folding strategy, if available. 'indentation' forces that the indentation based folding strategy is used.")
},
'editor.showFoldingControls': {
'type': 'string',
'enum': ['always', 'mouseover'],
@@ -637,6 +652,25 @@ const editorConfiguration: IConfigurationNode = {
'default': EDITOR_DEFAULTS.contribInfo.lightbulbEnabled,
'description': nls.localize('codeActions', "Enables the code action lightbulb")
},
'editor.codeActionsOnSave': {
'type': 'object',
'properties': {
'source.organizeImports': {
'type': 'boolean',
'description': nls.localize('codeActionsOnSave.organizeImports', "Run organize imports on save?")
}
},
'additionalProperties': {
'type': 'boolean'
},
'default': EDITOR_DEFAULTS.contribInfo.codeActionsOnSave,
'description': nls.localize('codeActionsOnSave', "Code action kinds to be run on save.")
},
'editor.codeActionsOnSaveTimeout': {
'type': 'number',
'default': EDITOR_DEFAULTS.contribInfo.codeActionsOnSaveTimeout,
'description': nls.localize('codeActionsOnSaveTimeout', "Timeout for code actions run on save.")
},
'editor.selectionClipboard': {
'type': 'boolean',
'default': EDITOR_DEFAULTS.contribInfo.selectionClipboard,
@@ -653,6 +687,11 @@ const editorConfiguration: IConfigurationNode = {
'default': true,
'description': nls.localize('ignoreTrimWhitespace', "Controls if the diff editor shows changes in leading or trailing whitespace as diffs")
},
'editor.largeFileOptimizations': {
'type': 'boolean',
'default': EDITOR_MODEL_DEFAULTS.largeFileOptimizations,
'description': nls.localize('largeFileOptimizations', "Special handling for large files to disable certain memory intensive features.")
},
'diffEditor.renderIndicators': {
'type': 'boolean',
'default': true,

View File

@@ -10,6 +10,8 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { Constants } from 'vs/editor/common/core/uint';
import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper';
import * as arrays from 'vs/base/common/arrays';
import * as objects from 'vs/base/common/objects';
/**
* Configuration options for editor scrollbars
@@ -135,6 +137,13 @@ export interface IEditorLightbulbOptions {
enabled?: boolean;
}
/**
* Configuration map for codeActionsOnSave
*/
export interface ICodeActionsOnSaveOptions {
[kind: string]: boolean;
}
/**
* Configuration options for the editor.
*/
@@ -381,6 +390,11 @@ export interface IEditorOptions {
* Defaults to 'alt'
*/
multiCursorModifier?: 'ctrlCmd' | 'alt';
/**
* Merge overlapping selections.
* Defaults to true
*/
multiCursorMergeOverlapping?: boolean;
/**
* Configure the editor's accessibility support.
* Defaults to 'auto'. It is best to leave this to 'auto'.
@@ -460,7 +474,7 @@ export interface IEditorOptions {
/**
* The history mode for suggestions.
*/
suggestSelection?: string;
suggestSelection?: 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix';
/**
* The font size for the suggest widget.
* Defaults to the editor font size.
@@ -486,20 +500,28 @@ export interface IEditorOptions {
* Defaults to true.
*/
codeLens?: boolean;
/**
* @deprecated - use codeLens instead
* @internal
*/
referenceInfos?: boolean;
/**
* Control the behavior and rendering of the code action lightbulb.
*/
lightbulb?: IEditorLightbulbOptions;
/**
* Code action kinds to be run on save.
*/
codeActionsOnSave?: ICodeActionsOnSaveOptions;
/**
* Timeout for running code actions on save.
*/
codeActionsOnSaveTimeout?: number;
/**
* Enable code folding
* Defaults to true in vscode and to false in monaco-editor.
* Defaults to true.
*/
folding?: boolean;
/**
* Selects the folding strategy. 'auto' uses the strategies contributed for the current document, 'indentation' uses the indentation based folding strategy.
* Defaults to 'auto'.
*/
foldingStrategy?: 'auto' | 'indentation';
/**
* Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter.
* Defaults to 'mouseover'.
@@ -838,11 +860,14 @@ export interface EditorContribOptions {
readonly occurrencesHighlight: boolean;
readonly codeLens: boolean;
readonly folding: boolean;
readonly foldingStrategy: 'auto' | 'indentation';
readonly showFoldingControls: 'always' | 'mouseover';
readonly matchBrackets: boolean;
readonly find: InternalEditorFindOptions;
readonly colorDecorators: boolean;
readonly lightbulbEnabled: boolean;
readonly codeActionsOnSave: ICodeActionsOnSaveOptions;
readonly codeActionsOnSaveTimeout: number;
}
/**
@@ -872,6 +897,7 @@ export interface IValidatedEditorOptions {
readonly emptySelectionClipboard: boolean;
readonly useTabStops: boolean;
readonly multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey';
readonly multiCursorMergeOverlapping: boolean;
readonly accessibilitySupport: 'auto' | 'off' | 'on';
readonly viewInfo: InternalEditorViewOptions;
@@ -894,6 +920,7 @@ export class InternalEditorOptions {
*/
readonly accessibilitySupport: platform.AccessibilitySupport;
readonly multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey';
readonly multiCursorMergeOverlapping: boolean;
// ---- cursor options
readonly wordSeparators: string;
@@ -922,6 +949,7 @@ export class InternalEditorOptions {
readOnly: boolean;
accessibilitySupport: platform.AccessibilitySupport;
multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey';
multiCursorMergeOverlapping: boolean;
wordSeparators: string;
autoClosingBrackets: boolean;
autoIndent: boolean;
@@ -942,6 +970,7 @@ export class InternalEditorOptions {
this.readOnly = source.readOnly;
this.accessibilitySupport = source.accessibilitySupport;
this.multiCursorModifier = source.multiCursorModifier;
this.multiCursorMergeOverlapping = source.multiCursorMergeOverlapping;
this.wordSeparators = source.wordSeparators;
this.autoClosingBrackets = source.autoClosingBrackets;
this.autoIndent = source.autoIndent;
@@ -968,6 +997,7 @@ export class InternalEditorOptions {
&& this.readOnly === other.readOnly
&& this.accessibilitySupport === other.accessibilitySupport
&& this.multiCursorModifier === other.multiCursorModifier
&& this.multiCursorMergeOverlapping === other.multiCursorMergeOverlapping
&& this.wordSeparators === other.wordSeparators
&& this.autoClosingBrackets === other.autoClosingBrackets
&& this.autoIndent === other.autoIndent
@@ -995,6 +1025,7 @@ export class InternalEditorOptions {
readOnly: (this.readOnly !== newOpts.readOnly),
accessibilitySupport: (this.accessibilitySupport !== newOpts.accessibilitySupport),
multiCursorModifier: (this.multiCursorModifier !== newOpts.multiCursorModifier),
multiCursorMergeOverlapping: (this.multiCursorMergeOverlapping !== newOpts.multiCursorMergeOverlapping),
wordSeparators: (this.wordSeparators !== newOpts.wordSeparators),
autoClosingBrackets: (this.autoClosingBrackets !== newOpts.autoClosingBrackets),
autoIndent: (this.autoIndent !== newOpts.autoIndent),
@@ -1058,7 +1089,7 @@ export class InternalEditorOptions {
return (
a.extraEditorClassName === b.extraEditorClassName
&& a.disableMonospaceOptimizations === b.disableMonospaceOptimizations
&& this._equalsNumberArrays(a.rulers, b.rulers)
&& arrays.equals(a.rulers, b.rulers)
&& a.ariaLabel === b.ariaLabel
&& a.renderLineNumbers === b.renderLineNumbers
&& a.renderCustomLineNumbers === b.renderCustomLineNumbers
@@ -1120,18 +1151,6 @@ export class InternalEditorOptions {
);
}
private static _equalsNumberArrays(a: number[], b: number[]): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
/**
* @internal
*/
@@ -1188,10 +1207,13 @@ export class InternalEditorOptions {
&& a.occurrencesHighlight === b.occurrencesHighlight
&& a.codeLens === b.codeLens
&& a.folding === b.folding
&& a.foldingStrategy === b.foldingStrategy
&& a.showFoldingControls === b.showFoldingControls
&& a.matchBrackets === b.matchBrackets
&& this._equalFindOptions(a.find, b.find)
&& a.colorDecorators === b.colorDecorators
&& objects.equals(a.codeActionsOnSave, b.codeActionsOnSave)
&& a.codeActionsOnSaveTimeout === b.codeActionsOnSaveTimeout
&& a.lightbulbEnabled === b.lightbulbEnabled
);
}
@@ -1347,6 +1369,7 @@ export interface IConfigurationChangedEvent {
readonly readOnly: boolean;
readonly accessibilitySupport: boolean;
readonly multiCursorModifier: boolean;
readonly multiCursorMergeOverlapping: boolean;
readonly wordSeparators: boolean;
readonly autoClosingBrackets: boolean;
readonly autoIndent: boolean;
@@ -1388,6 +1411,21 @@ function _boolean<T>(value: any, defaultValue: T): boolean | T {
return Boolean(value);
}
function _booleanMap(value: { [key: string]: boolean }, defaultValue: { [key: string]: boolean }): { [key: string]: boolean } {
if (!value) {
return defaultValue;
}
const out = Object.create(null);
for (const k of Object.keys(value)) {
const v = value[k];
if (typeof v === 'boolean') {
out[k] = v;
}
}
return out;
}
function _string(value: any, defaultValue: string): string {
if (typeof value !== 'string') {
return defaultValue;
@@ -1395,14 +1433,14 @@ function _string(value: any, defaultValue: string): string {
return value;
}
function _stringSet<T>(value: any, defaultValue: T, allowedValues: string[]): T {
function _stringSet<T>(value: T, defaultValue: T, allowedValues: T[]): T {
if (typeof value !== 'string') {
return defaultValue;
}
if (allowedValues.indexOf(value) === -1) {
return defaultValue;
}
return <T><any>value;
return value;
}
function _clampedInt(value: any, defaultValue: number, minimum: number, maximum: number): number {
@@ -1532,6 +1570,7 @@ export class EditorOptionsValidator {
emptySelectionClipboard: _boolean(opts.emptySelectionClipboard, defaults.emptySelectionClipboard),
useTabStops: _boolean(opts.useTabStops, defaults.useTabStops),
multiCursorModifier: multiCursorModifier,
multiCursorMergeOverlapping: _boolean(opts.multiCursorMergeOverlapping, defaults.multiCursorMergeOverlapping),
accessibilitySupport: _stringSet<'auto' | 'on' | 'off'>(opts.accessibilitySupport, defaults.accessibilitySupport, ['auto', 'on', 'off']),
viewInfo: viewInfo,
contribInfo: contribInfo,
@@ -1652,7 +1691,11 @@ export class EditorOptionsValidator {
renderLineHighlight = _stringSet<'none' | 'gutter' | 'line' | 'all'>(opts.renderLineHighlight, defaults.renderLineHighlight, ['none', 'gutter', 'line', 'all']);
}
const mouseWheelScrollSensitivity = _float(opts.mouseWheelScrollSensitivity, defaults.scrollbar.mouseWheelScrollSensitivity);
let mouseWheelScrollSensitivity = _float(opts.mouseWheelScrollSensitivity, defaults.scrollbar.mouseWheelScrollSensitivity);
if (mouseWheelScrollSensitivity === 0) {
// Disallow 0, as it would prevent/block scrolling
mouseWheelScrollSensitivity = 1;
}
const scrollbar = this._sanitizeScrollbarOpts(opts.scrollbar, defaults.scrollbar, mouseWheelScrollSensitivity);
const minimap = this._sanitizeMinimapOpts(opts.minimap, defaults.minimap);
@@ -1695,6 +1738,10 @@ export class EditorOptionsValidator {
} else {
quickSuggestions = _boolean(opts.quickSuggestions, defaults.quickSuggestions);
}
// Compatibility support for acceptSuggestionOnEnter
if (typeof opts.acceptSuggestionOnEnter === 'boolean') {
opts.acceptSuggestionOnEnter = opts.acceptSuggestionOnEnter ? 'on' : 'off';
}
const find = this._santizeFindOpts(opts.find, defaults.find);
return {
selectionClipboard: _boolean(opts.selectionClipboard, defaults.selectionClipboard),
@@ -1708,7 +1755,7 @@ export class EditorOptionsValidator {
formatOnType: _boolean(opts.formatOnType, defaults.formatOnType),
formatOnPaste: _boolean(opts.formatOnPaste, defaults.formatOnPaste),
suggestOnTriggerCharacters: _boolean(opts.suggestOnTriggerCharacters, defaults.suggestOnTriggerCharacters),
acceptSuggestionOnEnter: typeof opts.acceptSuggestionOnEnter === 'string' ? _stringSet<'on' | 'smart' | 'off'>(opts.acceptSuggestionOnEnter, defaults.acceptSuggestionOnEnter, ['on', 'smart', 'off']) : opts.acceptSuggestionOnEnter ? 'on' : 'off',
acceptSuggestionOnEnter: _stringSet<'on' | 'smart' | 'off'>(opts.acceptSuggestionOnEnter, defaults.acceptSuggestionOnEnter, ['on', 'smart', 'off']),
acceptSuggestionOnCommitCharacter: _boolean(opts.acceptSuggestionOnCommitCharacter, defaults.acceptSuggestionOnCommitCharacter),
snippetSuggestions: _stringSet<'top' | 'bottom' | 'inline' | 'none'>(opts.snippetSuggestions, defaults.snippetSuggestions, ['top', 'bottom', 'inline', 'none']),
wordBasedSuggestions: _boolean(opts.wordBasedSuggestions, defaults.wordBasedSuggestions),
@@ -1717,13 +1764,16 @@ export class EditorOptionsValidator {
suggestLineHeight: _clampedInt(opts.suggestLineHeight, defaults.suggestLineHeight, 0, 1000),
selectionHighlight: _boolean(opts.selectionHighlight, defaults.selectionHighlight),
occurrencesHighlight: _boolean(opts.occurrencesHighlight, defaults.occurrencesHighlight),
codeLens: _boolean(opts.codeLens, defaults.codeLens) && _boolean(opts.referenceInfos, true),
codeLens: _boolean(opts.codeLens, defaults.codeLens),
folding: _boolean(opts.folding, defaults.folding),
foldingStrategy: _stringSet<'auto' | 'indentation'>(opts.foldingStrategy, defaults.foldingStrategy, ['auto', 'indentation']),
showFoldingControls: _stringSet<'always' | 'mouseover'>(opts.showFoldingControls, defaults.showFoldingControls, ['always', 'mouseover']),
matchBrackets: _boolean(opts.matchBrackets, defaults.matchBrackets),
find: find,
colorDecorators: _boolean(opts.colorDecorators, defaults.colorDecorators),
lightbulbEnabled: _boolean(opts.lightbulb ? opts.lightbulb.enabled : false, defaults.lightbulbEnabled)
lightbulbEnabled: _boolean(opts.lightbulb ? opts.lightbulb.enabled : false, defaults.lightbulbEnabled),
codeActionsOnSave: _booleanMap(opts.codeActionsOnSave, {}),
codeActionsOnSaveTimeout: _clampedInt(opts.codeActionsOnSaveTimeout, defaults.codeActionsOnSaveTimeout, 1, 10000)
};
}
}
@@ -1758,6 +1808,7 @@ export class InternalEditorOptionsFactory {
emptySelectionClipboard: opts.emptySelectionClipboard,
useTabStops: opts.useTabStops,
multiCursorModifier: opts.multiCursorModifier,
multiCursorMergeOverlapping: opts.multiCursorMergeOverlapping,
accessibilitySupport: opts.accessibilitySupport,
viewInfo: {
@@ -1820,11 +1871,14 @@ export class InternalEditorOptionsFactory {
occurrencesHighlight: (accessibilityIsOn ? false : opts.contribInfo.occurrencesHighlight), // DISABLED WHEN SCREEN READER IS ATTACHED
codeLens: (accessibilityIsOn ? false : opts.contribInfo.codeLens), // DISABLED WHEN SCREEN READER IS ATTACHED
folding: (accessibilityIsOn ? false : opts.contribInfo.folding), // DISABLED WHEN SCREEN READER IS ATTACHED
foldingStrategy: opts.contribInfo.foldingStrategy,
showFoldingControls: opts.contribInfo.showFoldingControls,
matchBrackets: (accessibilityIsOn ? false : opts.contribInfo.matchBrackets), // DISABLED WHEN SCREEN READER IS ATTACHED
find: opts.contribInfo.find,
colorDecorators: opts.contribInfo.colorDecorators,
lightbulbEnabled: opts.contribInfo.lightbulbEnabled
lightbulbEnabled: opts.contribInfo.lightbulbEnabled,
codeActionsOnSave: opts.contribInfo.codeActionsOnSave,
codeActionsOnSaveTimeout: opts.contribInfo.codeActionsOnSaveTimeout
}
};
}
@@ -1964,6 +2018,7 @@ export class InternalEditorOptionsFactory {
readOnly: opts.readOnly,
accessibilitySupport: accessibilitySupport,
multiCursorModifier: opts.multiCursorModifier,
multiCursorMergeOverlapping: opts.multiCursorMergeOverlapping,
wordSeparators: opts.wordSeparators,
autoClosingBrackets: opts.autoClosingBrackets,
autoIndent: opts.autoIndent,
@@ -1984,31 +2039,31 @@ export class InternalEditorOptionsFactory {
* @internal
*/
export interface IEditorLayoutProviderOpts {
outerWidth: number;
outerHeight: number;
readonly outerWidth: number;
readonly outerHeight: number;
showGlyphMargin: boolean;
lineHeight: number;
readonly showGlyphMargin: boolean;
readonly lineHeight: number;
showLineNumbers: boolean;
lineNumbersMinChars: number;
lineNumbersDigitCount: number;
readonly showLineNumbers: boolean;
readonly lineNumbersMinChars: number;
readonly lineNumbersDigitCount: number;
lineDecorationsWidth: number;
readonly lineDecorationsWidth: number;
typicalHalfwidthCharacterWidth: number;
maxDigitWidth: number;
readonly typicalHalfwidthCharacterWidth: number;
readonly maxDigitWidth: number;
verticalScrollbarWidth: number;
verticalScrollbarHasArrows: boolean;
scrollbarArrowSize: number;
horizontalScrollbarHeight: number;
readonly verticalScrollbarWidth: number;
readonly verticalScrollbarHasArrows: boolean;
readonly scrollbarArrowSize: number;
readonly horizontalScrollbarHeight: number;
minimap: boolean;
minimapSide: string;
minimapRenderCharacters: boolean;
minimapMaxColumn: number;
pixelRatio: number;
readonly minimap: boolean;
readonly minimapSide: string;
readonly minimapRenderCharacters: boolean;
readonly minimapMaxColumn: number;
readonly pixelRatio: number;
}
/**
@@ -2074,18 +2129,19 @@ export class EditorLayoutProvider {
}
// Given:
// viewportColumn = (contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth
// (leaving 2px for the cursor to have space after the last character)
// viewportColumn = (contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth
// minimapWidth = viewportColumn * minimapCharWidth
// contentWidth = remainingWidth - minimapWidth
// What are good values for contentWidth and minimapWidth ?
// minimapWidth = ((contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth) * minimapCharWidth
// typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth) * minimapCharWidth
// typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth) * minimapCharWidth
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth) * minimapCharWidth
// minimapWidth = ((remainingWidth - verticalScrollbarWidth) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
// minimapWidth = ((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth) * minimapCharWidth
// typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth - 2) * minimapCharWidth
// typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth - 2) * minimapCharWidth
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
// minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)));
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)));
let minimapColumns = minimapWidth / minimapCharWidth;
if (minimapColumns > minimapMaxColumn) {
minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
@@ -2103,7 +2159,8 @@ export class EditorLayoutProvider {
}
}
const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth));
// (leaving 2px for the cursor to have space after the last character)
const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth));
const verticalArrowSize = (verticalScrollbarHasArrows ? scrollbarArrowSize : 0);
@@ -2172,7 +2229,8 @@ export const EDITOR_MODEL_DEFAULTS = {
tabSize: 4,
insertSpaces: true,
detectIndentation: true,
trimAutoWhitespace: true
trimAutoWhitespace: true,
largeFileOptimizations: true
};
/**
@@ -2200,6 +2258,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
emptySelectionClipboard: true,
useTabStops: true,
multiCursorModifier: 'altKey',
multiCursorMergeOverlapping: true,
accessibilitySupport: 'auto',
viewInfo: {
@@ -2276,6 +2335,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
occurrencesHighlight: true,
codeLens: true,
folding: true,
foldingStrategy: 'auto',
showFoldingControls: 'mouseover',
matchBrackets: true,
find: {
@@ -2284,6 +2344,8 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
globalFindClipboard: false
},
colorDecorators: true,
lightbulbEnabled: true
lightbulbEnabled: true,
codeActionsOnSave: {},
codeActionsOnSaveTimeout: 750
},
};

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
export interface IEditorZoom {
onDidChangeZoomLevel: Event<number>;
@@ -16,8 +16,8 @@ export const EditorZoom: IEditorZoom = new class implements IEditorZoom {
private _zoomLevel: number = 0;
private _onDidChangeZoomLevel: Emitter<number> = new Emitter<number>();
public onDidChangeZoomLevel: Event<number> = this._onDidChangeZoomLevel.event;
private readonly _onDidChangeZoomLevel: Emitter<number> = new Emitter<number>();
public readonly onDidChangeZoomLevel: Event<number> = this._onDidChangeZoomLevel.event;
public getZoomLevel(): number {
return this._zoomLevel;

View File

@@ -19,7 +19,7 @@ import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { ITextModel, IIdentifiedSingleEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model';
function containsLineMappingChanged(events: viewEvents.ViewEvent[]): boolean {
@@ -87,6 +87,14 @@ export class CursorModelState {
export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
public static MAX_CURSOR_COUNT = 10000;
private readonly _onDidReachMaxCursorCount: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidReachMaxCursorCount: Event<void> = this._onDidReachMaxCursorCount.event;
private readonly _onDidAttemptReadOnlyEdit: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidAttemptReadOnlyEdit: Event<void> = this._onDidAttemptReadOnlyEdit.event;
private readonly _onDidChange: Emitter<CursorStateChangedEvent> = this._register(new Emitter<CursorStateChangedEvent>());
public readonly onDidChange: Event<CursorStateChangedEvent> = this._onDidChange.event;
@@ -185,6 +193,11 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
}
public setStates(source: string, reason: CursorChangeReason, states: CursorState[]): void {
if (states.length > Cursor.MAX_CURSOR_COUNT) {
states = states.slice(0, Cursor.MAX_CURSOR_COUNT);
this._onDidReachMaxCursorCount.fire(void 0);
}
const oldState = new CursorModelState(this._model, this);
this._cursors.setStates(states);
@@ -361,7 +374,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
private _interpretCommandResult(cursorState: Selection[]): void {
if (!cursorState || cursorState.length === 0) {
return;
cursorState = this._cursors.readSelectionFromMarkers();
}
this._columnSelectData = null;
@@ -456,12 +469,19 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
if (this._configuration.editor.readOnly) {
// All the remaining handlers will try to edit the model,
// but we cannot edit when read only...
this._onDidAttemptReadOnlyEdit.fire(void 0);
return;
}
const oldState = new CursorModelState(this._model, this);
let cursorChangeReason = CursorChangeReason.NotSet;
if (handlerId !== H.Undo && handlerId !== H.Redo) {
// TODO@Alex: if the undo/redo stack contains non-null selections
// it would also be OK to stop tracking selections here
this._cursors.stopTrackingSelections();
}
// ensure valid state on all cursors
this._cursors.ensureValidState();
@@ -510,6 +530,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._isHandling = false;
if (handlerId !== H.Undo && handlerId !== H.Redo) {
this._cursors.startTrackingSelections();
}
if (this._emitStateChangedIfNecessary(source, cursorChangeReason, oldState)) {
this._revealRange(RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
}

View File

@@ -31,6 +31,20 @@ export class CursorCollection {
this.killSecondaryCursors();
}
public startTrackingSelections(): void {
this.primaryCursor.startTrackingSelection(this.context);
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
this.secondaryCursors[i].startTrackingSelection(this.context);
}
}
public stopTrackingSelections(): void {
this.primaryCursor.stopTrackingSelection(this.context);
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
this.secondaryCursors[i].stopTrackingSelection(this.context);
}
}
public updateContext(context: CursorContext): void {
this.context = context;
}
@@ -193,6 +207,10 @@ export class CursorCollection {
const currentViewSelection = current.viewSelection;
const nextViewSelection = next.viewSelection;
if (!this.context.config.multiCursorMergeOverlapping) {
continue;
}
let shouldMergeCursors: boolean;
if (nextViewSelection.isEmpty() || currentViewSelection.isEmpty()) {
// Merge touching cursors if one of them is collapsed

View File

@@ -78,6 +78,7 @@ export class CursorConfiguration {
public readonly useTabStops: boolean;
public readonly wordSeparators: string;
public readonly emptySelectionClipboard: boolean;
public readonly multiCursorMergeOverlapping: boolean;
public readonly autoClosingBrackets: boolean;
public readonly autoIndent: boolean;
public readonly autoClosingPairsOpen: CharacterMap;
@@ -92,6 +93,7 @@ export class CursorConfiguration {
e.layoutInfo
|| e.wordSeparators
|| e.emptySelectionClipboard
|| e.multiCursorMergeOverlapping
|| e.autoClosingBrackets
|| e.useTabStops
|| e.lineHeight
@@ -118,6 +120,7 @@ export class CursorConfiguration {
this.useTabStops = c.useTabStops;
this.wordSeparators = c.wordSeparators;
this.emptySelectionClipboard = c.emptySelectionClipboard;
this.multiCursorMergeOverlapping = c.multiCursorMergeOverlapping;
this.autoClosingBrackets = c.autoClosingBrackets;
this.autoIndent = c.autoIndent;

View File

@@ -18,6 +18,7 @@ import { IndentAction, EnterAction } from 'vs/editor/common/modes/languageConfig
import { SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand';
import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
import { getMapForWordSeparators, WordCharacterClass } from 'vs/editor/common/controller/wordCharacterClassifier';
import { CharCode } from 'vs/base/common/charCode';
export class TypeOperations {
@@ -123,6 +124,15 @@ export class TypeOperations {
return multicursorText;
}
// Remove trailing \n if present
if (text.charCodeAt(text.length - 1) === CharCode.LineFeed) {
text = text.substr(0, text.length - 1);
}
let lines = text.split(/\r\n|\r|\n/);
if (lines.length === selections.length) {
return lines;
}
return null;
}

View File

@@ -24,6 +24,10 @@ interface IFindWordResult {
* The word type.
*/
wordType: WordType;
/**
* The reason the word ended.
*/
nextCharClass: WordCharacterClass;
}
const enum WordType {
@@ -39,9 +43,9 @@ export const enum WordNavigationType {
export class WordOperations {
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IFindWordResult {
private static _createWord(lineContent: string, wordType: WordType, nextCharClass: WordCharacterClass, start: number, end: number): IFindWordResult {
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
return { start: start, end: end, wordType: wordType };
return { start: start, end: end, wordType: wordType, nextCharClass: nextCharClass };
}
private static _findPreviousWordOnLine(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position): IFindWordResult {
@@ -57,23 +61,23 @@ export class WordOperations {
if (chClass === WordCharacterClass.Regular) {
if (wordType === WordType.Separator) {
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
return this._createWord(lineContent, wordType, chClass, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
}
wordType = WordType.Regular;
} else if (chClass === WordCharacterClass.WordSeparator) {
if (wordType === WordType.Regular) {
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
return this._createWord(lineContent, wordType, chClass, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
}
wordType = WordType.Separator;
} else if (chClass === WordCharacterClass.Whitespace) {
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
return this._createWord(lineContent, wordType, chClass, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
}
}
}
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, 0, this._findEndOfWord(lineContent, wordSeparators, wordType, 0));
return this._createWord(lineContent, wordType, WordCharacterClass.Whitespace, 0, this._findEndOfWord(lineContent, wordSeparators, wordType, 0));
}
return null;
@@ -113,23 +117,23 @@ export class WordOperations {
if (chClass === WordCharacterClass.Regular) {
if (wordType === WordType.Separator) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
return this._createWord(lineContent, wordType, chClass, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
}
wordType = WordType.Regular;
} else if (chClass === WordCharacterClass.WordSeparator) {
if (wordType === WordType.Regular) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
return this._createWord(lineContent, wordType, chClass, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
}
wordType = WordType.Separator;
} else if (chClass === WordCharacterClass.Whitespace) {
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
return this._createWord(lineContent, wordType, chClass, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
}
}
}
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, len - 1), len);
return this._createWord(lineContent, wordType, WordCharacterClass.Whitespace, this._findStartOfWord(lineContent, wordSeparators, wordType, len - 1), len);
}
return null;
@@ -167,6 +171,12 @@ export class WordOperations {
let prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, column));
if (wordNavigationType === WordNavigationType.WordStart) {
if (prevWordOnLine && prevWordOnLine.wordType === WordType.Separator) {
if (prevWordOnLine.end - prevWordOnLine.start === 1 && prevWordOnLine.nextCharClass === WordCharacterClass.Regular) {
// Skip over a word made up of one single separator and followed by a regular character
prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, prevWordOnLine.start + 1));
}
}
if (prevWordOnLine) {
column = prevWordOnLine.start + 1;
} else {
@@ -200,6 +210,12 @@ export class WordOperations {
let nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, column));
if (wordNavigationType === WordNavigationType.WordEnd) {
if (nextWordOnLine && nextWordOnLine.wordType === WordType.Separator) {
if (nextWordOnLine.end - nextWordOnLine.start === 1 && nextWordOnLine.nextCharClass === WordCharacterClass.Regular) {
// Skip over a word made up of one single separator and followed by a regular character
nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1));
}
}
if (nextWordOnLine) {
column = nextWordOnLine.end + 1;
} else {
@@ -374,20 +390,20 @@ export class WordOperations {
public static word(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, position: Position): SingleCursorState {
const wordSeparators = getMapForWordSeparators(config.wordSeparators);
let prevWord = WordOperations._findPreviousWordOnLine(wordSeparators, model, position);
let isInPrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start < position.column - 1 && position.column - 1 <= prevWord.end);
let nextWord = WordOperations._findNextWordOnLine(wordSeparators, model, position);
let isInNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start < position.column - 1 && position.column - 1 <= nextWord.end);
if (!inSelectionMode) {
// Entering word selection for the first time
const isTouchingPrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start <= position.column - 1 && position.column - 1 <= prevWord.end);
const isTouchingNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start <= position.column - 1 && position.column - 1 <= nextWord.end);
let startColumn: number;
let endColumn: number;
if (isInPrevWord) {
if (isTouchingPrevWord) {
startColumn = prevWord.start + 1;
endColumn = prevWord.end + 1;
} else if (isInNextWord) {
} else if (isTouchingNextWord) {
startColumn = nextWord.start + 1;
endColumn = nextWord.end + 1;
} else {
@@ -409,13 +425,16 @@ export class WordOperations {
);
}
const isInsidePrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start < position.column - 1 && position.column - 1 < prevWord.end);
const isInsideNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start < position.column - 1 && position.column - 1 < nextWord.end);
let startColumn: number;
let endColumn: number;
if (isInPrevWord) {
if (isInsidePrevWord) {
startColumn = prevWord.start + 1;
endColumn = prevWord.end + 1;
} else if (isInNextWord) {
} else if (isInsideNextWord) {
startColumn = nextWord.start + 1;
endColumn = nextWord.end + 1;
} else {

View File

@@ -16,12 +16,14 @@ export class OneCursor {
public viewState: SingleCursorState;
private _selTrackedRange: string;
private _trackSelection: boolean;
constructor(context: CursorContext) {
this.modelState = null;
this.viewState = null;
this._selTrackedRange = null;
this._trackSelection = true;
this._setState(
context,
@@ -31,6 +33,28 @@ export class OneCursor {
}
public dispose(context: CursorContext): void {
this._removeTrackedRange(context);
}
public startTrackingSelection(context: CursorContext): void {
this._trackSelection = true;
this._updateTrackedRange(context);
}
public stopTrackingSelection(context: CursorContext): void {
this._trackSelection = false;
this._removeTrackedRange(context);
}
private _updateTrackedRange(context: CursorContext): void {
if (!this._trackSelection) {
// don't track the selection
return;
}
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, this.modelState.selection, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
}
private _removeTrackedRange(context: CursorContext): void {
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, null, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
}
@@ -93,14 +117,9 @@ export class OneCursor {
viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns);
}
if (this.modelState && this.viewState && this.modelState.equals(modelState) && this.viewState.equals(viewState)) {
// No-op, early return
return;
}
this.modelState = modelState;
this.viewState = viewState;
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, this.modelState.selection, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
this._updateTrackedRange(context);
}
}

View File

@@ -13,6 +13,7 @@ export interface IViewLineTokens {
getEndOffset(tokenIndex: number): number;
getClassName(tokenIndex: number): string;
getInlineStyle(tokenIndex: number, colorMap: string[]): string;
findTokenIndexAtOffset(offset: number): number;
}
export class LineTokens implements IViewLineTokens {
@@ -207,4 +208,8 @@ export class SlicedLineTokens implements IViewLineTokens {
public getInlineStyle(tokenIndex: number, colorMap: string[]): string {
return this._source.getInlineStyle(this._firstTokenIndex + tokenIndex, colorMap);
}
public findTokenIndexAtOffset(offset: number): number {
return this._source.findTokenIndexAtOffset(offset + this._startOffset - this._deltaOffset) - this._firstTokenIndex;
}
}

View File

@@ -284,7 +284,7 @@ class LineChange implements ILineChange {
const originalCharSequence = originalLineSequence.getCharSequence(diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);
const modifiedCharSequence = modifiedLineSequence.getCharSequence(diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueProcessingPredicate, false);
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueProcessingPredicate, true);
if (shouldPostProcessCharChanges) {
rawChanges = postProcessCharChanges(rawChanges);

View File

@@ -199,9 +199,13 @@ export interface ICursorState {
* A (serializable) state of the view.
*/
export interface IViewState {
scrollTop: number;
scrollTopWithoutViewZones: number;
/** written by previous versions */
scrollTop?: number;
/** written by previous versions */
scrollTopWithoutViewZones?: number;
scrollLeft: number;
firstPosition: IPosition;
firstPositionDeltaTop: number;
}
/**
* A (serializable) state of the code editor.
@@ -505,6 +509,7 @@ export interface IThemeDecorationRenderOptions {
textDecoration?: string;
cursor?: string;
color?: string | ThemeColor;
opacity?: number;
letterSpacing?: string;
gutterIconPath?: string | UriComponents;

View File

@@ -10,12 +10,17 @@ export namespace EditorContextKeys {
/**
* A context key that is set when the editor's text has focus (cursor is blinking).
*/
export const textFocus = new RawContextKey<boolean>('editorTextFocus', false);
export const editorTextFocus = new RawContextKey<boolean>('editorTextFocus', false);
/**
* A context key that is set when the editor's text or an editor's widget has focus.
*/
export const focus = new RawContextKey<boolean>('editorFocus', false);
/**
* A context key that is set when any editor input has focus (regular editor, repl input...).
*/
export const textInputFocus = new RawContextKey<boolean>('textInputFocus', false);
export const readOnly = new RawContextKey<boolean>('editorReadonly', false);
export const writable: ContextKeyExpr = readOnly.toNegated();
export const hasNonEmptySelection = new RawContextKey<boolean>('editorHasSelection', false);

View File

@@ -15,6 +15,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { ModelRawContentChangedEvent, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelLanguageConfigurationChangedEvent, IModelTokensChangedEvent, IModelContentChange } from 'vs/editor/common/model/textModelEvents';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { ITextSnapshot } from 'vs/platform/files/common/files';
import { SearchData } from 'vs/editor/common/model/textModelSearch';
/**
* Vertical Lane in the overview ruler of the editor.
@@ -80,7 +81,12 @@ export interface IModelDecorationOptions {
* Always render the decoration (even when the range it encompasses is collapsed).
* @internal
*/
readonly showIfCollapsed?: boolean;
showIfCollapsed?: boolean;
/**
* Specifies the stack order of a decoration.
* A decoration with greater stack order is always in front of a decoration with a lower stack order.
*/
zIndex?: number;
/**
* If set, render this decoration in the overview ruler.
*/
@@ -103,6 +109,10 @@ export interface IModelDecorationOptions {
* to have a background color decoration.
*/
inlineClassName?: string;
/**
* If there is an `inlineClassName` which affects letter spacing.
*/
inlineClassNameAffectsLetterSpacing?: boolean;
/**
* If set, the decoration will be rendered before the text with this CSS class name.
*/
@@ -389,6 +399,8 @@ export interface ITextModelCreationOptions {
detectIndentation: boolean;
trimAutoWhitespace: boolean;
defaultEOL: DefaultEndOfLine;
isForSimpleWidget: boolean;
largeFileOptimizations: boolean;
}
export interface ITextModelUpdateOptions {
@@ -433,6 +445,15 @@ export enum TrackedRangeStickiness {
GrowsOnlyWhenTypingAfter = 3,
}
/**
* @internal
*/
export interface IActiveIndentGuideInfo {
startLineNumber: number;
endLineNumber: number;
indent: number;
}
/**
* A model.
*/
@@ -448,6 +469,12 @@ export interface ITextModel {
*/
readonly id: string;
/**
* This model is constructed for a simple widget code editor.
* @internal
*/
readonly isForSimpleWidget: boolean;
/**
* If true, the text model might contain RTL.
* If false, the text model **contains only** contain LTR.
@@ -553,6 +580,10 @@ export interface ITextModel {
*/
getLineContent(lineNumber: number): string;
/**
* Get the text length for a certain line.
*/
getLineLength(lineNumber: number): number;
/**
* Get the text for all lines.
@@ -643,11 +674,16 @@ export interface ITextModel {
isDisposed(): boolean;
/**
* Only basic mode supports allowed on this model because it is simply too large.
* (tokenization is allowed and other basic supports)
* @internal
*/
isTooLargeForHavingARichMode(): boolean;
tokenizeViewport(startLineNumber: number, endLineNumber: number): void;
/**
* This model is so large that it would not be a good idea to sync it over
* to web workers or other places.
* @internal
*/
isTooLargeForSyncing(): boolean;
/**
* The file is so large, that even tokenization is disabled.
@@ -829,6 +865,11 @@ export interface ITextModel {
*/
matchBracket(position: IPosition): [Range, Range];
/**
* @internal
*/
getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
/**
* @internal
*/
@@ -989,6 +1030,13 @@ export interface ITextModel {
*/
redo(): Selection[];
/**
* @deprecated Please use `onDidChangeContent` instead.
* An event emitted when the contents of the model have changed.
* @internal
* @event
*/
onDidChangeRawContentFast(listener: (e: ModelRawContentChangedEvent) => void): IDisposable;
/**
* @deprecated Please use `onDidChangeContent` instead.
* An event emitted when the contents of the model have changed.
@@ -1054,6 +1102,12 @@ export interface ITextModel {
* @internal
*/
isAttachedToEditor(): boolean;
/**
* Returns the count of editors this model is attached to.
* @internal
*/
getAttachedEditorCount(): number;
}
/**
@@ -1100,6 +1154,7 @@ export interface ITextBuffer {
setEOL(newEOL: '\r\n' | '\n'): void;
applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult;
findMatchesLineByLine?(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[];
}
/**
@@ -1120,6 +1175,5 @@ export class ApplyEditsResult {
*/
export interface IInternalModelContentChange extends IModelContentChange {
range: Range;
rangeOffset: number;
forceMoveMarkers: boolean;
}

View File

@@ -1,323 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { CharCode } from 'vs/base/common/charCode';
export class LeafOffsetLenEdit {
constructor(
public readonly start: number,
public readonly length: number,
public readonly text: string
) { }
}
export class BufferPiece {
private readonly _str: string;
public get text(): string { return this._str; }
private readonly _lineStarts: Uint32Array;
constructor(str: string, lineStarts: Uint32Array = null) {
this._str = str;
if (lineStarts === null) {
this._lineStarts = createLineStartsFast(str);
} else {
this._lineStarts = lineStarts;
}
}
public length(): number {
return this._str.length;
}
public newLineCount(): number {
return this._lineStarts.length;
}
public lineStartFor(relativeLineIndex: number): number {
return this._lineStarts[relativeLineIndex];
}
public charCodeAt(index: number): number {
return this._str.charCodeAt(index);
}
public substr(from: number, length: number): string {
return this._str.substr(from, length);
}
public findLineStartBeforeOffset(offset: number): number {
if (this._lineStarts.length === 0 || offset < this._lineStarts[0]) {
return -1;
}
let low = 0, high = this._lineStarts.length - 1;
while (low < high) {
let mid = low + Math.ceil((high - low) / 2);
let lineStart = this._lineStarts[mid];
if (offset === lineStart) {
return mid;
} else if (offset < lineStart) {
high = mid - 1;
} else {
low = mid;
}
}
return low;
}
public findLineFirstNonWhitespaceIndex(searchStartOffset: number): number {
for (let i = searchStartOffset, len = this._str.length; i < len; i++) {
const chCode = this._str.charCodeAt(i);
if (chCode === CharCode.CarriageReturn || chCode === CharCode.LineFeed) {
// Reached EOL
return -2;
}
if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
return i;
}
}
return -1;
}
public findLineLastNonWhitespaceIndex(searchStartOffset: number): number {
for (let i = searchStartOffset - 1; i >= 0; i--) {
const chCode = this._str.charCodeAt(i);
if (chCode === CharCode.CarriageReturn || chCode === CharCode.LineFeed) {
// Reached EOL
return -2;
}
if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
return i;
}
}
return -1;
}
public static normalizeEOL(target: BufferPiece, eol: '\r\n' | '\n'): BufferPiece {
return new BufferPiece(target._str.replace(/\r\n|\r|\n/g, eol));
}
public static deleteLastChar(target: BufferPiece): BufferPiece {
const targetCharsLength = target.length();
const targetLineStartsLength = target.newLineCount();
const targetLineStarts = target._lineStarts;
let newLineStartsLength;
if (targetLineStartsLength > 0 && targetLineStarts[targetLineStartsLength - 1] === targetCharsLength) {
newLineStartsLength = targetLineStartsLength - 1;
} else {
newLineStartsLength = targetLineStartsLength;
}
let newLineStarts = new Uint32Array(newLineStartsLength);
newLineStarts.set(targetLineStarts);
return new BufferPiece(
target._str.substr(0, targetCharsLength - 1),
newLineStarts
);
}
public static insertFirstChar(target: BufferPiece, character: number): BufferPiece {
const targetLineStartsLength = target.newLineCount();
const targetLineStarts = target._lineStarts;
const insertLineStart = ((character === CharCode.CarriageReturn && (targetLineStartsLength === 0 || targetLineStarts[0] !== 1 || target.charCodeAt(0) !== CharCode.LineFeed)) || (character === CharCode.LineFeed));
const newLineStartsLength = (insertLineStart ? targetLineStartsLength + 1 : targetLineStartsLength);
let newLineStarts = new Uint32Array(newLineStartsLength);
if (insertLineStart) {
newLineStarts[0] = 1;
for (let i = 0; i < targetLineStartsLength; i++) {
newLineStarts[i + 1] = targetLineStarts[i] + 1;
}
} else {
for (let i = 0; i < targetLineStartsLength; i++) {
newLineStarts[i] = targetLineStarts[i] + 1;
}
}
return new BufferPiece(
String.fromCharCode(character) + target._str,
newLineStarts
);
}
public static join(first: BufferPiece, second: BufferPiece): BufferPiece {
const firstCharsLength = first._str.length;
const firstLineStartsLength = first._lineStarts.length;
const secondLineStartsLength = second._lineStarts.length;
const firstLineStarts = first._lineStarts;
const secondLineStarts = second._lineStarts;
const newLineStartsLength = firstLineStartsLength + secondLineStartsLength;
let newLineStarts = new Uint32Array(newLineStartsLength);
newLineStarts.set(firstLineStarts, 0);
for (let i = 0; i < secondLineStartsLength; i++) {
newLineStarts[i + firstLineStartsLength] = secondLineStarts[i] + firstCharsLength;
}
return new BufferPiece(first._str + second._str, newLineStarts);
}
public static replaceOffsetLen(target: BufferPiece, edits: LeafOffsetLenEdit[], idealLeafLength: number, maxLeafLength: number, result: BufferPiece[]): void {
const editsSize = edits.length;
const originalCharsLength = target.length();
if (editsSize === 1 && edits[0].text.length === 0 && edits[0].start === 0 && edits[0].length === originalCharsLength) {
// special case => deleting everything
return;
}
let pieces: string[] = new Array<string>(2 * editsSize + 1);
let originalFromIndex = 0;
let piecesTextLength = 0;
for (let i = 0; i < editsSize; i++) {
const edit = edits[i];
const originalText = target._str.substr(originalFromIndex, edit.start - originalFromIndex);
pieces[2 * i] = originalText;
piecesTextLength += originalText.length;
originalFromIndex = edit.start + edit.length;
pieces[2 * i + 1] = edit.text;
piecesTextLength += edit.text.length;
}
// maintain the chars that survive to the right of the last edit
let text = target._str.substr(originalFromIndex, originalCharsLength - originalFromIndex);
pieces[2 * editsSize] = text;
piecesTextLength += text.length;
let targetDataLength = piecesTextLength > maxLeafLength ? idealLeafLength : piecesTextLength;
let targetDataOffset = 0;
let data: string = '';
for (let pieceIndex = 0, pieceCount = pieces.length; pieceIndex < pieceCount; pieceIndex++) {
const pieceText = pieces[pieceIndex];
const pieceLength = pieceText.length;
if (pieceLength === 0) {
continue;
}
let pieceOffset = 0;
while (pieceOffset < pieceLength) {
if (targetDataOffset >= targetDataLength) {
result.push(new BufferPiece(data));
targetDataLength = piecesTextLength > maxLeafLength ? idealLeafLength : piecesTextLength;
targetDataOffset = 0;
data = '';
}
let writingCnt = min(pieceLength - pieceOffset, targetDataLength - targetDataOffset);
data += pieceText.substr(pieceOffset, writingCnt);
pieceOffset += writingCnt;
targetDataOffset += writingCnt;
piecesTextLength -= writingCnt;
// check that the buffer piece does not end in a \r or high surrogate
if (targetDataOffset === targetDataLength && piecesTextLength > 0) {
const lastChar = data.charCodeAt(targetDataLength - 1);
if (lastChar === CharCode.CarriageReturn || (0xD800 <= lastChar && lastChar <= 0xDBFF)) {
// move lastChar over to next buffer piece
targetDataLength -= 1;
pieceOffset -= 1;
targetDataOffset -= 1;
piecesTextLength += 1;
data = data.substr(0, data.length - 1);
}
}
}
}
result.push(new BufferPiece(data));
}
}
function min(a: number, b: number): number {
return (a < b ? a : b);
}
export function createUint32Array(arr: number[]): Uint32Array {
let r = new Uint32Array(arr.length);
r.set(arr, 0);
return r;
}
export class LineStarts {
constructor(
public readonly lineStarts: Uint32Array,
public readonly cr: number,
public readonly lf: number,
public readonly crlf: number,
public readonly isBasicASCII: boolean
) { }
}
export function createLineStartsFast(str: string): Uint32Array {
let r: number[] = [], rLength = 0;
for (let i = 0, len = str.length; i < len; i++) {
const chr = str.charCodeAt(i);
if (chr === CharCode.CarriageReturn) {
if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) {
// \r\n... case
r[rLength++] = i + 2;
i++; // skip \n
} else {
// \r... case
r[rLength++] = i + 1;
}
} else if (chr === CharCode.LineFeed) {
r[rLength++] = i + 1;
}
}
return createUint32Array(r);
}
export function createLineStarts(r: number[], str: string): LineStarts {
r.length = 0;
let rLength = 0;
let cr = 0, lf = 0, crlf = 0;
let isBasicASCII = true;
for (let i = 0, len = str.length; i < len; i++) {
const chr = str.charCodeAt(i);
if (chr === CharCode.CarriageReturn) {
if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) {
// \r\n... case
crlf++;
r[rLength++] = i + 2;
i++; // skip \n
} else {
cr++;
// \r... case
r[rLength++] = i + 1;
}
} else if (chr === CharCode.LineFeed) {
lf++;
r[rLength++] = i + 1;
} else {
if (isBasicASCII) {
if (chr !== CharCode.Tab && (chr < 32 || chr > 126)) {
isBasicASCII = false;
}
}
}
}
const result = new LineStarts(createUint32Array(r), cr, lf, crlf, isBasicASCII);
r.length = 0;
return result;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,186 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as strings from 'vs/base/common/strings';
import { ITextBufferBuilder, ITextBufferFactory, ITextBuffer, DefaultEndOfLine } from 'vs/editor/common/model';
import { BufferPiece, createLineStarts } from 'vs/editor/common/model/chunksTextBuffer/bufferPiece';
import { ChunksTextBuffer } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBuffer';
import { CharCode } from 'vs/base/common/charCode';
export class TextBufferFactory implements ITextBufferFactory {
constructor(
private readonly _pieces: BufferPiece[],
private readonly _averageChunkSize: number,
private readonly _BOM: string,
private readonly _cr: number,
private readonly _lf: number,
private readonly _crlf: number,
private readonly _containsRTL: boolean,
private readonly _isBasicASCII: boolean,
) {
}
/**
* if text source is empty or with precisely one line, returns null. No end of line is detected.
* if text source contains more lines ending with '\r\n', returns '\r\n'.
* Otherwise returns '\n'. More lines end with '\n'.
*/
private _getEOL(defaultEOL: DefaultEndOfLine): '\r\n' | '\n' {
const totalEOLCount = this._cr + this._lf + this._crlf;
const totalCRCount = this._cr + this._crlf;
if (totalEOLCount === 0) {
// This is an empty file or a file with precisely one line
return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n');
}
if (totalCRCount > totalEOLCount / 2) {
// More than half of the file contains \r\n ending lines
return '\r\n';
}
// At least one line more ends in \n
return '\n';
}
public create(defaultEOL: DefaultEndOfLine): ITextBuffer {
const eol = this._getEOL(defaultEOL);
let pieces = this._pieces;
if (
(eol === '\r\n' && (this._cr > 0 || this._lf > 0))
|| (eol === '\n' && (this._cr > 0 || this._crlf > 0))
) {
// Normalize pieces
for (let i = 0, len = pieces.length; i < len; i++) {
pieces[i] = BufferPiece.normalizeEOL(pieces[i], eol);
}
}
return new ChunksTextBuffer(pieces, this._averageChunkSize, this._BOM, eol, this._containsRTL, this._isBasicASCII);
}
public getFirstLineText(lengthLimit: number): string {
const firstPiece = this._pieces[0];
if (firstPiece.newLineCount() === 0) {
return firstPiece.substr(0, lengthLimit);
}
const firstEOLOffset = firstPiece.lineStartFor(0);
return firstPiece.substr(0, Math.min(lengthLimit, firstEOLOffset));
}
}
export class ChunksTextBufferBuilder implements ITextBufferBuilder {
private _rawPieces: BufferPiece[];
private _hasPreviousChar: boolean;
private _previousChar: number;
private _averageChunkSize: number;
private _tmpLineStarts: number[];
private BOM: string;
private cr: number;
private lf: number;
private crlf: number;
private containsRTL: boolean;
private isBasicASCII: boolean;
constructor() {
this._rawPieces = [];
this._hasPreviousChar = false;
this._previousChar = 0;
this._averageChunkSize = 0;
this._tmpLineStarts = [];
this.BOM = '';
this.cr = 0;
this.lf = 0;
this.crlf = 0;
this.containsRTL = false;
this.isBasicASCII = true;
}
public acceptChunk(chunk: string): void {
if (chunk.length === 0) {
return;
}
if (this._rawPieces.length === 0) {
if (strings.startsWithUTF8BOM(chunk)) {
this.BOM = strings.UTF8_BOM_CHARACTER;
chunk = chunk.substr(1);
}
}
this._averageChunkSize = (this._averageChunkSize * this._rawPieces.length + chunk.length) / (this._rawPieces.length + 1);
const lastChar = chunk.charCodeAt(chunk.length - 1);
if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xd800 && lastChar <= 0xdbff)) {
// last character is \r or a high surrogate => keep it back
this._acceptChunk1(chunk.substr(0, chunk.length - 1), false);
this._hasPreviousChar = true;
this._previousChar = lastChar;
} else {
this._acceptChunk1(chunk, false);
this._hasPreviousChar = false;
this._previousChar = lastChar;
}
}
private _acceptChunk1(chunk: string, allowEmptyStrings: boolean): void {
if (!allowEmptyStrings && chunk.length === 0) {
// Nothing to do
return;
}
if (this._hasPreviousChar) {
this._acceptChunk2(chunk + String.fromCharCode(this._previousChar));
} else {
this._acceptChunk2(chunk);
}
}
private _acceptChunk2(chunk: string): void {
const lineStarts = createLineStarts(this._tmpLineStarts, chunk);
this._rawPieces.push(new BufferPiece(chunk, lineStarts.lineStarts));
this.cr += lineStarts.cr;
this.lf += lineStarts.lf;
this.crlf += lineStarts.crlf;
if (this.isBasicASCII) {
this.isBasicASCII = lineStarts.isBasicASCII;
}
if (!this.isBasicASCII && !this.containsRTL) {
// No need to check if is basic ASCII
this.containsRTL = strings.containsRTL(chunk);
}
}
public finish(): TextBufferFactory {
this._finish();
return new TextBufferFactory(this._rawPieces, this._averageChunkSize, this.BOM, this.cr, this.lf, this.crlf, this.containsRTL, this.isBasicASCII);
}
private _finish(): void {
if (this._rawPieces.length === 0) {
// no chunks => forcefully go through accept chunk
this._acceptChunk1('', true);
return;
}
if (this._hasPreviousChar) {
this._hasPreviousChar = false;
// recreate last chunk
const lastPiece = this._rawPieces[this._rawPieces.length - 1];
const tmp = new BufferPiece(String.fromCharCode(this._previousChar));
const newLastPiece = BufferPiece.join(lastPiece, tmp);
this._rawPieces[this._rawPieces.length - 1] = newLastPiece;
if (this._previousChar === CharCode.CarriageReturn) {
this.cr++;
}
}
}
}

View File

@@ -12,13 +12,11 @@ import { IModelDecoration } from 'vs/editor/common/model';
// The red-black tree is based on the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
//
/**
* The class name sort order must match the severity order. Highest severity last.
*/
export const ClassName = {
EditorInfoDecoration: 'squiggly-a-info',
EditorWarningDecoration: 'squiggly-b-warning',
EditorErrorDecoration: 'squiggly-c-error'
EditorHintDecoration: 'squiggly-hint',
EditorInfoDecoration: 'squiggly-info',
EditorWarningDecoration: 'squiggly-warning',
EditorErrorDecoration: 'squiggly-error'
};
/**

View File

@@ -1,660 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import * as strings from 'vs/base/common/strings';
import * as arrays from 'vs/base/common/arrays';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ISingleEditOperationIdentifier, IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange } from 'vs/editor/common/model';
import { ITextSnapshot } from 'vs/platform/files/common/files';
export interface IValidatedEditOperation {
sortIndex: number;
identifier: ISingleEditOperationIdentifier;
range: Range;
rangeOffset: number;
rangeLength: number;
lines: string[];
forceMoveMarkers: boolean;
isAutoWhitespaceEdit: boolean;
}
/**
* A processed string with its EOL resolved ready to be turned into an editor model.
*/
export interface ITextSource {
/**
* The text split into lines.
*/
readonly lines: string[];
/**
* The BOM (leading character sequence of the file).
*/
readonly BOM: string;
/**
* The end of line sequence.
*/
readonly EOL: string;
/**
* The text contains Unicode characters classified as "R" or "AL".
*/
readonly containsRTL: boolean;
/**
* The text contains only characters inside the ASCII range 32-126 or \t \r \n
*/
readonly isBasicASCII: boolean;
}
class LinesTextBufferSnapshot implements ITextSnapshot {
private readonly _lines: string[];
private readonly _linesLength: number;
private readonly _eol: string;
private readonly _bom: string;
private _lineIndex: number;
constructor(lines: string[], eol: string, bom: string) {
this._lines = lines;
this._linesLength = this._lines.length;
this._eol = eol;
this._bom = bom;
this._lineIndex = 0;
}
public read(): string {
if (this._lineIndex >= this._linesLength) {
return null;
}
let result: string = null;
if (this._lineIndex === 0) {
result = this._bom + this._lines[this._lineIndex];
} else {
result = this._lines[this._lineIndex];
}
this._lineIndex++;
if (this._lineIndex < this._linesLength) {
result += this._eol;
}
return result;
}
}
export class LinesTextBuffer implements ITextBuffer {
private _lines: string[];
private _BOM: string;
private _EOL: string;
private _mightContainRTL: boolean;
private _mightContainNonBasicASCII: boolean;
private _lineStarts: PrefixSumComputer;
constructor(textSource: ITextSource) {
this._lines = textSource.lines.slice(0);
this._BOM = textSource.BOM;
this._EOL = textSource.EOL;
this._mightContainRTL = textSource.containsRTL;
this._mightContainNonBasicASCII = !textSource.isBasicASCII;
this._constructLineStarts();
}
private _constructLineStarts(): void {
const eolLength = this._EOL.length;
const linesLength = this._lines.length;
const lineStartValues = new Uint32Array(linesLength);
for (let i = 0; i < linesLength; i++) {
lineStartValues[i] = this._lines[i].length + eolLength;
}
this._lineStarts = new PrefixSumComputer(lineStartValues);
}
public equals(other: ITextBuffer): boolean {
if (!(other instanceof LinesTextBuffer)) {
return false;
}
if (this._BOM !== other._BOM) {
return false;
}
if (this._EOL !== other._EOL) {
return false;
}
if (this._lines.length !== other._lines.length) {
return false;
}
for (let i = 0, len = this._lines.length; i < len; i++) {
if (this._lines[i] !== other._lines[i]) {
return false;
}
}
return true;
}
public mightContainRTL(): boolean {
return this._mightContainRTL;
}
public mightContainNonBasicASCII(): boolean {
return this._mightContainNonBasicASCII;
}
public getBOM(): string {
return this._BOM;
}
public getEOL(): string {
return this._EOL;
}
public getOffsetAt(lineNumber: number, column: number): number {
return this._lineStarts.getAccumulatedValue(lineNumber - 2) + column - 1;
}
public getPositionAt(offset: number): Position {
offset = Math.floor(offset);
offset = Math.max(0, offset);
let out = this._lineStarts.getIndexOf(offset);
let lineLength = this._lines[out.index].length;
// Ensure we return a valid position
return new Position(out.index + 1, Math.min(out.remainder + 1, lineLength + 1));
}
public getRangeAt(offset: number, length: number): Range {
const startResult = this._lineStarts.getIndexOf(offset);
const startLineLength = this._lines[startResult.index].length;
const startColumn = Math.min(startResult.remainder + 1, startLineLength + 1);
const endResult = this._lineStarts.getIndexOf(offset + length);
const endLineLength = this._lines[endResult.index].length;
const endColumn = Math.min(endResult.remainder + 1, endLineLength + 1);
return new Range(startResult.index + 1, startColumn, endResult.index + 1, endColumn);
}
private _getEndOfLine(eol: EndOfLinePreference): string {
switch (eol) {
case EndOfLinePreference.LF:
return '\n';
case EndOfLinePreference.CRLF:
return '\r\n';
case EndOfLinePreference.TextDefined:
return this.getEOL();
}
throw new Error('Unknown EOL preference');
}
public getValueInRange(range: Range, eol: EndOfLinePreference): string {
if (range.isEmpty()) {
return '';
}
if (range.startLineNumber === range.endLineNumber) {
return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1);
}
const lineEnding = this._getEndOfLine(eol);
const startLineIndex = range.startLineNumber - 1;
const endLineIndex = range.endLineNumber - 1;
let resultLines: string[] = [];
resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1));
for (let i = startLineIndex + 1; i < endLineIndex; i++) {
resultLines.push(this._lines[i]);
}
resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1));
return resultLines.join(lineEnding);
}
public createSnapshot(preserveBOM: boolean): ITextSnapshot {
return new LinesTextBufferSnapshot(this._lines.slice(0), this._EOL, preserveBOM ? this._BOM : '');
}
public getValueLengthInRange(range: Range, eol: EndOfLinePreference): number {
if (range.isEmpty()) {
return 0;
}
if (range.startLineNumber === range.endLineNumber) {
return (range.endColumn - range.startColumn);
}
let startOffset = this.getOffsetAt(range.startLineNumber, range.startColumn);
let endOffset = this.getOffsetAt(range.endLineNumber, range.endColumn);
return endOffset - startOffset;
}
public getLineCount(): number {
return this._lines.length;
}
public getLinesContent(): string[] {
return this._lines.slice(0);
}
public getLength(): number {
return this._lineStarts.getTotalValue();
}
public getLineContent(lineNumber: number): string {
return this._lines[lineNumber - 1];
}
public getLineCharCode(lineNumber: number, index: number): number {
return this._lines[lineNumber - 1].charCodeAt(index);
}
public getLineLength(lineNumber: number): number {
return this._lines[lineNumber - 1].length;
}
public getLineFirstNonWhitespaceColumn(lineNumber: number): number {
const result = strings.firstNonWhitespaceIndex(this._lines[lineNumber - 1]);
if (result === -1) {
return 0;
}
return result + 1;
}
public getLineLastNonWhitespaceColumn(lineNumber: number): number {
const result = strings.lastNonWhitespaceIndex(this._lines[lineNumber - 1]);
if (result === -1) {
return 0;
}
return result + 2;
}
//#region Editing
public setEOL(newEOL: '\r\n' | '\n'): void {
this._EOL = newEOL;
this._constructLineStarts();
}
private static _sortOpsAscending(a: IValidatedEditOperation, b: IValidatedEditOperation): number {
let r = Range.compareRangesUsingEnds(a.range, b.range);
if (r === 0) {
return a.sortIndex - b.sortIndex;
}
return r;
}
private static _sortOpsDescending(a: IValidatedEditOperation, b: IValidatedEditOperation): number {
let r = Range.compareRangesUsingEnds(a.range, b.range);
if (r === 0) {
return b.sortIndex - a.sortIndex;
}
return -r;
}
public applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult {
if (rawOperations.length === 0) {
return new ApplyEditsResult([], [], []);
}
let mightContainRTL = this._mightContainRTL;
let mightContainNonBasicASCII = this._mightContainNonBasicASCII;
let canReduceOperations = true;
let operations: IValidatedEditOperation[] = [];
for (let i = 0; i < rawOperations.length; i++) {
let op = rawOperations[i];
if (canReduceOperations && op._isTracked) {
canReduceOperations = false;
}
let validatedRange = op.range;
if (!mightContainRTL && op.text) {
// check if the new inserted text contains RTL
mightContainRTL = strings.containsRTL(op.text);
}
if (!mightContainNonBasicASCII && op.text) {
mightContainNonBasicASCII = !strings.isBasicASCII(op.text);
}
operations[i] = {
sortIndex: i,
identifier: op.identifier || null,
range: validatedRange,
rangeOffset: this.getOffsetAt(validatedRange.startLineNumber, validatedRange.startColumn),
rangeLength: this.getValueLengthInRange(validatedRange, EndOfLinePreference.TextDefined),
lines: op.text ? op.text.split(/\r\n|\r|\n/) : null,
forceMoveMarkers: op.forceMoveMarkers || false,
isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false
};
}
// Sort operations ascending
operations.sort(LinesTextBuffer._sortOpsAscending);
for (let i = 0, count = operations.length - 1; i < count; i++) {
let rangeEnd = operations[i].range.getEndPosition();
let nextRangeStart = operations[i + 1].range.getStartPosition();
if (nextRangeStart.isBefore(rangeEnd)) {
// overlapping ranges
throw new Error('Overlapping ranges are not allowed!');
}
}
if (canReduceOperations) {
operations = this._reduceOperations(operations);
}
// Delta encode operations
let reverseRanges = LinesTextBuffer._getInverseEditRanges(operations);
let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = [];
for (let i = 0; i < operations.length; i++) {
let op = operations[i];
let reverseRange = reverseRanges[i];
if (recordTrimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) {
// Record already the future line numbers that might be auto whitespace removal candidates on next edit
for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) {
let currentLineContent = '';
if (lineNumber === reverseRange.startLineNumber) {
currentLineContent = this.getLineContent(op.range.startLineNumber);
if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) {
continue;
}
}
newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent });
}
}
}
let reverseOperations: IIdentifiedSingleEditOperation[] = [];
for (let i = 0; i < operations.length; i++) {
let op = operations[i];
let reverseRange = reverseRanges[i];
reverseOperations[i] = {
identifier: op.identifier,
range: reverseRange,
text: this.getValueInRange(op.range, EndOfLinePreference.TextDefined),
forceMoveMarkers: op.forceMoveMarkers
};
}
this._mightContainRTL = mightContainRTL;
this._mightContainNonBasicASCII = mightContainNonBasicASCII;
const contentChanges = this._doApplyEdits(operations);
let trimAutoWhitespaceLineNumbers: number[] = null;
if (recordTrimAutoWhitespace && newTrimAutoWhitespaceCandidates.length > 0) {
// sort line numbers auto whitespace removal candidates for next edit descending
newTrimAutoWhitespaceCandidates.sort((a, b) => b.lineNumber - a.lineNumber);
trimAutoWhitespaceLineNumbers = [];
for (let i = 0, len = newTrimAutoWhitespaceCandidates.length; i < len; i++) {
let lineNumber = newTrimAutoWhitespaceCandidates[i].lineNumber;
if (i > 0 && newTrimAutoWhitespaceCandidates[i - 1].lineNumber === lineNumber) {
// Do not have the same line number twice
continue;
}
let prevContent = newTrimAutoWhitespaceCandidates[i].oldContent;
let lineContent = this.getLineContent(lineNumber);
if (lineContent.length === 0 || lineContent === prevContent || strings.firstNonWhitespaceIndex(lineContent) !== -1) {
continue;
}
trimAutoWhitespaceLineNumbers.push(lineNumber);
}
}
return new ApplyEditsResult(
reverseOperations,
contentChanges,
trimAutoWhitespaceLineNumbers
);
}
/**
* Transform operations such that they represent the same logic edit,
* but that they also do not cause OOM crashes.
*/
private _reduceOperations(operations: IValidatedEditOperation[]): IValidatedEditOperation[] {
if (operations.length < 1000) {
// We know from empirical testing that a thousand edits work fine regardless of their shape.
return operations;
}
// At one point, due to how events are emitted and how each operation is handled,
// some operations can trigger a high ammount of temporary string allocations,
// that will immediately get edited again.
// e.g. a formatter inserting ridiculous ammounts of \n on a model with a single line
// Therefore, the strategy is to collapse all the operations into a huge single edit operation
return [this._toSingleEditOperation(operations)];
}
_toSingleEditOperation(operations: IValidatedEditOperation[]): IValidatedEditOperation {
let forceMoveMarkers = false,
firstEditRange = operations[0].range,
lastEditRange = operations[operations.length - 1].range,
entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn),
lastEndLineNumber = firstEditRange.startLineNumber,
lastEndColumn = firstEditRange.startColumn,
result: string[] = [];
for (let i = 0, len = operations.length; i < len; i++) {
let operation = operations[i],
range = operation.range;
forceMoveMarkers = forceMoveMarkers || operation.forceMoveMarkers;
// (1) -- Push old text
for (let lineNumber = lastEndLineNumber; lineNumber < range.startLineNumber; lineNumber++) {
if (lineNumber === lastEndLineNumber) {
result.push(this._lines[lineNumber - 1].substring(lastEndColumn - 1));
} else {
result.push('\n');
result.push(this._lines[lineNumber - 1]);
}
}
if (range.startLineNumber === lastEndLineNumber) {
result.push(this._lines[range.startLineNumber - 1].substring(lastEndColumn - 1, range.startColumn - 1));
} else {
result.push('\n');
result.push(this._lines[range.startLineNumber - 1].substring(0, range.startColumn - 1));
}
// (2) -- Push new text
if (operation.lines) {
for (let j = 0, lenJ = operation.lines.length; j < lenJ; j++) {
if (j !== 0) {
result.push('\n');
}
result.push(operation.lines[j]);
}
}
lastEndLineNumber = operation.range.endLineNumber;
lastEndColumn = operation.range.endColumn;
}
return {
sortIndex: 0,
identifier: operations[0].identifier,
range: entireEditRange,
rangeOffset: this.getOffsetAt(entireEditRange.startLineNumber, entireEditRange.startColumn),
rangeLength: this.getValueLengthInRange(entireEditRange, EndOfLinePreference.TextDefined),
lines: result.join('').split('\n'),
forceMoveMarkers: forceMoveMarkers,
isAutoWhitespaceEdit: false
};
}
private _setLineContent(lineNumber: number, content: string): void {
this._lines[lineNumber - 1] = content;
this._lineStarts.changeValue(lineNumber - 1, content.length + this._EOL.length);
}
private _doApplyEdits(operations: IValidatedEditOperation[]): IInternalModelContentChange[] {
// Sort operations descending
operations.sort(LinesTextBuffer._sortOpsDescending);
let contentChanges: IInternalModelContentChange[] = [];
for (let i = 0, len = operations.length; i < len; i++) {
const op = operations[i];
const startLineNumber = op.range.startLineNumber;
const startColumn = op.range.startColumn;
const endLineNumber = op.range.endLineNumber;
const endColumn = op.range.endColumn;
if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.lines || op.lines.length === 0)) {
// no-op
continue;
}
const deletingLinesCnt = endLineNumber - startLineNumber;
const insertingLinesCnt = (op.lines ? op.lines.length - 1 : 0);
const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);
for (let j = editingLinesCnt; j >= 0; j--) {
const editLineNumber = startLineNumber + j;
let editText = (op.lines ? op.lines[j] : '');
if (editLineNumber === startLineNumber || editLineNumber === endLineNumber) {
const editStartColumn = (editLineNumber === startLineNumber ? startColumn : 1);
const editEndColumn = (editLineNumber === endLineNumber ? endColumn : this.getLineLength(editLineNumber) + 1);
editText = (
this._lines[editLineNumber - 1].substring(0, editStartColumn - 1)
+ editText
+ this._lines[editLineNumber - 1].substring(editEndColumn - 1)
);
}
this._setLineContent(editLineNumber, editText);
}
if (editingLinesCnt < deletingLinesCnt) {
// Must delete some lines
const spliceStartLineNumber = startLineNumber + editingLinesCnt;
const endLineRemains = this._lines[endLineNumber - 1].substring(endColumn - 1);
// Reconstruct first line
this._setLineContent(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1] + endLineRemains);
this._lines.splice(spliceStartLineNumber, endLineNumber - spliceStartLineNumber);
this._lineStarts.removeValues(spliceStartLineNumber, endLineNumber - spliceStartLineNumber);
}
if (editingLinesCnt < insertingLinesCnt) {
// Must insert some lines
const spliceLineNumber = startLineNumber + editingLinesCnt;
let spliceColumn = (spliceLineNumber === startLineNumber ? startColumn : 1);
if (op.lines) {
spliceColumn += op.lines[editingLinesCnt].length;
}
// Split last line
const leftoverLine = this._lines[spliceLineNumber - 1].substring(spliceColumn - 1);
this._setLineContent(spliceLineNumber, this._lines[spliceLineNumber - 1].substring(0, spliceColumn - 1));
// Lines in the middle
let newLines: string[] = new Array<string>(insertingLinesCnt - editingLinesCnt);
let newLinesLengths = new Uint32Array(insertingLinesCnt - editingLinesCnt);
for (let j = editingLinesCnt + 1; j <= insertingLinesCnt; j++) {
newLines[j - editingLinesCnt - 1] = op.lines[j];
newLinesLengths[j - editingLinesCnt - 1] = op.lines[j].length + this._EOL.length;
}
newLines[newLines.length - 1] += leftoverLine;
newLinesLengths[newLines.length - 1] += leftoverLine.length;
this._lines = arrays.arrayInsert(this._lines, startLineNumber + editingLinesCnt, newLines);
this._lineStarts.insertValues(startLineNumber + editingLinesCnt, newLinesLengths);
}
const contentChangeRange = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
const text = (op.lines ? op.lines.join(this.getEOL()) : '');
contentChanges.push({
range: contentChangeRange,
rangeLength: op.rangeLength,
text: text,
rangeOffset: op.rangeOffset,
forceMoveMarkers: op.forceMoveMarkers
});
}
return contentChanges;
}
/**
* Assumes `operations` are validated and sorted ascending
*/
public static _getInverseEditRanges(operations: IValidatedEditOperation[]): Range[] {
let result: Range[] = [];
let prevOpEndLineNumber: number;
let prevOpEndColumn: number;
let prevOp: IValidatedEditOperation = null;
for (let i = 0, len = operations.length; i < len; i++) {
let op = operations[i];
let startLineNumber: number;
let startColumn: number;
if (prevOp) {
if (prevOp.range.endLineNumber === op.range.startLineNumber) {
startLineNumber = prevOpEndLineNumber;
startColumn = prevOpEndColumn + (op.range.startColumn - prevOp.range.endColumn);
} else {
startLineNumber = prevOpEndLineNumber + (op.range.startLineNumber - prevOp.range.endLineNumber);
startColumn = op.range.startColumn;
}
} else {
startLineNumber = op.range.startLineNumber;
startColumn = op.range.startColumn;
}
let resultRange: Range;
if (op.lines && op.lines.length > 0) {
// the operation inserts something
let lineCount = op.lines.length;
let firstLine = op.lines[0];
let lastLine = op.lines[lineCount - 1];
if (lineCount === 1) {
// single line insert
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLine.length);
} else {
// multi line insert
resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLine.length + 1);
}
} else {
// There is nothing to insert
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn);
}
prevOpEndLineNumber = resultRange.endLineNumber;
prevOpEndColumn = resultRange.endColumn;
result.push(resultRange);
prevOp = op;
}
return result;
}
//#endregion
}

View File

@@ -1,139 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as strings from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import { ITextBufferBuilder, ITextBufferFactory, ITextBuffer, DefaultEndOfLine } from 'vs/editor/common/model';
import { IRawTextSource, TextSource } from 'vs/editor/common/model/linesTextBuffer/textSource';
import { LinesTextBuffer } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
export class TextBufferFactory implements ITextBufferFactory {
constructor(public readonly rawTextSource: IRawTextSource) {
}
public create(defaultEOL: DefaultEndOfLine): ITextBuffer {
const textSource = TextSource.fromRawTextSource(this.rawTextSource, defaultEOL);
return new LinesTextBuffer(textSource);
}
public getFirstLineText(lengthLimit: number): string {
return this.rawTextSource.lines[0].substr(0, lengthLimit);
}
}
class ModelLineBasedBuilder {
private BOM: string;
private lines: string[];
private currLineIndex: number;
constructor() {
this.BOM = '';
this.lines = [];
this.currLineIndex = 0;
}
public acceptLines(lines: string[]): void {
if (this.currLineIndex === 0) {
// Remove the BOM (if present)
if (strings.startsWithUTF8BOM(lines[0])) {
this.BOM = strings.UTF8_BOM_CHARACTER;
lines[0] = lines[0].substr(1);
}
}
for (let i = 0, len = lines.length; i < len; i++) {
this.lines[this.currLineIndex++] = lines[i];
}
}
public finish(carriageReturnCnt: number, containsRTL: boolean, isBasicASCII: boolean): TextBufferFactory {
return new TextBufferFactory({
BOM: this.BOM,
lines: this.lines,
containsRTL: containsRTL,
totalCRCount: carriageReturnCnt,
isBasicASCII,
});
}
}
export class LinesTextBufferBuilder implements ITextBufferBuilder {
private leftoverPrevChunk: string;
private leftoverEndsInCR: boolean;
private totalCRCount: number;
private lineBasedBuilder: ModelLineBasedBuilder;
private containsRTL: boolean;
private isBasicASCII: boolean;
constructor() {
this.leftoverPrevChunk = '';
this.leftoverEndsInCR = false;
this.totalCRCount = 0;
this.lineBasedBuilder = new ModelLineBasedBuilder();
this.containsRTL = false;
this.isBasicASCII = true;
}
private _updateCRCount(chunk: string): void {
// Count how many \r are present in chunk to determine the majority EOL sequence
let chunkCarriageReturnCnt = 0;
let lastCarriageReturnIndex = -1;
while ((lastCarriageReturnIndex = chunk.indexOf('\r', lastCarriageReturnIndex + 1)) !== -1) {
chunkCarriageReturnCnt++;
}
this.totalCRCount += chunkCarriageReturnCnt;
}
public acceptChunk(chunk: string): void {
if (chunk.length === 0) {
return;
}
this._updateCRCount(chunk);
if (!this.containsRTL) {
this.containsRTL = strings.containsRTL(chunk);
}
if (this.isBasicASCII) {
this.isBasicASCII = strings.isBasicASCII(chunk);
}
// Avoid dealing with a chunk that ends in \r (push the \r to the next chunk)
if (this.leftoverEndsInCR) {
chunk = '\r' + chunk;
}
if (chunk.charCodeAt(chunk.length - 1) === CharCode.CarriageReturn) {
this.leftoverEndsInCR = true;
chunk = chunk.substr(0, chunk.length - 1);
} else {
this.leftoverEndsInCR = false;
}
let lines = chunk.split(/\r\n|\r|\n/);
if (lines.length === 1) {
// no \r or \n encountered
this.leftoverPrevChunk += lines[0];
return;
}
lines[0] = this.leftoverPrevChunk + lines[0];
this.lineBasedBuilder.acceptLines(lines.slice(0, lines.length - 1));
this.leftoverPrevChunk = lines[lines.length - 1];
}
public finish(): TextBufferFactory {
let finalLines = [this.leftoverPrevChunk];
if (this.leftoverEndsInCR) {
finalLines.push('');
}
this.lineBasedBuilder.acceptLines(finalLines);
return this.lineBasedBuilder.finish(this.totalCRCount, this.containsRTL, this.isBasicASCII);
}
}

View File

@@ -1,66 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { ITextSource } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
/**
* A processed string ready to be turned into an editor model.
*/
export interface IRawTextSource {
/**
* The text split into lines.
*/
readonly lines: string[];
/**
* The BOM (leading character sequence of the file).
*/
readonly BOM: string;
/**
* The number of lines ending with '\r\n'
*/
readonly totalCRCount: number;
/**
* The text contains Unicode characters classified as "R" or "AL".
*/
readonly containsRTL: boolean;
/**
* The text contains only characters inside the ASCII range 32-126 or \t \r \n
*/
readonly isBasicASCII: boolean;
}
export class TextSource {
/**
* if text source is empty or with precisely one line, returns null. No end of line is detected.
* if text source contains more lines ending with '\r\n', returns '\r\n'.
* Otherwise returns '\n'. More lines end with '\n'.
*/
private static _getEOL(rawTextSource: IRawTextSource, defaultEOL: DefaultEndOfLine): '\r\n' | '\n' {
const lineFeedCnt = rawTextSource.lines.length - 1;
if (lineFeedCnt === 0) {
// This is an empty file or a file with precisely one line
return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n');
}
if (rawTextSource.totalCRCount > lineFeedCnt / 2) {
// More than half of the file contains \r\n ending lines
return '\r\n';
}
// At least one line more ends in \n
return '\n';
}
public static fromRawTextSource(rawTextSource: IRawTextSource, defaultEOL: DefaultEndOfLine): ITextSource {
return {
lines: rawTextSource.lines,
BOM: rawTextSource.BOM,
EOL: TextSource._getEOL(rawTextSource, defaultEOL),
containsRTL: rawTextSource.containsRTL,
isBasicASCII: rawTextSource.isBasicASCII,
};
}
}

View File

@@ -9,8 +9,11 @@ import { CharCode } from 'vs/base/common/charCode';
import { Range } from 'vs/editor/common/core/range';
import { ITextSnapshot } from 'vs/platform/files/common/files';
import { leftest, righttest, updateTreeMetadata, rbDelete, fixInsert, NodeColor, SENTINEL, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase';
import { SearchData, isValidMatch, Searcher, createFindMatch } from 'vs/editor/common/model/textModelSearch';
import { FindMatch } from 'vs/editor/common/model';
// const lfRegex = new RegExp(/\r\n|\r|\n/g);
export const AverageBufferSize = 65535;
export function createUintArray(arr: number[]): Uint32Array | Uint16Array {
let r;
@@ -33,7 +36,7 @@ export class LineStarts {
) { }
}
export function createLineStartsFast(str: string, readonly: boolean = true): Uint32Array | number[] {
export function createLineStartsFast(str: string, readonly: boolean = true): Uint32Array | Uint16Array | number[] {
let r: number[] = [0], rLength = 1;
for (let i = 0, len = str.length; i < len; i++) {
@@ -309,7 +312,7 @@ export class PieceTreeBase {
}
normalizeEOL(eol: '\r\n' | '\n') {
let averageBufferSize = 65536;
let averageBufferSize = AverageBufferSize;
let min = averageBufferSize - Math.floor(averageBufferSize / 3);
let max = min * 2;
@@ -446,7 +449,7 @@ export class PieceTreeBase {
return new Position(1, 1);
}
public getValueInRange(range: Range): string {
public getValueInRange(range: Range, eol?: string): string {
if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) {
return '';
}
@@ -454,7 +457,21 @@ export class PieceTreeBase {
let startPosition = this.nodeAt2(range.startLineNumber, range.startColumn);
let endPosition = this.nodeAt2(range.endLineNumber, range.endColumn);
return this.getValueInRange2(startPosition, endPosition);
let value = this.getValueInRange2(startPosition, endPosition);
if (eol) {
if (eol !== this._EOL || !this._EOLNormalized) {
return value.replace(/\r\n|\r|\n/g, eol);
}
if (eol === this.getEOL() && this._EOLNormalized) {
if (eol === '\r\n') {
}
return value;
}
return value.replace(/\r\n|\r|\n/g, eol);
}
return value;
}
public getValueInRange2(startPosition: NodePosition, endPosition: NodePosition): string {
@@ -520,11 +537,23 @@ export class PieceTreeBase {
public getLineCharCode(lineNumber: number, index: number): number {
let nodePos = this.nodeAt2(lineNumber, index + 1);
let buffer = this._buffers[nodePos.node.piece.bufferIndex];
let startOffset = this.offsetInBuffer(nodePos.node.piece.bufferIndex, nodePos.node.piece.start);
let targetOffset = startOffset + index;
if (nodePos.remainder === nodePos.node.piece.length) {
// the char we want to fetch is at the head of next node.
let matchingNode = nodePos.node.next();
if (!matchingNode) {
return 0;
}
return buffer.buffer.charCodeAt(targetOffset);
let buffer = this._buffers[matchingNode.piece.bufferIndex];
let startOffset = this.offsetInBuffer(matchingNode.piece.bufferIndex, matchingNode.piece.start);
return buffer.buffer.charCodeAt(startOffset);
} else {
let buffer = this._buffers[nodePos.node.piece.bufferIndex];
let startOffset = this.offsetInBuffer(nodePos.node.piece.bufferIndex, nodePos.node.piece.start);
let targetOffset = startOffset + nodePos.remainder;
return buffer.buffer.charCodeAt(targetOffset);
}
}
public getLineLength(lineNumber: number): number {
@@ -535,6 +564,151 @@ export class PieceTreeBase {
return this.getOffsetAt(lineNumber + 1, 1) - this.getOffsetAt(lineNumber, 1) - this._EOLLength;
}
public findMatchesInNode(node: TreeNode, searcher: Searcher, startLineNumber: number, startColumn: number, startCursor: BufferCursor, endCursor: BufferCursor, searchData: SearchData, captureMatches: boolean, limitResultCount: number, resultLen: number, result: FindMatch[]) {
let buffer = this._buffers[node.piece.bufferIndex];
let startOffsetInBuffer = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start);
let start = this.offsetInBuffer(node.piece.bufferIndex, startCursor);
let end = this.offsetInBuffer(node.piece.bufferIndex, endCursor);
let m: RegExpExecArray;
// Reset regex to search from the beginning
searcher.reset(start);
let ret: BufferCursor = { line: 0, column: 0 };
do {
m = searcher.next(buffer.buffer);
if (m) {
if (m.index >= end) {
return resultLen;
}
this.positionInBuffer(node, m.index - startOffsetInBuffer, ret);
let lineFeedCnt = this.getLineFeedCnt(node.piece.bufferIndex, startCursor, ret);
let retStartColumn = ret.line === startCursor.line ? ret.column - startCursor.column + startColumn : ret.column + 1;
let retEndColumn = retStartColumn + m[0].length;
result[resultLen++] = createFindMatch(new Range(startLineNumber + lineFeedCnt, retStartColumn, startLineNumber + lineFeedCnt, retEndColumn), m, captureMatches);
if (m.index + m[0].length >= end) {
return resultLen;
}
if (resultLen >= limitResultCount) {
return resultLen;
}
}
} while (m);
return resultLen;
}
public findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[] {
const result: FindMatch[] = [];
let resultLen = 0;
const searcher = new Searcher(searchData.wordSeparators, searchData.regex);
let startPostion = this.nodeAt2(searchRange.startLineNumber, searchRange.startColumn);
if (startPostion === null) {
return [];
}
let endPosition = this.nodeAt2(searchRange.endLineNumber, searchRange.endColumn);
if (endPosition === null) {
return [];
}
let start = this.positionInBuffer(startPostion.node, startPostion.remainder);
let end = this.positionInBuffer(endPosition.node, endPosition.remainder);
if (startPostion.node === endPosition.node) {
this.findMatchesInNode(startPostion.node, searcher, searchRange.startLineNumber, searchRange.startColumn, start, end, searchData, captureMatches, limitResultCount, resultLen, result);
return result;
}
let startLineNumber = searchRange.startLineNumber;
let currentNode = startPostion.node;
while (currentNode !== endPosition.node) {
let lineBreakCnt = this.getLineFeedCnt(currentNode.piece.bufferIndex, start, currentNode.piece.end);
if (lineBreakCnt >= 1) {
// last line break position
let lineStarts = this._buffers[currentNode.piece.bufferIndex].lineStarts;
let startOffsetInBuffer = this.offsetInBuffer(currentNode.piece.bufferIndex, currentNode.piece.start);
let nextLineStartOffset = lineStarts[start.line + lineBreakCnt];
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn : 1;
resultLen = this.findMatchesInNode(currentNode, searcher, startLineNumber, startColumn, start, this.positionInBuffer(currentNode, nextLineStartOffset - startOffsetInBuffer), searchData, captureMatches, limitResultCount, resultLen, result);
if (resultLen >= limitResultCount) {
return result;
}
startLineNumber += lineBreakCnt;
}
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn - 1 : 0;
// search for the remaining content
if (startLineNumber === searchRange.endLineNumber) {
const text = this.getLineContent(startLineNumber).substring(startColumn, searchRange.endColumn - 1);
resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, startColumn, resultLen, result, captureMatches, limitResultCount);
return result;
}
resultLen = this._findMatchesInLine(searchData, searcher, this.getLineContent(startLineNumber).substr(startColumn), startLineNumber, startColumn, resultLen, result, captureMatches, limitResultCount);
if (resultLen >= limitResultCount) {
return result;
}
startLineNumber++;
startPostion = this.nodeAt2(startLineNumber, 1);
currentNode = startPostion.node;
start = this.positionInBuffer(startPostion.node, startPostion.remainder);
}
if (startLineNumber === searchRange.endLineNumber) {
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn - 1 : 0;
const text = this.getLineContent(startLineNumber).substring(startColumn, searchRange.endColumn - 1);
resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, startColumn, resultLen, result, captureMatches, limitResultCount);
return result;
}
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn : 1;
resultLen = this.findMatchesInNode(endPosition.node, searcher, startLineNumber, startColumn, start, end, searchData, captureMatches, limitResultCount, resultLen, result);
return result;
}
private _findMatchesInLine(searchData: SearchData, searcher: Searcher, text: string, lineNumber: number, deltaOffset: number, resultLen: number, result: FindMatch[], captureMatches: boolean, limitResultCount: number): number {
const wordSeparators = searchData.wordSeparators;
if (!captureMatches && searchData.simpleSearch) {
const searchString = searchData.simpleSearch;
const searchStringLen = searchString.length;
const textLength = text.length;
let lastMatchIndex = -searchStringLen;
while ((lastMatchIndex = text.indexOf(searchString, lastMatchIndex + searchStringLen)) !== -1) {
if (!wordSeparators || isValidMatch(wordSeparators, text, textLength, lastMatchIndex, searchStringLen)) {
result[resultLen++] = new FindMatch(new Range(lineNumber, lastMatchIndex + 1 + deltaOffset, lineNumber, lastMatchIndex + 1 + searchStringLen + deltaOffset), null);
if (resultLen >= limitResultCount) {
return resultLen;
}
}
}
return resultLen;
}
let m: RegExpExecArray;
// Reset regex to search from the beginning
searcher.reset(0);
do {
m = searcher.next(text);
if (m) {
result[resultLen++] = createFindMatch(new Range(lineNumber, m.index + 1 + deltaOffset, lineNumber, m.index + 1 + m[0].length + deltaOffset), m, captureMatches);
if (resultLen >= limitResultCount) {
return resultLen;
}
}
} while (m);
return resultLen;
}
// #endregion
// #region Piece Table
@@ -551,7 +725,8 @@ export class PieceTreeBase {
if (node.piece.bufferIndex === 0 &&
piece.end.line === this._lastChangeBufferPos.line &&
piece.end.column === this._lastChangeBufferPos.column &&
(nodeStartOffset + piece.length === offset)
(nodeStartOffset + piece.length === offset) &&
value.length < AverageBufferSize
) {
// changed buffer
this.appendToNode(node, value);
@@ -608,19 +783,27 @@ export class PieceTreeBase {
this.deleteNodeTail(node, insertPosInBuffer);
}
let newPiece = this.createNewPiece(value);
let newPieces = this.createNewPieces(value);
if (newRightPiece.length > 0) {
this.rbInsertRight(node, newRightPiece);
}
this.rbInsertRight(node, newPiece);
let tmpNode = node;
for (let k = 0; k < newPieces.length; k++) {
tmpNode = this.rbInsertRight(tmpNode, newPieces[k]);
}
this.deleteNodes(nodesToDel);
} else {
this.insertContentToNodeRight(value, node);
}
} else {
// insert new node
let piece = this.createNewPiece(value);
this.rbInsertLeft(null, piece);
let pieces = this.createNewPieces(value);
let node = this.rbInsertLeft(null, pieces[0]);
for (let k = 1; k < pieces.length; k++) {
node = this.rbInsertRight(node, pieces[k]);
}
}
// todo, this is too brutal. Total line feed count should be updated the same way as lf_left.
@@ -726,8 +909,11 @@ export class PieceTreeBase {
}
}
let newPiece = this.createNewPiece(value);
let newNode = this.rbInsertLeft(node, newPiece);
let newPieces = this.createNewPieces(value);
let newNode = this.rbInsertLeft(node, newPieces[newPieces.length - 1]);
for (let k = newPieces.length - 2; k >= 0; k--) {
newNode = this.rbInsertLeft(newNode, newPieces[k]);
}
this.validateCRLFWithPrevNode(newNode);
this.deleteNodes(nodesToDel);
}
@@ -739,12 +925,18 @@ export class PieceTreeBase {
value += '\n';
}
let newPiece = this.createNewPiece(value);
let newNode = this.rbInsertRight(node, newPiece);
let newPieces = this.createNewPieces(value);
let newNode = this.rbInsertRight(node, newPieces[0]);
let tmpNode = newNode;
for (let k = 1; k < newPieces.length; k++) {
tmpNode = this.rbInsertRight(tmpNode, newPieces[k]);
}
this.validateCRLFWithPrevNode(newNode);
}
positionInBuffer(node: TreeNode, remainder: number): BufferCursor {
positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor {
let piece = node.piece;
let bufferIndex = node.piece.bufferIndex;
let lineStarts = this._buffers[bufferIndex].lineStarts;
@@ -780,6 +972,12 @@ export class PieceTreeBase {
}
}
if (ret) {
ret.line = mid;
ret.column = offset - midStart;
return null;
}
return {
line: mid,
column: offset - midStart
@@ -827,7 +1025,47 @@ export class PieceTreeBase {
}
}
createNewPiece(text: string): Piece {
createNewPieces(text: string): Piece[] {
if (text.length > AverageBufferSize) {
// the content is large, operations like substring, charCode becomes slow
// so here we split it into smaller chunks, just like what we did for CR/LF normalization
let newPieces = [];
while (text.length > AverageBufferSize) {
const lastChar = text.charCodeAt(AverageBufferSize - 1);
let splitText;
if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xd800 && lastChar <= 0xdbff)) {
// last character is \r or a high surrogate => keep it back
splitText = text.substring(0, AverageBufferSize - 1);
text = text.substring(AverageBufferSize - 1);
} else {
splitText = text.substring(0, AverageBufferSize);
text = text.substring(AverageBufferSize);
}
let lineStarts = createLineStartsFast(splitText);
newPieces.push(new Piece(
this._buffers.length, /* buffer index */
{ line: 0, column: 0 },
{ line: lineStarts.length - 1, column: splitText.length - lineStarts[lineStarts.length - 1] },
lineStarts.length - 1,
splitText.length
));
this._buffers.push(new StringBuffer(splitText, lineStarts));
}
let lineStarts = createLineStartsFast(text);
newPieces.push(new Piece(
this._buffers.length, /* buffer index */
{ line: 0, column: 0 },
{ line: lineStarts.length - 1, column: text.length - lineStarts[lineStarts.length - 1] },
lineStarts.length - 1,
text.length
));
this._buffers.push(new StringBuffer(text, lineStarts));
return newPieces;
}
let startOffset = this._buffers[0].buffer.length;
const lineStarts = createLineStartsFast(text, false);
@@ -862,14 +1100,14 @@ export class PieceTreeBase {
let endColumn = endOffset - this._buffers[0].lineStarts[endIndex];
let endPos = { line: endIndex, column: endColumn };
let newPiece = new Piece(
0,
0, /** todo */
start,
endPos,
this.getLineFeedCnt(0, start, endPos),
endOffset - startOffset
);
this._lastChangeBufferPos = endPos;
return newPiece;
return [newPiece];
}
getLinesRawContent(): string {
@@ -1352,8 +1590,8 @@ export class PieceTreeBase {
}
// create new piece which contains \r\n
let piece = this.createNewPiece('\r\n');
this.rbInsertRight(prev, piece);
let pieces = this.createNewPieces('\r\n');
this.rbInsertRight(prev, pieces[0]);
// delete empty nodes
for (let i = 0; i < nodesToDel.length; i++) {

View File

@@ -7,10 +7,25 @@
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import * as strings from 'vs/base/common/strings';
import { IValidatedEditOperation } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
import { IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange } from 'vs/editor/common/model';
import { IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange, FindMatch, ISingleEditOperationIdentifier } from 'vs/editor/common/model';
import { ITextSnapshot } from 'vs/platform/files/common/files';
import { SearchData } from 'vs/editor/common/model/textModelSearch';
export interface IValidatedEditOperation {
sortIndex: number;
identifier: ISingleEditOperationIdentifier;
range: Range;
rangeOffset: number;
rangeLength: number;
lines: string[];
forceMoveMarkers: boolean;
isAutoWhitespaceEdit: boolean;
}
export interface IReverseSingleEditOperation extends IIdentifiedSingleEditOperation {
sortIndex: number;
}
export class PieceTreeTextBuffer implements ITextBuffer {
private _pieceTree: PieceTreeBase;
@@ -76,8 +91,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
}
const lineEnding = this._getEndOfLine(eol);
const text = this._pieceTree.getValueInRange(range);
return text.replace(/\r\n|\r|\n/g, lineEnding);
return this._pieceTree.getValueInRange(range, lineEnding);
}
public getValueLengthInRange(range: Range, eol: EndOfLinePreference = EndOfLinePreference.TextDefined): number {
@@ -192,13 +206,17 @@ export class PieceTreeTextBuffer implements ITextBuffer {
// Sort operations ascending
operations.sort(PieceTreeTextBuffer._sortOpsAscending);
let hasTouchingRanges = false;
for (let i = 0, count = operations.length - 1; i < count; i++) {
let rangeEnd = operations[i].range.getEndPosition();
let nextRangeStart = operations[i + 1].range.getStartPosition();
if (nextRangeStart.isBefore(rangeEnd)) {
// overlapping ranges
throw new Error('Overlapping ranges are not allowed!');
if (nextRangeStart.isBeforeOrEqual(rangeEnd)) {
if (nextRangeStart.isBefore(rangeEnd)) {
// overlapping ranges
throw new Error('Overlapping ranges are not allowed!');
}
hasTouchingRanges = true;
}
}
@@ -229,12 +247,13 @@ export class PieceTreeTextBuffer implements ITextBuffer {
}
}
let reverseOperations: IIdentifiedSingleEditOperation[] = [];
let reverseOperations: IReverseSingleEditOperation[] = [];
for (let i = 0; i < operations.length; i++) {
let op = operations[i];
let reverseRange = reverseRanges[i];
reverseOperations[i] = {
sortIndex: op.sortIndex,
identifier: op.identifier,
range: reverseRange,
text: this.getValueInRange(op.range),
@@ -242,6 +261,11 @@ export class PieceTreeTextBuffer implements ITextBuffer {
};
}
// Can only sort reverse operations when the order is not significant
if (!hasTouchingRanges) {
reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex);
}
this._mightContainRTL = mightContainRTL;
this._mightContainNonBasicASCII = mightContainNonBasicASCII;
@@ -279,9 +303,9 @@ export class PieceTreeTextBuffer implements ITextBuffer {
}
/**
* Transform operations such that they represent the same logic edit,
* but that they also do not cause OOM crashes.
*/
* Transform operations such that they represent the same logic edit,
* but that they also do not cause OOM crashes.
*/
private _reduceOperations(operations: IValidatedEditOperation[]): IValidatedEditOperation[] {
if (operations.length < 1000) {
// We know from empirical testing that a thousand edits work fine regardless of their shape.
@@ -410,6 +434,10 @@ export class PieceTreeTextBuffer implements ITextBuffer {
return contentChanges;
}
findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[] {
return this._pieceTree.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
}
// #endregion
// #region helper

View File

@@ -5,7 +5,7 @@
'use strict';
import URI from 'vs/base/common/uri';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import * as model from 'vs/editor/common/model';
import { LanguageIdentifier, TokenizationRegistry, LanguageId } from 'vs/editor/common/modes';
import { EditStack } from 'vs/editor/common/model/editStack';
@@ -30,31 +30,13 @@ import { getWordAtText } from 'vs/editor/common/model/wordHelper';
import { ModelLinesTokens, ModelTokensChangedEventBuilder } from 'vs/editor/common/model/textModelTokens';
import { guessIndentation } from 'vs/editor/common/model/indentationGuesser';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch';
import { TextModelSearch, SearchParams, SearchData } from 'vs/editor/common/model/textModelSearch';
import { TPromise } from 'vs/base/common/winjs.base';
import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files';
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { ChunksTextBufferBuilder } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder';
export enum TextBufferType {
LinesArray,
PieceTree,
Chunks
}
// Here is the master switch for the text buffer implementation:
export const OPTIONS = {
TEXT_BUFFER_IMPLEMENTATION: TextBufferType.PieceTree
};
function createTextBufferBuilder() {
if (OPTIONS.TEXT_BUFFER_IMPLEMENTATION === TextBufferType.PieceTree) {
return new PieceTreeTextBufferBuilder();
}
if (OPTIONS.TEXT_BUFFER_IMPLEMENTATION === TextBufferType.Chunks) {
return new ChunksTextBufferBuilder();
}
return new LinesTextBufferBuilder();
return new PieceTreeTextBufferBuilder();
}
export function createTextBufferFactory(text: string): model.ITextBufferFactory {
@@ -173,15 +155,17 @@ class TextModelSnapshot implements ITextSnapshot {
export class TextModel extends Disposable implements model.ITextModel {
private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
private static readonly MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
private static readonly MANY_MANY_LINES = 300 * 1000; // 300K lines
private static readonly LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; // 20 MB;
private static readonly LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; // 300K lines
public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = {
isForSimpleWidget: false,
tabSize: EDITOR_MODEL_DEFAULTS.tabSize,
insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces,
detectIndentation: false,
defaultEOL: model.DefaultEndOfLine.LF,
trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace,
largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations,
};
public static createFromString(text: string, options: model.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null, uri: URI = null): TextModel {
@@ -228,15 +212,19 @@ export class TextModel extends Disposable implements model.ITextModel {
public readonly onDidChangeOptions: Event<IModelOptionsChangedEvent> = this._onDidChangeOptions.event;
private readonly _eventEmitter: DidChangeContentEmitter = this._register(new DidChangeContentEmitter());
public onDidChangeRawContentFast(listener: (e: ModelRawContentChangedEvent) => void): IDisposable {
return this._eventEmitter.fastEvent((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent));
}
public onDidChangeRawContent(listener: (e: ModelRawContentChangedEvent) => void): IDisposable {
return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent));
return this._eventEmitter.slowEvent((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent));
}
public onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable {
return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent));
return this._eventEmitter.slowEvent((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent));
}
//#endregion
public readonly id: string;
public readonly isForSimpleWidget: boolean;
private readonly _associatedResource: URI;
private _attachedEditorCount: number;
private _buffer: model.ITextBuffer;
@@ -249,7 +237,7 @@ export class TextModel extends Disposable implements model.ITextModel {
* Unlike, versionId, this can go down (via undo) or go to previous values (via redo)
*/
private _alternativeVersionId: number;
private readonly _shouldSimplifyMode: boolean;
private readonly _isTooLargeForSyncing: boolean;
private readonly _isTooLargeForTokenization: boolean;
//#region Editing
@@ -284,6 +272,7 @@ export class TextModel extends Disposable implements model.ITextModel {
// Generate a new unique model id
MODEL_ID++;
this.id = '$model' + MODEL_ID;
this.isForSimpleWidget = creationOptions.isForSimpleWidget;
if (typeof associatedResource === 'undefined' || associatedResource === null) {
this._associatedResource = URI.parse('inmemory://model/' + MODEL_ID);
} else {
@@ -297,18 +286,20 @@ export class TextModel extends Disposable implements model.ITextModel {
const bufferLineCount = this._buffer.getLineCount();
const bufferTextLength = this._buffer.getValueLengthInRange(new Range(1, 1, bufferLineCount, this._buffer.getLineLength(bufferLineCount) + 1), model.EndOfLinePreference.TextDefined);
// !!! Make a decision in the ctor and permanently respect this decision !!!
// If a model is too large at construction time, it will never get tokenized,
// under no circumstances.
this._isTooLargeForTokenization = (
(bufferTextLength > TextModel.MODEL_TOKENIZATION_LIMIT)
|| (bufferLineCount > TextModel.MANY_MANY_LINES)
);
if (creationOptions.largeFileOptimizations) {
this._isTooLargeForTokenization = (
(bufferTextLength > TextModel.LARGE_FILE_SIZE_THRESHOLD)
|| (bufferLineCount > TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD)
);
} else {
this._isTooLargeForTokenization = false;
}
this._shouldSimplifyMode = (
this._isTooLargeForTokenization
|| (bufferTextLength > TextModel.MODEL_SYNC_LIMIT)
);
this._isTooLargeForSyncing = (bufferTextLength > TextModel.MODEL_SYNC_LIMIT);
this._setVersionId(1);
this._isDisposed = false;
@@ -398,10 +389,11 @@ export class TextModel extends Disposable implements model.ITextModel {
this.setValueFromTextBuffer(textBuffer);
}
private _createContentChanged2(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent {
private _createContentChanged2(range: Range, rangeOffset: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent {
return {
changes: [{
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
range: range,
rangeOffset: rangeOffset,
rangeLength: rangeLength,
text: text,
}],
@@ -447,7 +439,7 @@ export class TextModel extends Disposable implements model.ITextModel {
false,
false
),
this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, true)
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, true)
);
}
@@ -478,7 +470,7 @@ export class TextModel extends Disposable implements model.ITextModel {
false,
false
),
this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, false)
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, false)
);
}
@@ -548,8 +540,12 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._attachedEditorCount > 0;
}
public isTooLargeForHavingARichMode(): boolean {
return this._shouldSimplifyMode;
public getAttachedEditorCount(): number {
return this._attachedEditorCount;
}
public isTooLargeForSyncing(): boolean {
return this._isTooLargeForSyncing;
}
public isTooLargeForTokenization(): boolean {
@@ -779,6 +775,15 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._buffer.getLineContent(lineNumber);
}
public getLineLength(lineNumber: number): number {
this._assertNotDisposed();
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
throw new Error('Illegal value for lineNumber');
}
return this._buffer.getLineLength(lineNumber);
}
public getLinesContent(): string[] {
this._assertNotDisposed();
return this._buffer.getLinesContent();
@@ -889,6 +894,41 @@ export class TextModel extends Disposable implements model.ITextModel {
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
}
/**
* @param strict Do NOT allow a position inside a high-low surrogate pair
*/
private _isValidPosition(lineNumber: number, column: number, strict: boolean): boolean {
if (lineNumber < 1) {
return false;
}
const lineCount = this._buffer.getLineCount();
if (lineNumber > lineCount) {
return false;
}
if (column < 1) {
return false;
}
const maxColumn = this.getLineMaxColumn(lineNumber);
if (column > maxColumn) {
return false;
}
if (strict) {
if (column > 1) {
const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);
if (strings.isHighSurrogate(charCodeBefore)) {
return false;
}
}
}
return true;
}
/**
* @param strict Do NOT allow a position inside a high-low surrogate pair
*/
@@ -929,11 +969,60 @@ export class TextModel extends Disposable implements model.ITextModel {
public validatePosition(position: IPosition): Position {
this._assertNotDisposed();
// Avoid object allocation and cover most likely case
if (position instanceof Position) {
if (this._isValidPosition(position.lineNumber, position.column, true)) {
return position;
}
}
return this._validatePosition(position.lineNumber, position.column, true);
}
/**
* @param strict Do NOT allow a range to have its boundaries inside a high-low surrogate pair
*/
private _isValidRange(range: Range, strict: boolean): boolean {
const startLineNumber = range.startLineNumber;
const startColumn = range.startColumn;
const endLineNumber = range.endLineNumber;
const endColumn = range.endColumn;
if (!this._isValidPosition(startLineNumber, startColumn, false)) {
return false;
}
if (!this._isValidPosition(endLineNumber, endColumn, false)) {
return false;
}
if (strict) {
const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0);
const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0);
const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);
const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);
if (!startInsideSurrogatePair && !endInsideSurrogatePair) {
return true;
}
return false;
}
return true;
}
public validateRange(_range: IRange): Range {
this._assertNotDisposed();
// Avoid object allocation and cover most likely case
if ((_range instanceof Range) && !(_range instanceof Selection)) {
if (this._isValidRange(_range, true)) {
return _range;
}
}
const start = this._validatePosition(_range.startLineNumber, _range.startColumn, false);
const end = this._validatePosition(_range.endLineNumber, _range.endColumn, false);
@@ -983,6 +1072,10 @@ export class TextModel extends Disposable implements model.ITextModel {
return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));
}
private findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): model.FindMatch[] {
return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
}
public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {
this._assertNotDisposed();
@@ -993,12 +1086,46 @@ export class TextModel extends Disposable implements model.ITextModel {
searchRange = this.getFullModelRange();
}
if (!isRegex && searchString.indexOf('\n') < 0) {
// not regex, not multi line
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
const searchData = searchParams.parseSearchRequest();
if (!searchData) {
return [];
}
return this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
}
return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);
}
public findNextMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch {
this._assertNotDisposed();
const searchStart = this.validatePosition(rawSearchStart);
if (!isRegex && searchString.indexOf('\n') < 0) {
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
const searchData = searchParams.parseSearchRequest();
const lineCount = this.getLineCount();
let searchRange = new Range(searchStart.lineNumber, searchStart.column, lineCount, this.getLineMaxColumn(lineCount));
let ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);
TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
if (ret.length > 0) {
return ret[0];
}
searchRange = new Range(1, 1, searchStart.lineNumber, this.getLineMaxColumn(searchStart.lineNumber));
ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);
if (ret.length > 0) {
return ret[0];
}
return null;
}
return TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
}
@@ -1584,6 +1711,88 @@ export class TextModel extends Disposable implements model.ITextModel {
//#region Tokenization
public tokenizeViewport(startLineNumber: number, endLineNumber: number): void {
if (!this._tokens.tokenizationSupport) {
return;
}
// we tokenize `this._tokens.inValidLineStartIndex` lines in around 20ms so it's a good baseline.
const contextBefore = Math.floor(this._tokens.inValidLineStartIndex * 0.3);
startLineNumber = Math.max(1, startLineNumber - contextBefore);
if (startLineNumber <= this._tokens.inValidLineStartIndex) {
this.forceTokenization(endLineNumber);
return;
}
const eventBuilder = new ModelTokensChangedEventBuilder();
let nonWhitespaceColumn = this.getLineFirstNonWhitespaceColumn(startLineNumber);
let fakeLines = [];
let i = startLineNumber - 1;
let initialState = null;
if (nonWhitespaceColumn > 0) {
while (nonWhitespaceColumn > 0 && i >= 1) {
let newNonWhitespaceIndex = this.getLineFirstNonWhitespaceColumn(i);
if (newNonWhitespaceIndex === 0) {
i--;
continue;
}
if (newNonWhitespaceIndex < nonWhitespaceColumn) {
initialState = this._tokens._getState(i - 1);
if (initialState) {
break;
}
fakeLines.push(this.getLineContent(i));
nonWhitespaceColumn = newNonWhitespaceIndex;
}
i--;
}
}
if (!initialState) {
initialState = this._tokens.tokenizationSupport.getInitialState();
}
let state = initialState.clone();
for (let i = fakeLines.length - 1; i >= 0; i--) {
let r = this._tokens._tokenizeText(this._buffer, fakeLines[i], state);
if (r) {
state = r.endState.clone();
} else {
state = initialState.clone();
}
}
const contextAfter = Math.floor(this._tokens.inValidLineStartIndex * 0.4);
endLineNumber = Math.min(this.getLineCount(), endLineNumber + contextAfter);
for (let i = startLineNumber; i <= endLineNumber; i++) {
let text = this.getLineContent(i);
let r = this._tokens._tokenizeText(this._buffer, text, state);
if (r) {
this._tokens._setTokens(this._tokens.languageIdentifier.id, i - 1, text.length, r.tokens);
/*
* we think it's valid and give it a state but we don't update `_invalidLineStartIndex` then the top-to-bottom tokenization
* goes through the viewport, it can skip them if they already have correct tokens and state, and the lines after the viewport
* can still be tokenized.
*/
this._tokens._setIsInvalid(i - 1, false);
this._tokens._setState(i - 1, state);
state = r.endState.clone();
eventBuilder.registerChangedTokens(i);
} else {
state = initialState.clone();
}
}
const e = eventBuilder.build();
if (e) {
this._onDidChangeTokens.fire(e);
}
}
public forceTokenization(lineNumber: number): void {
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
throw new Error('Illegal value for lineNumber');
@@ -1726,8 +1935,39 @@ export class TextModel extends Disposable implements model.ITextModel {
const position = this.validatePosition(_position);
const lineContent = this.getLineContent(position.lineNumber);
const lineTokens = this._getLineTokens(position.lineNumber);
const offset = position.column - 1;
const tokenIndex = lineTokens.findTokenIndexAtOffset(offset);
const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
// (1). First try checking right biased word
const [rbStartOffset, rbEndOffset] = TextModel._findLanguageBoundaries(lineTokens, tokenIndex);
const rightBiasedWord = getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(lineTokens.getLanguageId(tokenIndex)),
lineContent.substring(rbStartOffset, rbEndOffset),
rbStartOffset
);
if (rightBiasedWord) {
return rightBiasedWord;
}
// (2). Else, if we were at a language boundary, check the left biased word
if (tokenIndex > 0 && rbStartOffset === position.column - 1) {
// edge case, where `position` sits between two tokens belonging to two different languages
const [lbStartOffset, lbEndOffset] = TextModel._findLanguageBoundaries(lineTokens, tokenIndex - 1);
const leftBiasedWord = getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(lineTokens.getLanguageId(tokenIndex - 1)),
lineContent.substring(lbStartOffset, lbEndOffset),
lbStartOffset
);
if (leftBiasedWord) {
return leftBiasedWord;
}
}
return null;
}
private static _findLanguageBoundaries(lineTokens: LineTokens, tokenIndex: number): [number, number] {
const languageId = lineTokens.getLanguageId(tokenIndex);
// go left until a different language is hit
@@ -1742,12 +1982,7 @@ export class TextModel extends Disposable implements model.ITextModel {
endOffset = lineTokens.getEndOffset(i);
}
return getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(languageId),
lineContent.substring(startOffset, endOffset),
startOffset
);
return [startOffset, endOffset];
}
public getWordUntilPosition(position: IPosition): model.IWordAtPosition {
@@ -1809,25 +2044,13 @@ export class TextModel extends Disposable implements model.ITextModel {
// limit search to not go after `maxBracketLength`
const searchEndOffset = Math.min(lineTokens.getEndOffset(tokenIndex), position.column - 1 + currentModeBrackets.maxBracketLength);
// first, check if there is a bracket to the right of `position`
let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, position.column - 1, searchEndOffset);
if (foundBracket && foundBracket.startColumn === position.column) {
let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1);
foundBracketText = foundBracketText.toLowerCase();
let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]);
// check that we can actually match this bracket
if (r) {
return r;
}
}
// it might still be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
// it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
// `bestResult` will contain the most right-side result
let bestResult: [Range, Range] = null;
while (true) {
let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (!foundBracket) {
// there are no brackets in this text
// there are no more brackets in this text
break;
}
@@ -1840,12 +2063,16 @@ export class TextModel extends Disposable implements model.ITextModel {
// check that we can actually match this bracket
if (r) {
return r;
bestResult = r;
}
}
searchStartOffset = foundBracket.endColumn - 1;
}
if (bestResult) {
return bestResult;
}
}
// If position is in between two tokens, try also looking in the previous token
@@ -1879,6 +2106,10 @@ export class TextModel extends Disposable implements model.ITextModel {
}
private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean): [Range, Range] {
if (!data) {
return null;
}
if (isOpen) {
let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition());
if (matched) {
@@ -2158,6 +2389,173 @@ export class TextModel extends Disposable implements model.ITextModel {
return TextModel.computeIndentLevel(this._buffer.getLineContent(lineIndex + 1), this._options.tabSize);
}
public getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): model.IActiveIndentGuideInfo {
this._assertNotDisposed();
const lineCount = this.getLineCount();
if (lineNumber < 1 || lineNumber > lineCount) {
throw new Error('Illegal value for lineNumber');
}
const foldingRules = LanguageConfigurationRegistry.getFoldingRules(this._languageIdentifier.id);
const offSide = foldingRules && foldingRules.offSide;
let up_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
let up_aboveContentLineIndent = -1;
let up_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
let up_belowContentLineIndent = -1;
const up_resolveIndents = (lineNumber: number) => {
if (up_aboveContentLineIndex !== -1 && (up_aboveContentLineIndex === -2 || up_aboveContentLineIndex > lineNumber - 1)) {
up_aboveContentLineIndex = -1;
up_aboveContentLineIndent = -1;
// must find previous line with content
for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
let indent = this._computeIndentLevel(lineIndex);
if (indent >= 0) {
up_aboveContentLineIndex = lineIndex;
up_aboveContentLineIndent = indent;
break;
}
}
}
if (up_belowContentLineIndex === -2) {
up_belowContentLineIndex = -1;
up_belowContentLineIndent = -1;
// must find next line with content
for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
let indent = this._computeIndentLevel(lineIndex);
if (indent >= 0) {
up_belowContentLineIndex = lineIndex;
up_belowContentLineIndent = indent;
break;
}
}
}
};
let down_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
let down_aboveContentLineIndent = -1;
let down_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
let down_belowContentLineIndent = -1;
const down_resolveIndents = (lineNumber: number) => {
if (down_aboveContentLineIndex === -2) {
down_aboveContentLineIndex = -1;
down_aboveContentLineIndent = -1;
// must find previous line with content
for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
let indent = this._computeIndentLevel(lineIndex);
if (indent >= 0) {
down_aboveContentLineIndex = lineIndex;
down_aboveContentLineIndent = indent;
break;
}
}
}
if (down_belowContentLineIndex !== -1 && (down_belowContentLineIndex === -2 || down_belowContentLineIndex < lineNumber - 1)) {
down_belowContentLineIndex = -1;
down_belowContentLineIndent = -1;
// must find next line with content
for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
let indent = this._computeIndentLevel(lineIndex);
if (indent >= 0) {
down_belowContentLineIndex = lineIndex;
down_belowContentLineIndent = indent;
break;
}
}
}
};
let startLineNumber = 0;
let goUp = true;
let endLineNumber = 0;
let goDown = true;
let indent = 0;
for (let distance = 0; goUp || goDown; distance++) {
const upLineNumber = lineNumber - distance;
const downLineNumber = lineNumber + distance;
if (upLineNumber < 1 || upLineNumber < minLineNumber) {
goUp = false;
}
if (downLineNumber > lineCount || downLineNumber > maxLineNumber) {
goDown = false;
}
if (distance > 50000) {
// stop processing
goUp = false;
goDown = false;
}
if (goUp) {
// compute indent level going up
let upLineIndentLevel: number;
const currentIndent = this._computeIndentLevel(upLineNumber - 1);
if (currentIndent >= 0) {
// This line has content (besides whitespace)
// Use the line's indent
up_belowContentLineIndex = upLineNumber - 1;
up_belowContentLineIndent = currentIndent;
upLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize);
} else {
up_resolveIndents(upLineNumber);
upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent);
}
if (distance === 0) {
// This is the initial line number
startLineNumber = upLineNumber;
endLineNumber = downLineNumber;
indent = upLineIndentLevel;
if (indent === 0) {
// No need to continue
return { startLineNumber, endLineNumber, indent };
}
continue;
}
if (upLineIndentLevel >= indent) {
startLineNumber = upLineNumber;
} else {
goUp = false;
}
}
if (goDown) {
// compute indent level going down
let downLineIndentLevel: number;
const currentIndent = this._computeIndentLevel(downLineNumber - 1);
if (currentIndent >= 0) {
// This line has content (besides whitespace)
// Use the line's indent
down_aboveContentLineIndex = downLineNumber - 1;
down_aboveContentLineIndent = currentIndent;
downLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize);
} else {
down_resolveIndents(downLineNumber);
downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent);
}
if (downLineIndentLevel >= indent) {
endLineNumber = downLineNumber;
} else {
goDown = false;
}
}
}
return { startLineNumber, endLineNumber, indent };
}
public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] {
this._assertNotDisposed();
const lineCount = this.getLineCount();
@@ -2223,32 +2621,38 @@ export class TextModel extends Disposable implements model.ITextModel {
}
}
if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) {
// At the top or bottom of the file
result[resultIndex] = 0;
result[resultIndex] = this._getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent);
} else if (aboveContentLineIndent < belowContentLineIndent) {
// we are inside the region above
result[resultIndex] = (1 + Math.floor(aboveContentLineIndent / this._options.tabSize));
} else if (aboveContentLineIndent === belowContentLineIndent) {
// we are in between two regions
result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize);
} else {
if (offSide) {
// same level as region below
result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize);
} else {
// we are inside the region that ends below
result[resultIndex] = (1 + Math.floor(belowContentLineIndent / this._options.tabSize));
}
}
}
return result;
}
private _getIndentLevelForWhitespaceLine(offSide: boolean, aboveContentLineIndent: number, belowContentLineIndent: number): number {
if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) {
// At the top or bottom of the file
return 0;
} else if (aboveContentLineIndent < belowContentLineIndent) {
// we are inside the region above
return (1 + Math.floor(aboveContentLineIndent / this._options.tabSize));
} else if (aboveContentLineIndent === belowContentLineIndent) {
// we are in between two regions
return Math.ceil(belowContentLineIndent / this._options.tabSize);
} else {
if (offSide) {
// same level as region below
return Math.ceil(belowContentLineIndent / this._options.tabSize);
} else {
// we are inside the region that ends below
return (1 + Math.floor(belowContentLineIndent / this._options.tabSize));
}
}
}
//#endregion
}
@@ -2363,22 +2767,20 @@ export class ModelDecorationOverviewRulerOptions implements model.IModelDecorati
}
}
let lastStaticId = 0;
export class ModelDecorationOptions implements model.IModelDecorationOptions {
public static EMPTY: ModelDecorationOptions;
public static register(options: model.IModelDecorationOptions): ModelDecorationOptions {
return new ModelDecorationOptions(++lastStaticId, options);
return new ModelDecorationOptions(options);
}
public static createDynamic(options: model.IModelDecorationOptions): ModelDecorationOptions {
return new ModelDecorationOptions(0, options);
return new ModelDecorationOptions(options);
}
readonly staticId: number;
readonly stickiness: model.TrackedRangeStickiness;
readonly zIndex: number;
readonly className: string;
readonly hoverMessage: IMarkdownString | IMarkdownString[];
readonly glyphMarginHoverMessage: IMarkdownString | IMarkdownString[];
@@ -2389,12 +2791,13 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
readonly linesDecorationsClassName: string;
readonly marginClassName: string;
readonly inlineClassName: string;
readonly inlineClassNameAffectsLetterSpacing: boolean;
readonly beforeContentClassName: string;
readonly afterContentClassName: string;
private constructor(staticId: number, options: model.IModelDecorationOptions) {
this.staticId = staticId;
private constructor(options: model.IModelDecorationOptions) {
this.stickiness = options.stickiness || model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges;
this.zIndex = options.zIndex || 0;
this.className = options.className ? cleanClassName(options.className) : strings.empty;
this.hoverMessage = options.hoverMessage || [];
this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || [];
@@ -2405,6 +2808,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : strings.empty;
this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : strings.empty;
this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : strings.empty;
this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false;
this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : strings.empty;
this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : strings.empty;
}
@@ -2465,8 +2869,13 @@ export class DidChangeDecorationsEmitter extends Disposable {
export class DidChangeContentEmitter extends Disposable {
private readonly _actual: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
public readonly event: Event<InternalModelContentChangeEvent> = this._actual.event;
/**
* Both `fastEvent` and `slowEvent` work the same way and contain the same events, but first we invoke `fastEvent` and then `slowEvent`.
*/
private readonly _fastEmitter: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
public readonly fastEvent: Event<InternalModelContentChangeEvent> = this._fastEmitter.event;
private readonly _slowEmitter: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
public readonly slowEvent: Event<InternalModelContentChangeEvent> = this._slowEmitter.event;
private _deferredCnt: number;
private _deferredEvent: InternalModelContentChangeEvent;
@@ -2487,7 +2896,8 @@ export class DidChangeContentEmitter extends Disposable {
if (this._deferredEvent !== null) {
const e = this._deferredEvent;
this._deferredEvent = null;
this._actual.fire(e);
this._fastEmitter.fire(e);
this._slowEmitter.fire(e);
}
}
}
@@ -2501,6 +2911,7 @@ export class DidChangeContentEmitter extends Disposable {
}
return;
}
this._actual.fire(e);
this._fastEmitter.fire(e);
this._slowEmitter.fire(e);
}
}

View File

@@ -32,6 +32,10 @@ export interface IModelContentChange {
* The range that got replaced.
*/
readonly range: IRange;
/**
* The offset of the range that got replaced.
*/
readonly rangeOffset: number;
/**
* The length of the range that got replaced.
*/

View File

@@ -116,7 +116,7 @@ export class SearchData {
}
}
function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch {
export function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch {
if (!captureMatches) {
return new FindMatch(range, null);
}
@@ -434,6 +434,11 @@ function leftIsWordBounday(wordSeparators: WordCharacterClassifier, text: string
return true;
}
if (charBefore === CharCode.CarriageReturn || charBefore === CharCode.LineFeed) {
// The character before the match is line break or carriage return.
return true;
}
if (matchLength > 0) {
const firstCharInMatch = text.charCodeAt(matchStartIndex);
if (wordSeparators.get(firstCharInMatch) !== WordCharacterClass.Regular) {
@@ -457,6 +462,11 @@ function rightIsWordBounday(wordSeparators: WordCharacterClassifier, text: strin
return true;
}
if (charAfter === CharCode.CarriageReturn || charAfter === CharCode.LineFeed) {
// The character after the match is line break or carriage return.
return true;
}
if (matchLength > 0) {
const lastCharInMatch = text.charCodeAt(matchStartIndex + matchLength - 1);
if (wordSeparators.get(lastCharInMatch) !== WordCharacterClass.Regular) {
@@ -468,14 +478,14 @@ function rightIsWordBounday(wordSeparators: WordCharacterClassifier, text: strin
return false;
}
function isValidMatch(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
export function isValidMatch(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
return (
leftIsWordBounday(wordSeparators, text, textLength, matchStartIndex, matchLength)
&& rightIsWordBounday(wordSeparators, text, textLength, matchStartIndex, matchLength)
);
}
class Searcher {
export class Searcher {
private _wordSeparators: WordCharacterClassifier;
private _searchRegex: RegExp;
private _prevMatchStartIndex: number;

View File

@@ -25,7 +25,7 @@ function getDefaultMetadata(topLevelLanguageId: LanguageId): number {
) >>> 0;
}
const EMPTY_LINE_TOKENS = new Uint32Array(0);
const EMPTY_LINE_TOKENS = (new Uint32Array(0)).buffer;
class ModelLineTokens {
_state: IState;
@@ -196,9 +196,13 @@ export class ModelLinesTokens {
this._lastState = null;
}
public get inValidLineStartIndex() {
return this._invalidLineStartIndex;
}
public getTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineText: string): LineTokens {
let rawLineTokens: ArrayBuffer = null;
if (lineIndex < this._tokens.length) {
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
rawLineTokens = this._tokens[lineIndex]._lineTokens;
}
@@ -229,21 +233,21 @@ export class ModelLinesTokens {
}
}
private _setIsInvalid(lineIndex: number, invalid: boolean): void {
if (lineIndex < this._tokens.length) {
_setIsInvalid(lineIndex: number, invalid: boolean): void {
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
this._tokens[lineIndex]._invalid = invalid;
}
}
_isInvalid(lineIndex: number): boolean {
if (lineIndex < this._tokens.length) {
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
return this._tokens[lineIndex]._invalid;
}
return true;
}
_getState(lineIndex: number): IState {
if (lineIndex < this._tokens.length) {
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
return this._tokens[lineIndex]._state;
}
return null;
@@ -251,7 +255,7 @@ export class ModelLinesTokens {
_setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, tokens: Uint32Array): void {
let target: ModelLineTokens;
if (lineIndex < this._tokens.length) {
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
target = this._tokens[lineIndex];
} else {
target = new ModelLineTokens(null);
@@ -274,8 +278,8 @@ export class ModelLinesTokens {
target._lineTokens = tokens.buffer;
}
private _setState(lineIndex: number, state: IState): void {
if (lineIndex < this._tokens.length) {
_setState(lineIndex: number, state: IState): void {
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
this._tokens[lineIndex]._state = state;
} else {
const tmp = new ModelLineTokens(state);
@@ -376,6 +380,21 @@ export class ModelLinesTokens {
return lineNumber;
}
public _tokenizeText(buffer: ITextBuffer, text: string, state: IState): TokenizationResult2 {
let r: TokenizationResult2 = null;
try {
r = this.tokenizationSupport.tokenize2(text, state, 0);
} catch (e) {
onUnexpectedError(e);
}
if (!r) {
r = nullTokenize2(this.languageIdentifier.id, text, state, 0);
}
return r;
}
public _updateTokensUntilLine(buffer: ITextBuffer, eventBuilder: ModelTokensChangedEventBuilder, lineNumber: number): void {
if (!this.tokenizationSupport) {
this._invalidLineStartIndex = buffer.getLineCount();

View File

@@ -12,7 +12,7 @@ import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegis
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position } from 'vs/editor/common/core/position';
import { Range, IRange } from 'vs/editor/common/core/range';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry';
import { Color } from 'vs/base/common/color';
import { IMarkerData } from 'vs/platform/markers/common/markers';
@@ -230,7 +230,7 @@ export interface Hover {
* editor will use the range at the current position or the
* current position itself.
*/
range: IRange;
range?: IRange;
}
/**
@@ -364,6 +364,11 @@ export interface CodeActionProvider {
* Provide commands for the given document and range.
*/
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeAction[] | Thenable<CodeAction[]>;
/**
* Optional list of of CodeActionKinds that this provider returns.
*/
providedCodeActionKinds?: string[];
}
/**
@@ -825,66 +830,60 @@ export interface DocumentColorProvider {
*/
provideColorPresentations(model: model.ITextModel, colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable<IColorPresentation[]>;
}
export interface FoldingContext {
}
/**
* A provider of colors for editor models.
*/
/**
* @internal
*/
export interface FoldingProvider {
export interface FoldingRangeProvider {
/**
* Provides the color ranges for a specific model.
*/
provideFoldingRanges(model: model.ITextModel, token: CancellationToken): IFoldingRangeList | Thenable<IFoldingRangeList>;
provideFoldingRanges(model: model.ITextModel, context: FoldingContext, token: CancellationToken): FoldingRange[] | Thenable<FoldingRange[]>;
}
/**
* @internal
*/
export interface IFoldingRangeList {
ranges: IFoldingRange[];
export interface FoldingRange {
/**
* The zero-based start line of the range to fold. The folded area starts after the line's last character.
*/
start: number;
/**
* The zero-based end line of the range to fold. The folded area ends with the line's last character.
*/
end: number;
/**
* Describes the [Kind](#FoldingRangeKind) of the folding range such as [Comment](#FoldingRangeKind.Comment) or
* [Region](#FoldingRangeKind.Region). The kind is used to categorize folding ranges and used by commands
* like 'Fold all comments'. See
* [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
*/
kind?: FoldingRangeKind;
}
/**
* @internal
*/
export interface IFoldingRange {
export class FoldingRangeKind {
/**
* Kind for folding range representing a comment. The value of the kind is 'comment'.
*/
static readonly Comment = new FoldingRangeKind('comment');
/**
* Kind for folding range representing a import. The value of the kind is 'imports'.
*/
static readonly Imports = new FoldingRangeKind('imports');
/**
* Kind for folding range representing regions (for example marked by `#region`, `#endregion`).
* The value of the kind is 'region'.
*/
static readonly Region = new FoldingRangeKind('region');
/**
* The start line number
* Creates a new [FoldingRangeKind](#FoldingRangeKind).
*
* @param value of the kind.
*/
startLineNumber: number;
/**
* The end line number
*/
endLineNumber: number;
/**
* The optional type of the folding range
*/
type?: FoldingRangeType | string;
// auto-collapse
// header span
}
/**
* @internal
*/
export enum FoldingRangeType {
/**
* Folding range for a comment
*/
Comment = 'comment',
/**
* Folding range for a imports or includes
*/
Imports = 'imports',
/**
* Folding range for a region (e.g. `#region`)
*/
Region = 'region'
public constructor(public value: string) {
}
}
/**
@@ -917,14 +916,14 @@ export interface WorkspaceEdit {
rejectReason?: string; // TODO@joh, move to rename
}
export interface RenameInitialValue {
export interface RenameLocation {
range: IRange;
text?: string;
text: string;
}
export interface RenameProvider {
provideRenameEdits(model: model.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable<WorkspaceEdit>;
resolveInitialRenameValue?(model: model.ITextModel, position: Position, token: CancellationToken): RenameInitialValue | Thenable<RenameInitialValue>;
resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): RenameLocation | Thenable<RenameLocation>;
}
@@ -1035,7 +1034,7 @@ export const ColorProviderRegistry = new LanguageFeatureRegistry<DocumentColorPr
/**
* @internal
*/
export const FoldingProviderRegistry = new LanguageFeatureRegistry<FoldingProvider>();
export const FoldingRangeProviderRegistry = new LanguageFeatureRegistry<FoldingRangeProvider>();
/**
* @internal

View File

@@ -9,7 +9,7 @@ import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/comm
import { IOnEnterSupportOptions, OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter';
import { IndentRulesSupport, IndentConsts } from 'vs/editor/common/modes/supports/indentRules';
import { RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { ITextModel } from 'vs/editor/common/model';
import { onUnexpectedError } from 'vs/base/common/errors';
import * as strings from 'vs/base/common/strings';
@@ -177,8 +177,8 @@ export class LanguageConfigurationRegistryImpl {
private _entries: RichEditSupport[];
private _onDidChange: Emitter<LanguageConfigurationChangeEvent> = new Emitter<LanguageConfigurationChangeEvent>();
public onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event;
private readonly _onDidChange: Emitter<LanguageConfigurationChangeEvent> = new Emitter<LanguageConfigurationChangeEvent>();
public readonly onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event;
constructor() {
this._entries = [];

View File

@@ -5,10 +5,11 @@
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ITextModel } from 'vs/editor/common/model';
import { LanguageSelector, score } from 'vs/editor/common/modes/languageSelector';
import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
interface Entry<T> {
selector: LanguageSelector;
@@ -17,11 +18,21 @@ interface Entry<T> {
_time: number;
}
function isExclusive(selector: LanguageSelector): boolean {
if (typeof selector === 'string') {
return false;
} else if (Array.isArray(selector)) {
return selector.every(isExclusive);
} else {
return selector.exclusive;
}
}
export default class LanguageFeatureRegistry<T> {
private _clock: number = 0;
private _entries: Entry<T>[] = [];
private _onDidChange: Emitter<number> = new Emitter<number>();
private readonly _onDidChange: Emitter<number> = new Emitter<number>();
constructor() {
}
@@ -63,7 +74,7 @@ export default class LanguageFeatureRegistry<T> {
}
all(model: ITextModel): T[] {
if (!model || model.isTooLargeForHavingARichMode()) {
if (!model) {
return [];
}
@@ -106,7 +117,7 @@ export default class LanguageFeatureRegistry<T> {
private _orderedForEach(model: ITextModel, callback: (provider: Entry<T>) => any): void {
if (!model || model.isTooLargeForHavingARichMode()) {
if (!model) {
return;
}
@@ -140,7 +151,17 @@ export default class LanguageFeatureRegistry<T> {
this._lastCandidate = candidate;
for (let entry of this._entries) {
entry._score = score(entry.selector, model.uri, model.getLanguageIdentifier().language);
entry._score = score(entry.selector, model.uri, model.getLanguageIdentifier().language, shouldSynchronizeModel(model));
if (isExclusive(entry.selector) && entry._score > 0) {
// support for one exclusive selector that overwrites
// any other selector
for (let entry of this._entries) {
entry._score = 0;
}
entry._score = 1000;
break;
}
}
// needs sorting

View File

@@ -12,17 +12,22 @@ export interface LanguageFilter {
language?: string;
scheme?: string;
pattern?: string | IRelativePattern;
/**
* This provider is implemented in the UI thread.
*/
hasAccessToAllModels?: boolean;
exclusive?: boolean;
}
export type LanguageSelector = string | LanguageFilter | (string | LanguageFilter)[];
export function score(selector: LanguageSelector, candidateUri: URI, candidateLanguage: string): number {
export function score(selector: LanguageSelector, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean): number {
if (Array.isArray(selector)) {
// array -> take max individual value
let ret = 0;
for (const filter of selector) {
const value = score(filter, candidateUri, candidateLanguage);
const value = score(filter, candidateUri, candidateLanguage, candidateIsSynchronized);
if (value === 10) {
return value; // already at the highest
}
@@ -33,9 +38,14 @@ export function score(selector: LanguageSelector, candidateUri: URI, candidateLa
return ret;
} else if (typeof selector === 'string') {
if (!candidateIsSynchronized) {
return 0;
}
// short-hand notion, desugars to
// 'fooLang' -> [{ language: 'fooLang', scheme: 'file' }, { language: 'fooLang', scheme: 'untitled' }]
// '*' -> { language: '*', scheme: '*' }
// 'fooLang' -> { language: 'fooLang'}
// '*' -> { language: '*' }
if (selector === '*') {
return 5;
} else if (selector === candidateLanguage) {
@@ -46,7 +56,11 @@ export function score(selector: LanguageSelector, candidateUri: URI, candidateLa
} else if (selector) {
// filter -> select accordingly, use defaults for scheme
const { language, pattern, scheme } = selector;
const { language, pattern, scheme, hasAccessToAllModels } = selector;
if (!candidateIsSynchronized && !hasAccessToAllModels) {
return 0;
}
let ret = 0;

View File

@@ -5,7 +5,7 @@
'use strict';
import * as nls from 'vs/nls';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
@@ -20,8 +20,8 @@ export class EditorModesRegistry {
private _languages: ILanguageExtensionPoint[];
private _onDidAddLanguages: Emitter<ILanguageExtensionPoint[]> = new Emitter<ILanguageExtensionPoint[]>();
public onDidAddLanguages: Event<ILanguageExtensionPoint[]> = this._onDidAddLanguages.event;
private readonly _onDidAddLanguages: Emitter<ILanguageExtensionPoint[]> = new Emitter<ILanguageExtensionPoint[]>();
public readonly onDidAddLanguages: Event<ILanguageExtensionPoint[]> = this._onDidAddLanguages.event;
constructor() {
this._languages = [];

View File

@@ -85,14 +85,14 @@ function once<T, R>(keyFn: (input: T) => string, computeFn: (input: T) => R): (i
const getRegexForBracketPair = once<ISimpleInternalBracket, RegExp>(
(input) => `${input.open};${input.close}`,
(input) => {
return createOrRegex([input.open, input.close]);
return createBracketOrRegExp([input.open, input.close]);
}
);
const getReversedRegexForBracketPair = once<ISimpleInternalBracket, RegExp>(
(input) => `${input.open};${input.close}`,
(input) => {
return createOrRegex([toReversedString(input.open), toReversedString(input.close)]);
return createBracketOrRegExp([toReversedString(input.open), toReversedString(input.close)]);
}
);
@@ -104,7 +104,7 @@ const getRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
pieces.push(b.open);
pieces.push(b.close);
});
return createOrRegex(pieces);
return createBracketOrRegExp(pieces);
}
);
@@ -116,12 +116,19 @@ const getReversedRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
pieces.push(toReversedString(b.open));
pieces.push(toReversedString(b.close));
});
return createOrRegex(pieces);
return createBracketOrRegExp(pieces);
}
);
function createOrRegex(pieces: string[]): RegExp {
let regexStr = `(${pieces.map(strings.escapeRegExpCharacters).join(')|(')})`;
function prepareBracketForRegExp(str: string): string {
// This bracket pair uses letters like e.g. "begin" - "end"
const insertWordBoundaries = (/^[\w]+$/.test(str));
str = strings.escapeRegExpCharacters(str);
return (insertWordBoundaries ? `\\b${str}\\b` : str);
}
function createBracketOrRegExp(pieces: string[]): RegExp {
let regexStr = `(${pieces.map(prepareBracketForRegExp).join(')|(')})`;
return strings.createRegExp(regexStr, true);
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { IDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { ColorId, ITokenizationRegistry, ITokenizationSupport, ITokenizationSupportChangedEvent } from 'vs/editor/common/modes';
import { Color } from 'vs/base/common/color';
@@ -13,8 +13,8 @@ export class TokenizationRegistryImpl implements ITokenizationRegistry {
private _map: { [language: string]: ITokenizationSupport };
private _onDidChange: Emitter<ITokenizationSupportChangedEvent> = new Emitter<ITokenizationSupportChangedEvent>();
public onDidChange: Event<ITokenizationSupportChangedEvent> = this._onDidChange.event;
private readonly _onDidChange: Emitter<ITokenizationSupportChangedEvent> = new Emitter<ITokenizationSupportChangedEvent>();
public readonly onDidChange: Event<ITokenizationSupportChangedEvent> = this._onDidChange.event;
private _colorMap: Color[];

View File

@@ -22,6 +22,7 @@ import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model
import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase';
import { IWordAtPosition, EndOfLineSequence } from 'vs/editor/common/model';
import { globals } from 'vs/base/common/platform';
import { IIterator } from 'vs/base/common/iterator';
export interface IMirrorModel {
readonly uri: URI;
@@ -58,8 +59,8 @@ export interface ICommonModel {
getLinesContent(): string[];
getLineCount(): number;
getLineContent(lineNumber: number): string;
createWordIterator(wordDefinition: RegExp): IIterator<string>;
getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition;
getAllUniqueWords(wordDefinition: RegExp, skipWordOnce?: string): string[];
getValueInRange(range: IRange): string;
getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range;
offsetAt(position: IPosition): number;
@@ -146,30 +147,37 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel {
};
}
private _getAllWords(wordDefinition: RegExp): string[] {
let result: string[] = [];
this._lines.forEach((line) => {
this._wordenize(line, wordDefinition).forEach((info) => {
result.push(line.substring(info.start, info.end));
});
});
return result;
}
public createWordIterator(wordDefinition: RegExp): IIterator<string> {
let obj = {
done: false,
value: ''
};
let lineNumber = 0;
let lineText: string;
let wordRangesIdx = 0;
let wordRanges: IWordRange[] = [];
let next = () => {
if (wordRangesIdx < wordRanges.length) {
obj.done = false;
obj.value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end);
wordRangesIdx += 1;
} else if (lineNumber >= this._lines.length) {
obj.done = true;
obj.value = undefined;
public getAllUniqueWords(wordDefinition: RegExp, skipWordOnce?: string): string[] {
let foundSkipWord = false;
let uniqueWords = Object.create(null);
return this._getAllWords(wordDefinition).filter((word) => {
if (skipWordOnce && !foundSkipWord && skipWordOnce === word) {
foundSkipWord = true;
return false;
} else if (uniqueWords[word]) {
return false;
} else {
uniqueWords[word] = true;
return true;
lineText = this._lines[lineNumber];
wordRanges = this._wordenize(lineText, wordDefinition);
wordRangesIdx = 0;
lineNumber += 1;
return next();
}
});
return obj;
};
return { next };
}
private _wordenize(content: string, wordDefinition: RegExp): IWordRange[] {
@@ -288,13 +296,24 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel {
}
}
/**
* @internal
*/
export interface IForeignModuleFactory {
(ctx: IWorkerContext, createData: any): any;
}
declare var require;
/**
* @internal
*/
export abstract class BaseEditorSimpleWorker {
private _foreignModuleFactory: IForeignModuleFactory;
private _foreignModule: any;
constructor() {
constructor(foreignModuleFactory: IForeignModuleFactory) {
this._foreignModuleFactory = foreignModuleFactory;
this._foreignModule = null;
}
@@ -416,6 +435,8 @@ export abstract class BaseEditorSimpleWorker {
// ---- BEGIN suggest --------------------------------------------------------------------------
private static readonly _suggestionsLimit = 10000;
public textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): TPromise<ISuggestResult> {
const model = this._getModel(modelUrl);
if (model) {
@@ -423,17 +444,32 @@ export abstract class BaseEditorSimpleWorker {
const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
const currentWord = model.getWordUntilPosition(position, wordDefRegExp).word;
for (const word of model.getAllUniqueWords(wordDefRegExp)) {
if (word !== currentWord && isNaN(Number(word))) {
suggestions.push({
type: 'text',
label: word,
insertText: word,
noAutoAccept: true,
overwriteBefore: currentWord.length
});
const seen: Record<string, boolean> = Object.create(null);
seen[currentWord] = true;
for (
let iter = model.createWordIterator(wordDefRegExp), e = iter.next();
!e.done && suggestions.length <= BaseEditorSimpleWorker._suggestionsLimit;
e = iter.next()
) {
const word = e.value;
if (seen[word]) {
continue;
}
seen[word] = true;
if (!isNaN(Number(word))) {
continue;
}
suggestions.push({
type: 'text',
label: word,
insertText: word,
noAutoAccept: true,
overwriteBefore: currentWord.length
});
}
return TPromise.as({ suggestions });
}
return undefined;
@@ -474,14 +510,25 @@ export abstract class BaseEditorSimpleWorker {
// ---- BEGIN foreign module support --------------------------------------------------------------------------
public loadForeignModule(moduleId: string, createData: any): TPromise<string[]> {
let ctx: IWorkerContext = {
getMirrorModels: (): IMirrorModel[] => {
return this._getModels();
}
};
if (this._foreignModuleFactory) {
this._foreignModule = this._foreignModuleFactory(ctx, createData);
// static foreing module
let methods: string[] = [];
for (let prop in this._foreignModule) {
if (typeof this._foreignModule[prop] === 'function') {
methods.push(prop);
}
}
return TPromise.as(methods);
}
return new TPromise<any>((c, e) => {
// Use the global require to be sure to get the global config
(<any>self).require([moduleId], (foreignModule: { create: (ctx: IWorkerContext, createData: any) => any; }) => {
let ctx: IWorkerContext = {
getMirrorModels: (): IMirrorModel[] => {
return this._getModels();
}
};
require([moduleId], (foreignModule: { create: IForeignModuleFactory }) => {
this._foreignModule = foreignModule.create(ctx, createData);
let methods: string[] = [];
@@ -521,8 +568,8 @@ export class EditorSimpleWorkerImpl extends BaseEditorSimpleWorker implements IR
private _models: { [uri: string]: MirrorModel; };
constructor() {
super();
constructor(foreignModuleFactory: IForeignModuleFactory) {
super(foreignModuleFactory);
this._models = Object.create(null);
}
@@ -565,9 +612,12 @@ export class EditorSimpleWorkerImpl extends BaseEditorSimpleWorker implements IR
* @internal
*/
export function create(): IRequestHandler {
return new EditorSimpleWorkerImpl();
return new EditorSimpleWorkerImpl(null);
}
// This is only available in a Web Worker
declare function importScripts(...urls: string[]): void;
if (typeof importScripts === 'function') {
// Running in a web worker
globals.monaco = createMonacoBaseAPI();

View File

@@ -351,7 +351,7 @@ export class EditorWorkerClient extends Disposable {
));
} catch (err) {
logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl(null));
}
}
return this._worker;
@@ -360,7 +360,7 @@ export class EditorWorkerClient extends Disposable {
protected _getProxy(): TPromise<EditorSimpleWorkerImpl> {
return new ShallowCancelThenPromise(this._getOrCreateWorker().getProxyObject().then(null, (err) => {
logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl(null));
return this._getOrCreateWorker().getProxyObject();
}));
}

View File

@@ -111,13 +111,9 @@ export class LanguagesRegistry {
let primaryMime: string = null;
if (typeof lang.mimetypes !== 'undefined' && Array.isArray(lang.mimetypes)) {
for (let i = 0; i < lang.mimetypes.length; i++) {
if (!primaryMime) {
primaryMime = lang.mimetypes[i];
}
resolvedLanguage.mimetypes.push(lang.mimetypes[i]);
}
if (Array.isArray(lang.mimetypes) && lang.mimetypes.length > 0) {
resolvedLanguage.mimetypes.push(...lang.mimetypes);
primaryMime = lang.mimetypes[0];
}
if (!primaryMime) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';

View File

@@ -5,7 +5,7 @@
'use strict';
import { onUnexpectedError } from 'vs/base/common/errors';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
import { FrankensteinMode } from 'vs/editor/common/modes/abstractMode';

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -16,7 +16,7 @@ export const IModelService = createDecorator<IModelService>('modelService');
export interface IModelService {
_serviceBrand: any;
createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI): ITextModel;
createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI, isForSimpleWidget?: boolean): ITextModel;
updateModel(model: ITextModel, value: string | ITextBufferFactory): void;
@@ -26,7 +26,7 @@ export interface IModelService {
getModels(): ITextModel[];
getCreationOptions(language: string, resource: URI): ITextModelCreationOptions;
getCreationOptions(language: string, resource: URI, isForSimpleWidget: boolean): ITextModelCreationOptions;
getModel(resource: URI): ITextModel;
@@ -36,3 +36,9 @@ export interface IModelService {
onModelModeChanged: Event<{ model: ITextModel; oldModeId: string; }>;
}
export function shouldSynchronizeModel(model: ITextModel): boolean {
return (
!model.isTooLargeForSyncing() && !model.isForSimpleWidget
);
}

View File

@@ -5,16 +5,14 @@
'use strict';
import * as nls from 'vs/nls';
import network = require('vs/base/common/network');
import Event, { Emitter } from 'vs/base/common/event';
import * as network from 'vs/base/common/network';
import { Event, Emitter } from 'vs/base/common/event';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IMarker, IMarkerService } from 'vs/platform/markers/common/markers';
import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
import { IMode, LanguageIdentifier } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
@@ -28,6 +26,8 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService';
import { overviewRulerWarning, overviewRulerError, overviewRulerInfo } from 'vs/editor/common/view/editorColorRegistry';
import { ITextModel, IModelDeltaDecoration, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, DefaultEndOfLine, ITextModelCreationOptions, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBufferFactory, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { basename } from 'vs/base/common/paths';
function MODEL_ID(resource: URI): string {
return resource.toString();
@@ -82,15 +82,23 @@ class ModelMarkerHandler {
}
private static _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range {
let marker = model.validateRange(new Range(rawMarker.startLineNumber, rawMarker.startColumn, rawMarker.endLineNumber, rawMarker.endColumn));
let ret: Range = new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn);
let ret = Range.lift(rawMarker);
if (rawMarker.severity === MarkerSeverity.Hint && Range.spansMultipleLines(ret)) {
// never render hints on multiple lines
ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn);
}
ret = model.validateRange(ret);
if (ret.isEmpty()) {
let word = model.getWordAtPosition(ret.getStartPosition());
if (word) {
ret = new Range(ret.startLineNumber, word.startColumn, ret.endLineNumber, word.endColumn);
} else {
let maxColumn = model.getLineLastNonWhitespaceColumn(marker.startLineNumber) ||
model.getLineMaxColumn(marker.startLineNumber);
let maxColumn = model.getLineLastNonWhitespaceColumn(ret.startLineNumber) ||
model.getLineMaxColumn(ret.startLineNumber);
if (maxColumn === 1) {
// empty line
@@ -118,31 +126,36 @@ class ModelMarkerHandler {
let className: string;
let color: ThemeColor;
let darkColor: ThemeColor;
let zIndex: number;
switch (marker.severity) {
case Severity.Ignore:
// do something
case MarkerSeverity.Hint:
className = ClassName.EditorHintDecoration;
zIndex = 0;
break;
case Severity.Warning:
case MarkerSeverity.Warning:
className = ClassName.EditorWarningDecoration;
color = themeColorFromId(overviewRulerWarning);
darkColor = themeColorFromId(overviewRulerWarning);
zIndex = 20;
break;
case Severity.Info:
case MarkerSeverity.Info:
className = ClassName.EditorInfoDecoration;
color = themeColorFromId(overviewRulerInfo);
darkColor = themeColorFromId(overviewRulerInfo);
zIndex = 10;
break;
case Severity.Error:
case MarkerSeverity.Error:
default:
className = ClassName.EditorErrorDecoration;
color = themeColorFromId(overviewRulerError);
darkColor = themeColorFromId(overviewRulerError);
zIndex = 30;
break;
}
let hoverMessage: MarkdownString = null;
let { message, source } = marker;
let { message, source, relatedInformation } = marker;
if (typeof message === 'string') {
message = message.trim();
@@ -156,6 +169,16 @@ class ModelMarkerHandler {
}
hoverMessage = new MarkdownString().appendCodeblock('_', message);
if (!isFalsyOrEmpty(relatedInformation)) {
hoverMessage.appendMarkdown('\n');
for (const { message, resource, startLineNumber, startColumn } of relatedInformation) {
hoverMessage.appendMarkdown(
`* [${basename(resource.path)}(${startLineNumber}, ${startColumn})](${resource.toString(false)}#${startLineNumber},${startColumn}): \`${message}\` \n`
);
}
hoverMessage.appendMarkdown('\n');
}
}
return {
@@ -167,7 +190,8 @@ class ModelMarkerHandler {
color,
darkColor,
position: OverviewRulerLane.Right
}
},
zIndex
};
}
}
@@ -181,6 +205,8 @@ interface IRawConfig {
insertSpaces?: any;
detectIndentation?: any;
trimAutoWhitespace?: any;
creationOptions?: any;
largeFileOptimizations?: any;
};
}
@@ -194,9 +220,9 @@ export class ModelServiceImpl implements IModelService {
private _configurationService: IConfigurationService;
private _configurationServiceSubscription: IDisposable;
private _onModelAdded: Emitter<ITextModel>;
private _onModelRemoved: Emitter<ITextModel>;
private _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }>;
private readonly _onModelAdded: Emitter<ITextModel>;
private readonly _onModelRemoved: Emitter<ITextModel>;
private readonly _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }>;
private _modelCreationOptionsByLanguageAndResource: {
[languageAndResource: string]: ITextModelCreationOptions;
@@ -227,7 +253,7 @@ export class ModelServiceImpl implements IModelService {
this._updateModelOptions();
}
private static _readModelOptions(config: IRawConfig): ITextModelCreationOptions {
private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
let tabSize = EDITOR_MODEL_DEFAULTS.tabSize;
if (config.editor && typeof config.editor.tabSize !== 'undefined') {
let parsedTabSize = parseInt(config.editor.tabSize, 10);
@@ -259,19 +285,26 @@ export class ModelServiceImpl implements IModelService {
detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation));
}
let largeFileOptimizations = EDITOR_MODEL_DEFAULTS.largeFileOptimizations;
if (config.editor && typeof config.editor.largeFileOptimizations !== 'undefined') {
largeFileOptimizations = (config.editor.largeFileOptimizations === 'false' ? false : Boolean(config.editor.largeFileOptimizations));
}
return {
isForSimpleWidget: isForSimpleWidget,
tabSize: tabSize,
insertSpaces: insertSpaces,
detectIndentation: detectIndentation,
defaultEOL: newDefaultEOL,
trimAutoWhitespace: trimAutoWhitespace
trimAutoWhitespace: trimAutoWhitespace,
largeFileOptimizations: largeFileOptimizations
};
}
public getCreationOptions(language: string, resource: URI): ITextModelCreationOptions {
public getCreationOptions(language: string, resource: URI, isForSimpleWidget: boolean): ITextModelCreationOptions {
let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
if (!creationOptions) {
creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getValue({ overrideIdentifier: language, resource }));
creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getValue({ overrideIdentifier: language, resource }), isForSimpleWidget);
this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions;
}
return creationOptions;
@@ -289,7 +322,7 @@ export class ModelServiceImpl implements IModelService {
const language = modelData.model.getLanguageIdentifier().language;
const uri = modelData.model.uri;
const oldOptions = oldOptionsByLanguageAndResource[language + uri];
const newOptions = this.getCreationOptions(language, uri);
const newOptions = this.getCreationOptions(language, uri, modelData.model.isForSimpleWidget);
ModelServiceImpl._setModelOptionsForModel(modelData.model, newOptions, oldOptions);
}
}
@@ -353,9 +386,9 @@ export class ModelServiceImpl implements IModelService {
// --- begin IModelService
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI): ModelData {
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI, isForSimpleWidget: boolean): ModelData {
// create & save the model
const options = this.getCreationOptions(languageIdentifier.language, resource);
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
const model: TextModel = new TextModel(value, options, languageIdentifier, resource);
const modelId = MODEL_ID(model.uri);
@@ -375,7 +408,7 @@ export class ModelServiceImpl implements IModelService {
}
public updateModel(model: ITextModel, value: string | ITextBufferFactory): void {
const options = this.getCreationOptions(model.getLanguageIdentifier().language, model.uri);
const options = this.getCreationOptions(model.getLanguageIdentifier().language, model.uri, model.isForSimpleWidget);
const textBuffer = createTextBuffer(value, options.defaultEOL);
// Return early if the text is already set in that form
@@ -384,12 +417,14 @@ export class ModelServiceImpl implements IModelService {
}
// Otherwise find a diff between the values and update model
model.pushStackElement();
model.setEOL(textBuffer.getEOL() === '\r\n' ? EndOfLineSequence.CRLF : EndOfLineSequence.LF);
model.pushEditOperations(
[new Selection(1, 1, 1, 1)],
[],
ModelServiceImpl._computeEdits(model, textBuffer),
(inverseEditOperations: IIdentifiedSingleEditOperation[]) => [new Selection(1, 1, 1, 1)]
(inverseEditOperations: IIdentifiedSingleEditOperation[]) => []
);
model.pushStackElement();
}
private static _commonPrefix(a: ILineSequence, aLen: number, aDelta: number, b: ILineSequence, bLen: number, bDelta: number): number {
@@ -439,17 +474,17 @@ export class ModelServiceImpl implements IModelService {
newRange = new Range(1, 1, textBufferLineCount, 1 + textBuffer.getLineLength(textBufferLineCount));
}
return [EditOperation.replace(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
}
public createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI): ITextModel {
public createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI, isForSimpleWidget: boolean = false): ITextModel {
let modelData: ModelData;
if (!modeOrPromise || TPromise.is(modeOrPromise)) {
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource);
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource, isForSimpleWidget);
this.setMode(modelData.model, modeOrPromise);
} else {
modelData = this._createModelData(value, modeOrPromise.getLanguageIdentifier(), resource);
modelData = this._createModelData(value, modeOrPromise.getLanguageIdentifier(), resource, isForSimpleWidget);
}
// handle markers (marker service => model)
@@ -535,8 +570,8 @@ export class ModelServiceImpl implements IModelService {
private _onDidChangeLanguage(model: ITextModel, e: IModelLanguageChangedEvent): void {
const oldModeId = e.oldLanguage;
const newModeId = model.getLanguageIdentifier().language;
const oldOptions = this.getCreationOptions(oldModeId, model.uri);
const newOptions = this.getCreationOptions(newModeId, model.uri);
const oldOptions = this.getCreationOptions(oldModeId, model.uri, model.isForSimpleWidget);
const newOptions = this.getCreationOptions(newModeId, model.uri, model.isForSimpleWidget);
ModelServiceImpl._setModelOptionsForModel(model, newOptions, oldOptions);
this._onModelModeChanged.fire({ model, oldModeId });
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IPosition } from 'vs/editor/common/core/position';

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
@@ -11,6 +11,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { basename } from 'vs/base/common/paths';
export class TextResourceConfigurationService extends Disposable implements ITextResourceConfigurationService {
@@ -42,6 +43,6 @@ export class TextResourceConfigurationService extends Disposable implements ITex
if (model) {
return position ? this.modeService.getLanguageIdentifier(model.getLanguageIdAtPosition(position.lineNumber, position.column)).language : model.getLanguageIdentifier().language;
}
return this.modeService.getModeIdByFilenameOrFirstLine(resource.fsPath);
return this.modeService.getModeIdByFilenameOrFirstLine(basename(resource.path));
}
}

View File

@@ -25,6 +25,13 @@ export enum Severity {
Error = 3,
}
export enum MarkerSeverity {
Hint = 1,
Info = 2,
Warning = 4,
Error = 8,
}
// --------------------------------------------
// This is repeated here so it can be exported
// because TS inlines const enums
@@ -238,6 +245,7 @@ export function createMonacoBaseAPI(): typeof monaco {
Selection: Selection,
SelectionDirection: SelectionDirection,
Severity: Severity,
MarkerSeverity: MarkerSeverity,
Promise: TPromise,
Uri: <any>URI,
Token: Token

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import * as nls from 'vs/nls';
import { registerColor, editorBackground, activeContrastBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { Color, RGBA } from 'vs/base/common/color';
@@ -20,8 +20,12 @@ export const editorCursorForeground = registerColor('editorCursor.foreground', {
export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.'));
export const editorWhitespaces = registerColor('editorWhitespace.foreground', { dark: '#e3e4e229', light: '#33333333', hc: '#e3e4e229' }, nls.localize('editorWhitespaces', 'Color of whitespace characters in the editor.'));
export const editorIndentGuides = registerColor('editorIndentGuide.background', { dark: editorWhitespaces, light: editorWhitespaces, hc: editorWhitespaces }, nls.localize('editorIndentGuides', 'Color of the editor indentation guides.'));
export const editorActiveIndentGuides = registerColor('editorIndentGuide.activeBackground', { dark: editorWhitespaces, light: editorWhitespaces, hc: editorWhitespaces }, nls.localize('editorActiveIndentGuide', 'Color of the active editor indentation guides.'));
export const editorLineNumbers = registerColor('editorLineNumber.foreground', { dark: '#5A5A5A', light: '#2B91AF', hc: Color.white }, nls.localize('editorLineNumbers', 'Color of editor line numbers.'));
export const editorActiveLineNumber = registerColor('editorActiveLineNumber.foreground', { dark: null, light: null, hc: null }, nls.localize('editorActiveLineNumber', 'Color of editor active line number'));
const deprecatedEditorActiveLineNumber = registerColor('editorActiveLineNumber.foreground', { dark: null, light: null, hc: null }, nls.localize('editorActiveLineNumber', 'Color of editor active line number'), false, nls.localize('deprecatedEditorActiveLineNumber', 'Id is deprecated. Use \'editorLineNumber.activeForeground\' instead.'));
export const editorActiveLineNumber = registerColor('editorLineNumber.activeForeground', { dark: deprecatedEditorActiveLineNumber, light: deprecatedEditorActiveLineNumber, hc: deprecatedEditorActiveLineNumber }, nls.localize('editorActiveLineNumber', 'Color of editor active line number'));
export const editorRuler = registerColor('editorRuler.foreground', { dark: '#5A5A5A', light: Color.lightgrey, hc: Color.white }, nls.localize('editorRuler', 'Color of the editor rulers.'));
export const editorCodeLensForeground = registerColor('editorCodeLens.foreground', { dark: '#999999', light: '#999999', hc: '#999999' }, nls.localize('editorCodeLensForeground', 'Foreground color of editor code lenses'));
@@ -42,36 +46,44 @@ export const editorWarningBorder = registerColor('editorWarning.border', { dark:
export const editorInfoForeground = registerColor('editorInfo.foreground', { dark: '#008000', light: '#008000', hc: null }, nls.localize('infoForeground', 'Foreground color of info squigglies in the editor.'));
export const editorInfoBorder = registerColor('editorInfo.border', { dark: null, light: null, hc: Color.fromHex('#71B771').transparent(0.8) }, nls.localize('infoBorder', 'Border color of info squigglies in the editor.'));
export const editorHintForeground = registerColor('editorHint.foreground', { dark: Color.fromHex('#eeeeee').transparent(0.7), light: '#6c6c6c', hc: null }, nls.localize('hintForeground', 'Foreground color of hint squigglies in the editor.'));
export const editorHintBorder = registerColor('editorHint.border', { dark: null, light: null, hc: Color.fromHex('#eeeeee').transparent(0.8) }, nls.localize('hintBorder', 'Border color of hint squigglies in the editor.'));
const rulerRangeDefault = new Color(new RGBA(0, 122, 204, 0.6));
export const overviewRulerRangeHighlight = registerColor('editorOverviewRuler.rangeHighlightForeground', { dark: rulerRangeDefault, light: rulerRangeDefault, hc: rulerRangeDefault }, nls.localize('overviewRulerRangeHighlight', 'Overview ruler marker color for range highlights.'));
export const overviewRulerRangeHighlight = registerColor('editorOverviewRuler.rangeHighlightForeground', { dark: rulerRangeDefault, light: rulerRangeDefault, hc: rulerRangeDefault }, nls.localize('overviewRulerRangeHighlight', 'Overview ruler marker color for range highlights. The color must not be opaque to not hide underlying decorations.'), true);
export const overviewRulerError = registerColor('editorOverviewRuler.errorForeground', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('overviewRuleError', 'Overview ruler marker color for errors.'));
export const overviewRulerWarning = registerColor('editorOverviewRuler.warningForeground', { dark: new Color(new RGBA(18, 136, 18, 0.7)), light: new Color(new RGBA(18, 136, 18, 0.7)), hc: new Color(new RGBA(50, 255, 50, 1)) }, nls.localize('overviewRuleWarning', 'Overview ruler marker color for warnings.'));
export const overviewRulerInfo = registerColor('editorOverviewRuler.infoForeground', { dark: new Color(new RGBA(18, 18, 136, 0.7)), light: new Color(new RGBA(18, 18, 136, 0.7)), hc: new Color(new RGBA(50, 50, 255, 1)) }, nls.localize('overviewRuleInfo', 'Overview ruler marker color for infos.'));
// contains all color rules that used to defined in editor/browser/widget/editor.css
registerThemingParticipant((theme, collector) => {
let background = theme.getColor(editorBackground);
const background = theme.getColor(editorBackground);
if (background) {
collector.addRule(`.monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input { background-color: ${background}; }`);
}
let foreground = theme.getColor(editorForeground);
const foreground = theme.getColor(editorForeground);
if (foreground) {
collector.addRule(`.monaco-editor, .monaco-editor .inputarea.ime-input { color: ${foreground}; }`);
}
let gutter = theme.getColor(editorGutter);
const gutter = theme.getColor(editorGutter);
if (gutter) {
collector.addRule(`.monaco-editor .margin { background-color: ${gutter}; }`);
}
let rangeHighlight = theme.getColor(editorRangeHighlight);
const rangeHighlight = theme.getColor(editorRangeHighlight);
if (rangeHighlight) {
collector.addRule(`.monaco-editor .rangeHighlight { background-color: ${rangeHighlight}; }`);
}
let rangeHighlightBorder = theme.getColor(editorRangeHighlightBorder);
const rangeHighlightBorder = theme.getColor(editorRangeHighlightBorder);
if (rangeHighlightBorder) {
collector.addRule(`.monaco-editor .rangeHighlight { border: 1px dotted ${rangeHighlightBorder}; }`);
collector.addRule(`.monaco-editor .rangeHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${rangeHighlightBorder}; }`);
}
let invisibles = theme.getColor(editorWhitespaces);
const invisibles = theme.getColor(editorWhitespaces);
if (invisibles) {
collector.addRule(`.vs-whitespace { color: ${invisibles} !important; }`);
}
});
});

View File

@@ -5,7 +5,7 @@
'use strict';
import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { RGBA8 } from 'vs/editor/common/core/rgba';
export class MinimapTokensColorTracker {
@@ -21,7 +21,7 @@ export class MinimapTokensColorTracker {
private _backgroundIsLight: boolean;
private _onDidChange = new Emitter<void>();
public onDidChange: Event<void> = this._onDidChange.event;
public readonly onDidChange: Event<void> = this._onDidChange.event;
private constructor() {
this._updateColorMap();

View File

@@ -58,7 +58,7 @@ export class LineDecoration {
continue;
}
if (range.isEmpty() && d.type === InlineDecorationType.Regular) {
if (range.isEmpty() && (d.type === InlineDecorationType.Regular || d.type === InlineDecorationType.RegularAffectingLetterSpacing)) {
// Ignore empty range decorations
continue;
}

View File

@@ -11,7 +11,7 @@ import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout';
import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
const SMOOTH_SCROLLING_TIME = 125;
@@ -75,15 +75,12 @@ export class ViewLayout extends Disposable implements IViewLayout {
}
public onFlushed(lineCount: number): void {
this._linesLayout.onFlushed(lineCount);
this._updateHeight();
}
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber);
this._updateHeight();
}
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber);
this._updateHeight();
}
// ---- end view event handlers
@@ -163,7 +160,7 @@ export class ViewLayout extends Disposable implements IViewLayout {
// ---- view state
public saveState(): editorCommon.IViewState {
public saveState(): { scrollTop: number; scrollTopWithoutViewZones: number; scrollLeft: number; } {
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
let scrollTop = currentScrollPosition.scrollTop;
let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
@@ -175,17 +172,6 @@ export class ViewLayout extends Disposable implements IViewLayout {
};
}
public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
let restoreScrollTop = state.scrollTop;
if (typeof state.scrollTopWithoutViewZones === 'number' && !this._linesLayout.hasWhitespace()) {
restoreScrollTop = state.scrollTopWithoutViewZones;
}
return {
scrollLeft: state.scrollLeft,
scrollTop: restoreScrollTop
};
}
// ---- IVerticalLayoutProvider
public addWhitespace(afterLineNumber: number, ordinal: number, height: number): number {

View File

@@ -36,7 +36,8 @@ export class RenderLineInput {
public readonly useMonospaceOptimizations: boolean;
public readonly lineContent: string;
public readonly mightContainRTL: boolean;
public readonly isBasicASCII: boolean;
public readonly containsRTL: boolean;
public readonly fauxIndentLength: number;
public readonly lineTokens: IViewLineTokens;
public readonly lineDecorations: LineDecoration[];
@@ -50,7 +51,8 @@ export class RenderLineInput {
constructor(
useMonospaceOptimizations: boolean,
lineContent: string,
mightContainRTL: boolean,
isBasicASCII: boolean,
containsRTL: boolean,
fauxIndentLength: number,
lineTokens: IViewLineTokens,
lineDecorations: LineDecoration[],
@@ -63,7 +65,8 @@ export class RenderLineInput {
) {
this.useMonospaceOptimizations = useMonospaceOptimizations;
this.lineContent = lineContent;
this.mightContainRTL = mightContainRTL;
this.isBasicASCII = isBasicASCII;
this.containsRTL = containsRTL;
this.fauxIndentLength = fauxIndentLength;
this.lineTokens = lineTokens;
this.lineDecorations = lineDecorations;
@@ -85,7 +88,8 @@ export class RenderLineInput {
return (
this.useMonospaceOptimizations === other.useMonospaceOptimizations
&& this.lineContent === other.lineContent
&& this.mightContainRTL === other.mightContainRTL
&& this.isBasicASCII === other.isBasicASCII
&& this.containsRTL === other.containsRTL
&& this.fauxIndentLength === other.fauxIndentLength
&& this.tabSize === other.tabSize
&& this.spaceWidth === other.spaceWidth
@@ -217,14 +221,20 @@ export class CharacterMapping {
}
}
export const enum ForeignElementType {
None = 0,
Before = 1,
After = 2
}
export class RenderLineOutput {
_renderLineOutputBrand: void;
readonly characterMapping: CharacterMapping;
readonly containsRTL: boolean;
readonly containsForeignElements: boolean;
readonly containsForeignElements: ForeignElementType;
constructor(characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) {
constructor(characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) {
this.characterMapping = characterMapping;
this.containsRTL = containsRTL;
this.containsForeignElements = containsForeignElements;
@@ -234,7 +244,7 @@ export class RenderLineOutput {
export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): RenderLineOutput {
if (input.lineContent.length === 0) {
let containsForeignElements = false;
let containsForeignElements = ForeignElementType.None;
// This is basically for IE's hit test to work
let content: string = '<span><span>\u00a0</span></span>';
@@ -244,13 +254,17 @@ export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): Rend
let classNames: string[] = [];
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
const lineDecoration = input.lineDecorations[i];
if (lineDecoration.type !== InlineDecorationType.Regular) {
if (lineDecoration.type === InlineDecorationType.Before) {
classNames.push(input.lineDecorations[i].className);
containsForeignElements = true;
containsForeignElements |= ForeignElementType.Before;
}
if (lineDecoration.type === InlineDecorationType.After) {
classNames.push(input.lineDecorations[i].className);
containsForeignElements |= ForeignElementType.After;
}
}
if (containsForeignElements) {
if (containsForeignElements !== ForeignElementType.None) {
content = `<span><span class="${classNames.join(' ')}"></span></span>`;
}
}
@@ -271,7 +285,7 @@ export class RenderLineOutput2 {
public readonly characterMapping: CharacterMapping,
public readonly html: string,
public readonly containsRTL: boolean,
public readonly containsForeignElements: boolean
public readonly containsForeignElements: ForeignElementType
) {
}
}
@@ -289,7 +303,7 @@ class ResolvedRenderLineInput {
public readonly len: number,
public readonly isOverflowing: boolean,
public readonly parts: LinePart[],
public readonly containsForeignElements: boolean,
public readonly containsForeignElements: ForeignElementType,
public readonly tabSize: number,
public readonly containsRTL: boolean,
public readonly spaceWidth: number,
@@ -319,22 +333,22 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary) {
tokens = _applyRenderWhitespace(lineContent, len, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.renderWhitespace === RenderWhitespace.Boundary);
}
let containsForeignElements = false;
let containsForeignElements = ForeignElementType.None;
if (input.lineDecorations.length > 0) {
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
const lineDecoration = input.lineDecorations[i];
if (lineDecoration.type !== InlineDecorationType.Regular) {
containsForeignElements = true;
break;
if (lineDecoration.type === InlineDecorationType.RegularAffectingLetterSpacing) {
// Pretend there are foreign elements... although not 100% accurate.
containsForeignElements |= ForeignElementType.Before;
} else if (lineDecoration.type === InlineDecorationType.Before) {
containsForeignElements |= ForeignElementType.Before;
} else if (lineDecoration.type === InlineDecorationType.After) {
containsForeignElements |= ForeignElementType.After;
}
}
tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
}
let containsRTL = false;
if (input.mightContainRTL) {
containsRTL = strings.containsRTL(lineContent);
}
if (!containsRTL && !input.fontLigatures) {
if (input.isBasicASCII && !input.fontLigatures) {
tokens = splitLargeTokens(lineContent, tokens);
}
@@ -346,7 +360,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
tokens,
containsForeignElements,
input.tabSize,
containsRTL,
input.containsRTL,
input.spaceWidth,
input.renderWhitespace,
input.renderControlCharacters
@@ -406,11 +420,6 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[]): LinePart[] {
const piecesCount = Math.ceil(diff / Constants.LongToken);
for (let j = 1; j < piecesCount; j++) {
let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken);
let lastCharInPiece = lineContent.charCodeAt(pieceEndIndex - 1);
if (strings.isHighSurrogate(lastCharInPiece)) {
// Don't cut in the middle of a surrogate pair
pieceEndIndex--;
}
result[resultLen++] = new LinePart(pieceEndIndex, tokenType);
}
result[resultLen++] = new LinePart(tokenEndIndex, tokenType);

View File

@@ -14,7 +14,7 @@ import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
import { ThemeColor, ITheme } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';
import { IModelDecoration, ITextModel, IModelDeltaDecoration, EndOfLinePreference } from 'vs/editor/common/model';
import { IModelDecoration, ITextModel, IModelDeltaDecoration, EndOfLinePreference, IActiveIndentGuideInfo } from 'vs/editor/common/model';
export class OutputPosition {
_outputPositionBrand: void;
@@ -41,6 +41,7 @@ export interface ILineMapperFactory {
export interface ISimpleModel {
getLineTokens(lineNumber: number): LineTokens;
getLineContent(lineNumber: number): string;
getLineLength(lineNumber: number): number;
getLineMinColumn(lineNumber: number): number;
getLineMaxColumn(lineNumber: number): number;
getValueInRange(range: IRange, eol?: EndOfLinePreference): string;
@@ -52,6 +53,7 @@ export interface ISplitLine {
getViewLineCount(): number;
getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string;
getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
getViewLineMaxColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
getViewLineData(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): ViewLineData;
@@ -80,8 +82,10 @@ export interface IViewModelLinesCollection {
getViewLineCount(): number;
warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void;
getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[];
getViewLineContent(viewLineNumber: number): string;
getViewLineLength(viewLineNumber: number): number;
getViewLineMinColumn(viewLineNumber: number): number;
getViewLineMaxColumn(viewLineNumber: number): number;
getViewLineData(viewLineNumber: number): ViewLineData;
@@ -501,6 +505,26 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
this.prefixSumComputer.warmUpCache(viewStartLineNumber - 1, viewEndLineNumber - 1);
}
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
minLineNumber = this._toValidViewLineNumber(minLineNumber);
maxLineNumber = this._toValidViewLineNumber(maxLineNumber);
const modelPosition = this.convertViewPositionToModelPosition(viewLineNumber, this.getViewLineMinColumn(viewLineNumber));
const modelMinPosition = this.convertViewPositionToModelPosition(minLineNumber, this.getViewLineMinColumn(minLineNumber));
const modelMaxPosition = this.convertViewPositionToModelPosition(maxLineNumber, this.getViewLineMinColumn(maxLineNumber));
const result = this.model.getActiveIndentGuide(modelPosition.lineNumber, modelMinPosition.lineNumber, modelMaxPosition.lineNumber);
const viewStartPosition = this.convertModelPositionToViewPosition(result.startLineNumber, 1);
const viewEndPosition = this.convertModelPositionToViewPosition(result.endLineNumber, 1);
return {
startLineNumber: viewStartPosition.lineNumber,
endLineNumber: viewEndPosition.lineNumber,
indent: result.indent
};
}
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
this._ensureValidState();
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
@@ -570,6 +594,16 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return this.lines[lineIndex].getViewLineContent(this.model, lineIndex + 1, remainder);
}
public getViewLineLength(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
let remainder = r.remainder;
return this.lines[lineIndex].getViewLineLength(this.model, lineIndex + 1, remainder);
}
public getViewLineMinColumn(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
@@ -815,6 +849,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
return model.getLineContent(modelLineNumber);
}
public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
return model.getLineLength(modelLineNumber);
}
public getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
return model.getLineMinColumn(modelLineNumber);
}
@@ -880,6 +918,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
throw new Error('Not supported');
}
public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
throw new Error('Not supported');
}
public getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
throw new Error('Not supported');
}
@@ -973,6 +1015,21 @@ export class SplitLine implements ISplitLine {
return r;
}
public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
if (!this._isVisible) {
throw new Error('Not supported');
}
let startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex);
let endOffset = this.getInputEndOffsetOfOutputLineIndex(model, modelLineNumber, outputLineIndex);
let r = endOffset - startOffset;
if (outputLineIndex > 0) {
r = this.wrappedIndent.length + r;
}
return r;
}
public getViewLineMinColumn(model: ITextModel, modelLineNumber: number, outputLineIndex: number): number {
if (!this._isVisible) {
throw new Error('Not supported');
@@ -1205,6 +1262,14 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
public warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void {
}
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
return {
startLineNumber: viewLineNumber,
endLineNumber: viewLineNumber,
indent: 0
};
}
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
const viewLineCount = viewEndLineNumber - viewStartLineNumber + 1;
let result = new Array<number>(viewLineCount);
@@ -1218,6 +1283,10 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return this.model.getLineContent(viewLineNumber);
}
public getViewLineLength(viewLineNumber: number): number {
return this.model.getLineLength(viewLineNumber);
}
public getViewLineMinColumn(viewLineNumber: number): number {
return this.model.getLineMinColumn(viewLineNumber);
}

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { INewScrollPosition, IViewState } from 'vs/editor/common/editorCommon';
import { EndOfLinePreference, IModelDecorationOptions } from 'vs/editor/common/model';
import { INewScrollPosition } from 'vs/editor/common/editorCommon';
import { EndOfLinePreference, IModelDecorationOptions, IActiveIndentGuideInfo } from 'vs/editor/common/model';
import { IViewLineTokens } from 'vs/editor/common/core/lineTokens';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@@ -15,6 +15,7 @@ import { Scrollable, IScrollPosition } from 'vs/base/common/scrollable';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { ITheme } from 'vs/platform/theme/common/themeService';
import * as strings from 'vs/base/common/strings';
export interface IViewWhitespaceViewportData {
readonly id: number;
@@ -63,9 +64,6 @@ export interface IViewLayout {
getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData;
getWhitespaces(): IEditorWhitespace[];
saveState(): IViewState;
reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number; };
isAfterLines(verticalOffset: number): boolean;
getLineNumberAtVerticalOffset(verticalOffset: number): number;
getVerticalOffsetForLineNumber(lineNumber: number): number;
@@ -122,9 +120,11 @@ export interface IViewModel {
* Gives a hint that a lot of requests are about to come in for these line numbers.
*/
setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void;
setHasFocus(hasFocus: boolean): void;
getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[];
getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData;
getViewLineData(lineNumber: number): ViewLineData;
getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData;
getCompletelyVisibleViewRange(): Range;
getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range;
@@ -132,6 +132,8 @@ export interface IViewModel {
getTabSize(): number;
getLineCount(): number;
getLineContent(lineNumber: number): string;
getLineLength(lineNumber: number): number;
getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[];
getLineMinColumn(lineNumber: number): number;
getLineMaxColumn(lineNumber: number): number;
@@ -146,7 +148,7 @@ export interface IViewModel {
deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position;
getEOL(): string;
getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string | string[];
getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean, forceCRLF: boolean): string | string[];
getHTMLToCopy(ranges: Range[], emptySelectionClipboard: boolean): string;
}
@@ -210,13 +212,13 @@ export class ViewLineRenderingData {
*/
public readonly content: string;
/**
* If set to false, it is guaranteed that `content` contains only LTR chars.
* Describes if `content` contains RTL characters.
*/
public readonly mightContainRTL: boolean;
public readonly containsRTL: boolean;
/**
* If set to false, it is guaranteed that `content` contains only basic ASCII chars.
* Describes if `content` contains non basic ASCII chars.
*/
public readonly mightContainNonBasicASCII: boolean;
public readonly isBasicASCII: boolean;
/**
* The tokens at this view line.
*/
@@ -243,18 +245,35 @@ export class ViewLineRenderingData {
this.minColumn = minColumn;
this.maxColumn = maxColumn;
this.content = content;
this.mightContainRTL = mightContainRTL;
this.mightContainNonBasicASCII = mightContainNonBasicASCII;
this.isBasicASCII = ViewLineRenderingData.isBasicASCII(content, mightContainNonBasicASCII);
this.containsRTL = ViewLineRenderingData.containsRTL(content, this.isBasicASCII, mightContainRTL);
this.tokens = tokens;
this.inlineDecorations = inlineDecorations;
this.tabSize = tabSize;
}
public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean {
if (mightContainNonBasicASCII) {
return strings.isBasicASCII(lineContent);
}
return true;
}
public static containsRTL(lineContent: string, isBasicASCII: boolean, mightContainRTL: boolean): boolean {
if (!isBasicASCII && mightContainRTL) {
return strings.containsRTL(lineContent);
}
return false;
}
}
export const enum InlineDecorationType {
Regular = 0,
Before = 1,
After = 2
After = 2,
RegularAffectingLetterSpacing = 3
}
export class InlineDecoration {

View File

@@ -124,7 +124,7 @@ export class ViewModelDecorations implements IDisposable {
decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration;
if (decorationOptions.inlineClassName) {
let inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, InlineDecorationType.Regular);
let inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, decorationOptions.inlineClassNameAffectsLetterSpacing ? InlineDecorationType.RegularAffectingLetterSpacing : InlineDecorationType.Regular);
let intersectedStartLineNumber = Math.max(startLineNumber, viewRange.startLineNumber);
let intersectedEndLineNumber = Math.min(endLineNumber, viewRange.endLineNumber);
for (let j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++) {

View File

@@ -11,7 +11,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
import { TokenizationRegistry, ColorId, LanguageId } from 'vs/editor/common/modes';
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, IOverviewRulerDecorations } from 'vs/editor/common/viewModel/viewModel';
import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { SplitLinesCollection, IViewModelLinesCollection, IdentityLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer';
@@ -23,7 +23,7 @@ import { Color } from 'vs/base/common/color';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
import { ITextModel, EndOfLinePreference } from 'vs/editor/common/model';
import { ITextModel, EndOfLinePreference, TrackedRangeStickiness, IActiveIndentGuideInfo } from 'vs/editor/common/model';
const USE_IDENTITY_LINES_COLLECTION = true;
@@ -32,20 +32,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
private readonly editorId: number;
private readonly configuration: editorCommon.IConfiguration;
private readonly model: ITextModel;
private hasFocus: boolean;
private viewportStartLine: number;
private viewportStartLineTrackedRange: string;
private viewportStartLineTop: number;
private readonly lines: IViewModelLinesCollection;
public readonly coordinatesConverter: ICoordinatesConverter;
public readonly viewLayout: ViewLayout;
private readonly decorations: ViewModelDecorations;
private _centeredViewLine: number;
constructor(editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
super();
this.editorId = editorId;
this.configuration = configuration;
this.model = model;
this.hasFocus = false;
this.viewportStartLine = -1;
this.viewportStartLineTrackedRange = null;
this.viewportStartLineTop = 0;
if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) {
@@ -83,8 +89,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
}
}));
this._centeredViewLine = -1;
this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter);
this._registerModelEvents();
@@ -114,13 +118,22 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
super.dispose();
this.decorations.dispose();
this.lines.dispose();
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
}
public setHasFocus(hasFocus: boolean): void {
this.hasFocus = hasFocus;
}
private _onConfigurationChanged(eventsCollector: viewEvents.ViewEventsCollector, e: IConfigurationChangedEvent): void {
// We might need to restore the current centered view range, so save it (if available)
const previousCenteredModelRange = this.getCenteredRangeInViewport();
let revealPreviousCenteredModelRange = false;
let previousViewportStartModelPosition: Position = null;
if (this.viewportStartLine !== -1) {
let previousViewportStartViewPosition = new Position(this.viewportStartLine, this.getLineMinColumn(this.viewportStartLine));
previousViewportStartModelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(previousViewportStartViewPosition);
}
let restorePreviousViewportStart = false;
const conf = this.configuration.editor;
@@ -133,7 +146,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
if (this.viewLayout.getCurrentScrollTop() !== 0) {
// Never change the scroll position from 0 to something else...
revealPreviousCenteredModelRange = true;
restorePreviousViewportStart = true;
}
}
@@ -146,23 +159,16 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
eventsCollector.emit(new viewEvents.ViewConfigurationChangedEvent(e));
this.viewLayout.onConfigurationChanged(e);
if (revealPreviousCenteredModelRange && previousCenteredModelRange) {
// modelLine -> viewLine
const newCenteredViewRange = this.coordinatesConverter.convertModelRangeToViewRange(previousCenteredModelRange);
// Send a reveal event to restore the centered content
eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(
newCenteredViewRange,
viewEvents.VerticalRevealType.Center,
false,
editorCommon.ScrollType.Immediate
));
if (restorePreviousViewportStart && previousViewportStartModelPosition) {
const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(previousViewportStartModelPosition);
const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);
this.viewLayout.deltaScrollNow(0, viewPositionTop - this.viewportStartLineTop);
}
}
private _registerModelEvents(): void {
this._register(this.model.onDidChangeRawContent((e) => {
this._register(this.model.onDidChangeRawContentFast((e) => {
try {
const eventsCollector = this._beginEmit();
@@ -225,6 +231,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
}
}
this.lines.acceptVersionId(versionId);
this.viewLayout.onHeightMaybeChanged();
if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) {
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
@@ -236,8 +243,18 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
}
// Update the configuration and reset the centered view line
this._centeredViewLine = -1;
this.viewportStartLine = -1;
this.configuration.setMaxLineNumber(this.model.getLineCount());
// Recover viewport
if (!this.hasFocus && this.model.getAttachedEditorCount() >= 2 && this.viewportStartLineTrackedRange) {
const modelRange = this.model._getTrackedRange(this.viewportStartLineTrackedRange);
if (modelRange) {
const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelRange.getStartPosition());
const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);
this.viewLayout.deltaScrollNow(0, viewPositionTop - this.viewportStartLineTop);
}
}
}));
this._register(this.model.onDidChangeTokens((e) => {
@@ -311,16 +328,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
}
}
public getCenteredRangeInViewport(): Range {
if (this._centeredViewLine === -1) {
// Never got rendered or not rendered since last content change event
return null;
}
let viewLineNumber = this._centeredViewLine;
let currentCenteredViewRange = new Range(viewLineNumber, this.getLineMinColumn(viewLineNumber), viewLineNumber, this.getLineMaxColumn(viewLineNumber));
return this.coordinatesConverter.convertViewRangeToModelRange(currentCenteredViewRange);
}
public getVisibleRanges(): Range[] {
const visibleViewRange = this.getCompletelyVisibleViewRange();
const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange);
@@ -388,6 +395,43 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
);
}
public saveState(): editorCommon.IViewState {
const compatViewState = this.viewLayout.saveState();
const scrollTop = compatViewState.scrollTop;
const firstViewLineNumber = this.viewLayout.getLineNumberAtVerticalOffset(scrollTop);
const firstPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(firstViewLineNumber, this.getLineMinColumn(firstViewLineNumber)));
const firstPositionDeltaTop = this.viewLayout.getVerticalOffsetForLineNumber(firstViewLineNumber) - scrollTop;
return {
scrollLeft: compatViewState.scrollLeft,
firstPosition: firstPosition,
firstPositionDeltaTop: firstPositionDeltaTop
};
}
public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
if (typeof state.firstPosition === 'undefined') {
// This is a view state serialized by an older version
return this._reduceRestoreStateCompatibility(state);
}
const modelPosition = this.model.validatePosition(state.firstPosition);
const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
const scrollTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber) - state.firstPositionDeltaTop;
return {
scrollLeft: state.scrollLeft,
scrollTop: scrollTop
};
}
private _reduceRestoreStateCompatibility(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
return {
scrollLeft: state.scrollLeft,
scrollTop: state.scrollTopWithoutViewZones
};
}
public getTabSize(): number {
return this.model.getOptions().tabSize;
}
@@ -400,8 +444,16 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
* Gives a hint that a lot of requests are about to come in for these line numbers.
*/
public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void {
this._centeredViewLine = centeredLineNumber;
this.lines.warmUpLookupCache(startLineNumber, endLineNumber);
this.viewportStartLine = startLineNumber;
let position = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, this.getLineMinColumn(startLineNumber)));
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
this.viewportStartLineTop = this.viewLayout.getVerticalOffsetForLineNumber(startLineNumber);
}
public getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
return this.lines.getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber);
}
public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] {
@@ -412,6 +464,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
return this.lines.getViewLineContent(lineNumber);
}
public getLineLength(lineNumber: number): number {
return this.lines.getViewLineLength(lineNumber);
}
public getLineMinColumn(lineNumber: number): number {
return this.lines.getViewLineMinColumn(lineNumber);
}
@@ -460,6 +516,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
);
}
public getViewLineData(lineNumber: number): ViewLineData {
return this.lines.getViewLineData(lineNumber);
}
public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData {
let result = this.lines.getViewLinesData(startLineNumber, endLineNumber, needed);
return new MinimapLinesRenderingData(
@@ -514,8 +574,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
return this.model.getEOL();
}
public getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string | string[] {
const newLineCharacter = this.model.getEOL();
public getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean, forceCRLF: boolean): string | string[] {
const newLineCharacter = forceCRLF ? '\r\n' : this.model.getEOL();
ranges = ranges.slice(0);
ranges.sort(Range.compareRangesUsingStarts);
@@ -543,7 +603,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
let result: string[] = [];
for (let i = 0; i < nonEmptyRanges.length; i++) {
result.push(this.getValueInRange(nonEmptyRanges[i], EndOfLinePreference.TextDefined));
result.push(this.getValueInRange(nonEmptyRanges[i], forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));
}
return result.length === 1 ? result[0] : result;
}

View File

@@ -33,7 +33,7 @@ class JumpToBracketAction extends EditorAction {
alias: 'Go to Bracket',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH
}
});
@@ -176,41 +176,47 @@ export class BracketMatchingController extends Disposable implements editorCommo
if (!model) {
return;
}
const selection = this._editor.getSelection();
if (!selection.isEmpty()) {
return;
}
const position = selection.getStartPosition();
let newSelections: Selection[] = [];
let brackets = model.matchBracket(position);
this._editor.getSelections().forEach(selection => {
const position = selection.getStartPosition();
let openBracket: Position = null;
let closeBracket: Position = null;
let brackets = model.matchBracket(position);
if (!brackets) {
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.matchBracket(nextBracket.range.getStartPosition());
let openBracket: Position = null;
let closeBracket: Position = null;
if (!brackets) {
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.matchBracket(nextBracket.range.getStartPosition());
}
}
}
if (brackets) {
if (brackets[0].startLineNumber === brackets[1].startLineNumber) {
openBracket = brackets[1].startColumn < brackets[0].startColumn ?
brackets[1].getStartPosition() : brackets[0].getStartPosition();
closeBracket = brackets[1].startColumn < brackets[0].startColumn ?
brackets[0].getEndPosition() : brackets[1].getEndPosition();
} else {
openBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
brackets[1].getStartPosition() : brackets[0].getStartPosition();
closeBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
brackets[0].getEndPosition() : brackets[1].getEndPosition();
if (brackets) {
if (brackets[0].startLineNumber === brackets[1].startLineNumber) {
openBracket = brackets[1].startColumn < brackets[0].startColumn ?
brackets[1].getStartPosition() : brackets[0].getStartPosition();
closeBracket = brackets[1].startColumn < brackets[0].startColumn ?
brackets[0].getEndPosition() : brackets[1].getEndPosition();
} else {
openBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
brackets[1].getStartPosition() : brackets[0].getStartPosition();
closeBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
brackets[0].getEndPosition() : brackets[1].getEndPosition();
}
}
}
if (openBracket && closeBracket) {
this._editor.setSelection(new Range(openBracket.lineNumber, openBracket.column, closeBracket.lineNumber, closeBracket.column));
if (openBracket && closeBracket) {
newSelections.push(new Selection(openBracket.lineNumber, openBracket.column, closeBracket.lineNumber, closeBracket.column));
}
});
if (newSelections.length > 0) {
this._editor.setSelections(newSelections);
this._editor.revealRange(newSelections[0]);
}
}

View File

@@ -144,4 +144,57 @@ suite('bracket matching', () => {
model.dispose();
mode.dispose();
});
test('issue #45369: Select to Bracket with multicursor', () => {
let mode = new BracketMode();
let model = TextModel.createFromString('{ } { } { }', undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor, cursor) => {
let bracketMatchingController = editor.registerAndInstantiateContribution<BracketMatchingController>(BracketMatchingController);
// cursors inside brackets become selections of the entire bracket contents
editor.setSelections([
new Selection(1, 3, 1, 3),
new Selection(1, 10, 1, 10),
new Selection(1, 17, 1, 17)
]);
bracketMatchingController.selectToBracket();
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(1, 8, 1, 13),
new Selection(1, 16, 1, 19)
]);
// cursors to the left of bracket pairs become selections of the entire pair
editor.setSelections([
new Selection(1, 1, 1, 1),
new Selection(1, 6, 1, 6),
new Selection(1, 14, 1, 14)
]);
bracketMatchingController.selectToBracket();
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(1, 8, 1, 13),
new Selection(1, 16, 1, 19)
]);
// cursors just right of a bracket pair become selections of the entire pair
editor.setSelections([
new Selection(1, 5, 1, 5),
new Selection(1, 13, 1, 13),
new Selection(1, 19, 1, 19)
]);
bracketMatchingController.selectToBracket();
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(1, 8, 1, 13),
new Selection(1, 16, 1, 19)
]);
bracketMatchingController.dispose();
});
model.dispose();
mode.dispose();
});
});

View File

@@ -6,15 +6,56 @@
import * as nls from 'vs/nls';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { isLowSurrogate, isHighSurrogate } from 'vs/base/common/strings';
import { Range } from 'vs/editor/common/core/range';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { ICommand } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerEditorAction, EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ITextModel } from 'vs/editor/common/model';
class TransposeLettersAction extends EditorAction {
private positionLeftOf(start: IPosition, model: ITextModel): Position {
let column = start.column;
let lineNumber = start.lineNumber;
if (column > model.getLineMinColumn(lineNumber)) {
if (isLowSurrogate(model.getLineContent(lineNumber).charCodeAt(column - 2))) {
// character before column is a low surrogate
column = column - 2;
} else {
column = column - 1;
}
} else if (lineNumber > 1) {
lineNumber = lineNumber - 1;
column = model.getLineMaxColumn(lineNumber);
}
return new Position(lineNumber, column);
}
private positionRightOf(start: IPosition, model: ITextModel): Position {
let column = start.column;
let lineNumber = start.lineNumber;
if (column < model.getLineMaxColumn(lineNumber)) {
if (isHighSurrogate(model.getLineContent(lineNumber).charCodeAt(column - 1))) {
// character after column is a high surrogate
column = column + 2;
} else {
column = column + 1;
}
} else if (lineNumber < model.getLineCount()) {
lineNumber = lineNumber + 1;
column = 0;
}
return new Position(lineNumber, column);
}
constructor() {
super({
id: 'editor.action.transposeLetters',
@@ -22,7 +63,7 @@ class TransposeLettersAction extends EditorAction {
alias: 'Transpose Letters',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: {
primary: KeyMod.WinCtrl | KeyCode.KEY_T
@@ -36,30 +77,35 @@ class TransposeLettersAction extends EditorAction {
let commands: ICommand[] = [];
let selections = editor.getSelections();
for (let i = 0; i < selections.length; i++) {
let selection = selections[i];
for (let selection of selections) {
if (!selection.isEmpty()) {
continue;
}
let lineNumber = selection.startLineNumber;
let column = selection.startColumn;
if (column === 1) {
// at the beginning of line
continue;
}
let maxColumn = model.getLineMaxColumn(lineNumber);
if (column === maxColumn) {
// at the end of line
let lastColumn = model.getLineMaxColumn(lineNumber);
if (lineNumber === 1 && (column === 1 || (column === 2 && lastColumn === 2))) {
// at beginning of file, nothing to do
continue;
}
let lineContent = model.getLineContent(lineNumber);
let charToTheLeft = lineContent.charAt(column - 2);
let charToTheRight = lineContent.charAt(column - 1);
// handle special case: when at end of line, transpose left two chars
// otherwise, transpose left and right chars
let endPosition = (column === lastColumn) ?
selection.getPosition() :
this.positionRightOf(selection.getPosition(), model);
let replaceRange = new Range(lineNumber, column - 1, lineNumber, column + 1);
let middlePosition = this.positionLeftOf(endPosition, model);
let beginPosition = this.positionLeftOf(middlePosition, model);
commands.push(new ReplaceCommand(replaceRange, charToTheRight + charToTheLeft));
let leftChar = model.getValueInRange(Range.fromPositions(beginPosition, middlePosition));
let rightChar = model.getValueInRange(Range.fromPositions(middlePosition, endPosition));
let replaceRange = Range.fromPositions(beginPosition, endPosition);
commands.push(new ReplaceCommand(replaceRange, rightChar + leftChar));
}
if (commands.length > 0) {

View File

@@ -60,7 +60,7 @@ class ExecCommandCutAction extends ExecCommandAction {
constructor() {
let kbOpts: ICommandKeybindingsOptions = {
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_X,
win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, secondary: [KeyMod.Shift | KeyCode.Delete] }
};
@@ -97,7 +97,7 @@ class ExecCommandCopyAction extends ExecCommandAction {
constructor() {
let kbOpts: ICommandKeybindingsOptions = {
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyCode.Insert] }
};
@@ -135,7 +135,7 @@ class ExecCommandPasteAction extends ExecCommandAction {
constructor() {
let kbOpts: ICommandKeybindingsOptions = {
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_V,
win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }
};
@@ -168,7 +168,7 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction {
alias: 'Copy With Syntax Highlighting',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.textInputFocus,
primary: null
}
});

View File

@@ -2,46 +2,58 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range';
import { CodeActionProviderRegistry, CodeAction } from 'vs/editor/common/modes';
import { isFalsyOrEmpty, mergeSort, flatten } from 'vs/base/common/arrays';
import { asWinJsPromise } from 'vs/base/common/async';
import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { onUnexpectedExternalError, illegalArgument } from 'vs/base/common/errors';
import { IModelService } from 'vs/editor/common/services/modelService';
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { isFalsyOrEmpty, mergeSort } from 'vs/base/common/arrays';
import { CodeActionKind } from './codeActionTrigger';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { CodeAction, CodeActionProviderRegistry } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { CodeActionFilter, CodeActionKind } from './codeActionTrigger';
export function getCodeActions(model: ITextModel, range: Range, scope?: CodeActionKind): TPromise<CodeAction[]> {
export function getCodeActions(model: ITextModel, range: Range, filter?: CodeActionFilter): TPromise<CodeAction[]> {
const codeActionContext = { only: filter && filter.kind ? filter.kind.value : undefined };
const allResults: CodeAction[] = [];
const promises = CodeActionProviderRegistry.all(model).map(support => {
return asWinJsPromise(token => support.provideCodeActions(model, range, { only: scope ? scope.value : undefined }, token)).then(result => {
if (Array.isArray(result)) {
for (const quickFix of result) {
if (quickFix) {
if (!scope || (quickFix.kind && scope.contains(quickFix.kind))) {
allResults.push(quickFix);
}
}
}
return asWinJsPromise(token => support.provideCodeActions(model, range, codeActionContext, token)).then(providedCodeActions => {
if (!Array.isArray(providedCodeActions)) {
return [];
}
}, err => {
return providedCodeActions.filter(action => isValidAction(filter, action));
}, (err): CodeAction[] => {
onUnexpectedExternalError(err);
return [];
});
});
return TPromise.join(promises).then(
() => mergeSort(allResults, codeActionsComparator)
);
return TPromise.join(promises)
.then(flatten)
.then(allCodeActions => mergeSort(allCodeActions, codeActionsComparator));
}
function isValidAction(filter: CodeActionFilter | undefined, action: CodeAction): boolean {
if (!action) {
return false;
}
// Filter out actions by kind
if (filter && filter.kind && (!action.kind || !filter.kind.contains(action.kind))) {
return false;
}
// Don't return source actions unless they are explicitly requested
if (action.kind && CodeActionKind.Source.contains(action.kind) && (!filter || !filter.includeSourceActions)) {
return false;
}
return true;
}
function codeActionsComparator(a: CodeAction, b: CodeAction): number {
const aHasDiags = !isFalsyOrEmpty(a.diagnostics);
const bHasDiags = !isFalsyOrEmpty(b.diagnostics);
if (aHasDiags) {
@@ -58,7 +70,6 @@ function codeActionsComparator(a: CodeAction, b: CodeAction): number {
}
registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) {
const { resource, range } = args;
if (!(resource instanceof URI) || !Range.isIRange(range)) {
throw illegalArgument();

View File

@@ -2,30 +2,37 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { QuickFixContextMenu } from './quickFixWidget';
import { LightBulbWidget } from './lightBulbWidget';
import { QuickFixModel, QuickFixComputeEvent } from './quickFixModel';
import { CodeActionKind, CodeActionAutoApply } from './codeActionTrigger';
import { TPromise } from 'vs/base/common/winjs.base';
import { CodeAction } from 'vs/editor/common/modes';
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { IFileService } from 'vs/platform/files/common/files';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { MessageController } from 'vs/editor/contrib/message/messageController';
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IFileService } from 'vs/platform/files/common/files';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { CodeActionModel, CodeActionsComputeEvent, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger';
import { CodeActionContextMenu } from './codeActionWidget';
import { LightBulbWidget } from './lightBulbWidget';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
function contextKeyForSupportedActions(kind: CodeActionKind) {
return ContextKeyExpr.regex(
SUPPORTED_CODE_ACTIONS.keys()[0],
new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b'));
}
export class QuickFixController implements IEditorContribution {
@@ -36,8 +43,8 @@ export class QuickFixController implements IEditorContribution {
}
private _editor: ICodeEditor;
private _model: QuickFixModel;
private _quickFixContextMenu: QuickFixContextMenu;
private _model: CodeActionModel;
private _codeActionContextMenu: CodeActionContextMenu;
private _lightBulbWidget: LightBulbWidget;
private _disposables: IDisposable[] = [];
@@ -51,16 +58,16 @@ export class QuickFixController implements IEditorContribution {
@optional(IFileService) private _fileService: IFileService
) {
this._editor = editor;
this._model = new QuickFixModel(this._editor, markerService);
this._quickFixContextMenu = new QuickFixContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action));
this._model = new CodeActionModel(this._editor, markerService, contextKeyService);
this._codeActionContextMenu = new CodeActionContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action));
this._lightBulbWidget = new LightBulbWidget(editor);
this._updateLightBulbTitle();
this._disposables.push(
this._quickFixContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto' })),
this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} })),
this._lightBulbWidget.onClick(this._handleLightBulbSelect, this),
this._model.onDidChangeFixes(e => this._onQuickFixEvent(e)),
this._model.onDidChangeFixes(e => this._onCodeActionsEvent(e)),
this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)
);
}
@@ -70,28 +77,28 @@ export class QuickFixController implements IEditorContribution {
dispose(this._disposables);
}
private _onQuickFixEvent(e: QuickFixComputeEvent): void {
if (e && e.trigger.kind) {
private _onCodeActionsEvent(e: CodeActionsComputeEvent): void {
if (e && e.trigger.filter && e.trigger.filter.kind) {
// Triggered for specific scope
// Apply if we only have one action or requested autoApply, otherwise show menu
e.fixes.then(fixes => {
e.actions.then(fixes => {
if (e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
this._onApplyCodeAction(fixes[0]);
} else {
this._quickFixContextMenu.show(e.fixes, e.position);
this._codeActionContextMenu.show(e.actions, e.position);
}
});
return;
}
if (e && e.trigger.type === 'manual') {
this._quickFixContextMenu.show(e.fixes, e.position);
} else if (e && e.fixes) {
this._codeActionContextMenu.show(e.actions, e.position);
} else if (e && e.actions) {
// auto magically triggered
// * update an existing list of code actions
// * manage light bulb
if (this._quickFixContextMenu.isVisible) {
this._quickFixContextMenu.show(e.fixes, e.position);
if (this._codeActionContextMenu.isVisible) {
this._codeActionContextMenu.show(e.actions, e.position);
} else {
this._lightBulbWidget.model = e;
}
@@ -105,15 +112,11 @@ export class QuickFixController implements IEditorContribution {
}
private _handleLightBulbSelect(coords: { x: number, y: number }): void {
this._quickFixContextMenu.show(this._lightBulbWidget.model.fixes, coords);
this._codeActionContextMenu.show(this._lightBulbWidget.model.actions, coords);
}
public triggerFromEditorSelection(): void {
this._model.trigger({ type: 'manual' });
}
public triggerCodeActionFromEditorSelection(kind?: CodeActionKind, autoApply?: CodeActionAutoApply): void {
this._model.trigger({ type: 'manual', kind, autoApply });
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): TPromise<CodeAction[] | undefined> {
return this._model.trigger({ type: 'manual', filter, autoApply });
}
private _updateLightBulbTitle(): void {
@@ -128,16 +131,44 @@ export class QuickFixController implements IEditorContribution {
}
private async _onApplyCodeAction(action: CodeAction): TPromise<void> {
if (action.edit) {
await BulkEdit.perform(action.edit.edits, this._textModelService, this._fileService, this._editor);
}
if (action.command) {
await this._commandService.executeCommand(action.command.id, ...action.command.arguments);
}
await applyCodeAction(action, this._textModelService, this._fileService, this._commandService, this._editor);
}
}
export async function applyCodeAction(
action: CodeAction,
textModelService: ITextModelService,
fileService: IFileService,
commandService: ICommandService,
editor: ICodeEditor,
) {
if (action.edit) {
await BulkEdit.perform(action.edit.edits, textModelService, fileService, editor);
}
if (action.command) {
await commandService.executeCommand(action.command.id, ...action.command.arguments);
}
}
function showCodeActionsForEditorSelection(
editor: ICodeEditor,
notAvailableMessage: string,
filter?: CodeActionFilter,
autoApply?: CodeActionAutoApply
) {
const controller = QuickFixController.get(editor);
if (!controller) {
return;
}
const pos = editor.getPosition();
controller.triggerFromEditorSelection(filter, autoApply).then(codeActions => {
if (!codeActions || !codeActions.length) {
MessageController.get(editor).showMessage(notAvailableMessage, pos);
}
});
}
export class QuickFixAction extends EditorAction {
static readonly Id = 'editor.action.quickFix';
@@ -145,21 +176,18 @@ export class QuickFixAction extends EditorAction {
constructor() {
super({
id: QuickFixAction.Id,
label: nls.localize('quickfix.trigger.label', "Quick Fix"),
label: nls.localize('quickfix.trigger.label', "Quick Fix..."),
alias: 'Quick Fix',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyMod.CtrlCmd | KeyCode.US_DOT
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
let controller = QuickFixController.get(editor);
if (controller) {
controller.triggerFromEditorSelection();
}
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"));
}
}
@@ -212,11 +240,8 @@ export class CodeActionCommand extends EditorCommand {
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
const controller = QuickFixController.get(editor);
if (controller) {
const args = CodeActionCommandArgs.fromUser(userArg);
controller.triggerCodeActionFromEditorSelection(args.kind, args.apply);
}
const args = CodeActionCommandArgs.fromUser(userArg);
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), { kind: args.kind, includeSourceActions: true }, args.apply);
}
}
@@ -228,26 +253,86 @@ export class RefactorAction extends EditorAction {
constructor() {
super({
id: RefactorAction.Id,
label: nls.localize('refactor.label', "Refactor"),
label: nls.localize('refactor.label', "Refactor..."),
alias: 'Refactor',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R,
mac: {
primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R
}
},
menuOpts: {
group: '1_modification',
order: 2,
when: ContextKeyExpr.and(
EditorContextKeys.writable,
contextKeyForSupportedActions(CodeActionKind.Refactor)),
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
const controller = QuickFixController.get(editor);
if (controller) {
controller.triggerCodeActionFromEditorSelection(CodeActionKind.Refactor, CodeActionAutoApply.Never);
}
return showCodeActionsForEditorSelection(editor,
nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
{ kind: CodeActionKind.Refactor },
CodeActionAutoApply.Never);
}
}
registerEditorContribution(QuickFixController);
registerEditorAction(QuickFixAction);
registerEditorAction(RefactorAction);
registerEditorCommand(new CodeActionCommand());
export class SourceAction extends EditorAction {
static readonly Id = 'editor.action.sourceAction';
constructor() {
super({
id: SourceAction.Id,
label: nls.localize('source.label', "Source Action..."),
alias: 'Source Action',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
menuOpts: {
group: '1_modification',
order: 2.1,
when: ContextKeyExpr.and(
EditorContextKeys.writable,
contextKeyForSupportedActions(CodeActionKind.Source)),
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
return showCodeActionsForEditorSelection(editor,
nls.localize('editor.action.source.noneMessage', "No source actions available"),
{ kind: CodeActionKind.Source, includeSourceActions: true },
CodeActionAutoApply.Never);
}
}
export class OrganizeImportsAction extends EditorAction {
static readonly Id = 'editor.action.organizeImports';
constructor() {
super({
id: OrganizeImportsAction.Id,
label: nls.localize('organizeImports.label', "Organize Imports"),
alias: 'Organize Imports',
precondition: ContextKeyExpr.and(
EditorContextKeys.writable,
contextKeyForSupportedActions(CodeActionKind.SourceOrganizeImports)),
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_O
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
return showCodeActionsForEditorSelection(editor,
nls.localize('editor.action.organize.noneMessage', "No organize imports action available"),
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
CodeActionAutoApply.IfSingle);
}
}

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { SourceAction, QuickFixController, QuickFixAction, CodeActionCommand, RefactorAction, OrganizeImportsAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
registerEditorContribution(QuickFixController);
registerEditorAction(QuickFixAction);
registerEditorAction(RefactorAction);
registerEditorAction(SourceAction);
registerEditorAction(OrganizeImportsAction);
registerEditorCommand(new CodeActionCommand());

View File

@@ -2,29 +2,31 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event, { Emitter, debounceEvent } from 'vs/base/common/event';
import { Emitter, Event, debounceEvent } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { CodeActionProviderRegistry, CodeAction } from 'vs/editor/common/modes';
import { getCodeActions } from './quickFix';
import { CodeAction, CodeActionProviderRegistry } from 'vs/editor/common/modes';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { getCodeActions } from './codeAction';
import { CodeActionTrigger } from './codeActionTrigger';
import { Position } from 'vs/editor/common/core/position';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export class QuickFixOracle {
export const SUPPORTED_CODE_ACTIONS = new RawContextKey<string>('supportedCodeAction', '');
export class CodeActionOracle {
private _disposables: IDisposable[] = [];
constructor(
private _editor: ICodeEditor,
private _markerService: IMarkerService,
private _signalChange: (e: QuickFixComputeEvent) => any,
private _signalChange: (e: CodeActionsComputeEvent) => any,
delay: number = 250
) {
this._disposables.push(
@@ -37,12 +39,12 @@ export class QuickFixOracle {
this._disposables = dispose(this._disposables);
}
trigger(trigger: CodeActionTrigger): void {
trigger(trigger: CodeActionTrigger) {
let rangeOrSelection = this._getRangeOfMarker() || this._getRangeOfSelectionUnlessWhitespaceEnclosed();
if (!rangeOrSelection && trigger.type === 'manual') {
rangeOrSelection = this._editor.getSelection();
}
this._createEventAndSignalChange(trigger, rangeOrSelection);
return this._createEventAndSignalChange(trigger, rangeOrSelection);
}
private _onMarkerChanges(resources: URI[]): void {
@@ -99,51 +101,56 @@ export class QuickFixOracle {
return selection;
}
private _createEventAndSignalChange(trigger: CodeActionTrigger, rangeOrSelection: Range | Selection): void {
private _createEventAndSignalChange(trigger: CodeActionTrigger, rangeOrSelection: Range | Selection): TPromise<CodeAction[] | undefined> {
if (!rangeOrSelection) {
// cancel
this._signalChange({
trigger,
range: undefined,
position: undefined,
fixes: undefined,
actions: undefined,
});
return TPromise.as(undefined);
} else {
// actual
const model = this._editor.getModel();
const range = model.validateRange(rangeOrSelection);
const position = rangeOrSelection instanceof Selection ? rangeOrSelection.getPosition() : rangeOrSelection.getStartPosition();
const fixes = getCodeActions(model, range, trigger && trigger.kind);
const actions = getCodeActions(model, range, trigger && trigger.filter);
this._signalChange({
trigger,
range,
position,
fixes
actions
});
return actions;
}
}
}
export interface QuickFixComputeEvent {
export interface CodeActionsComputeEvent {
trigger: CodeActionTrigger;
range: Range;
position: Position;
fixes: TPromise<CodeAction[]>;
actions: TPromise<CodeAction[]>;
}
export class QuickFixModel {
export class CodeActionModel {
private _editor: ICodeEditor;
private _markerService: IMarkerService;
private _quickFixOracle: QuickFixOracle;
private _onDidChangeFixes = new Emitter<QuickFixComputeEvent>();
private _codeActionOracle: CodeActionOracle;
private _onDidChangeFixes = new Emitter<CodeActionsComputeEvent>();
private _disposables: IDisposable[] = [];
private readonly _supportedCodeActions: IContextKey<string>;
constructor(editor: ICodeEditor, markerService: IMarkerService) {
constructor(editor: ICodeEditor, markerService: IMarkerService, contextKeyService: IContextKeyService) {
this._editor = editor;
this._markerService = markerService;
this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService);
this._disposables.push(this._editor.onDidChangeModel(() => this._update()));
this._disposables.push(this._editor.onDidChangeModelLanguage(() => this._update()));
this._disposables.push(CodeActionProviderRegistry.onDidChange(this._update, this));
@@ -153,18 +160,18 @@ export class QuickFixModel {
dispose(): void {
this._disposables = dispose(this._disposables);
dispose(this._quickFixOracle);
dispose(this._codeActionOracle);
}
get onDidChangeFixes(): Event<QuickFixComputeEvent> {
get onDidChangeFixes(): Event<CodeActionsComputeEvent> {
return this._onDidChangeFixes.event;
}
private _update(): void {
if (this._quickFixOracle) {
this._quickFixOracle.dispose();
this._quickFixOracle = undefined;
if (this._codeActionOracle) {
this._codeActionOracle.dispose();
this._codeActionOracle = undefined;
this._onDidChangeFixes.fire(undefined);
}
@@ -172,14 +179,26 @@ export class QuickFixModel {
&& CodeActionProviderRegistry.has(this._editor.getModel())
&& !this._editor.getConfiguration().readOnly) {
this._quickFixOracle = new QuickFixOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p));
this._quickFixOracle.trigger({ type: 'auto' });
const supportedActions: string[] = [];
for (const provider of CodeActionProviderRegistry.all(this._editor.getModel())) {
if (Array.isArray(provider.providedCodeActionKinds)) {
supportedActions.push(...provider.providedCodeActionKinds);
}
}
this._supportedCodeActions.set(supportedActions.join(' '));
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p));
this._codeActionOracle.trigger({ type: 'auto' });
} else {
this._supportedCodeActions.reset();
}
}
trigger(trigger: CodeActionTrigger): void {
if (this._quickFixOracle) {
this._quickFixOracle.trigger(trigger);
trigger(trigger: CodeActionTrigger): TPromise<CodeAction[] | undefined> {
if (this._codeActionOracle) {
return this._codeActionOracle.trigger(trigger);
}
return TPromise.as(undefined);
}
}

View File

@@ -10,6 +10,8 @@ export class CodeActionKind {
public static readonly Empty = new CodeActionKind('');
public static readonly Refactor = new CodeActionKind('refactor');
public static readonly Source = new CodeActionKind('source');
public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports');
constructor(
public readonly value: string
@@ -26,8 +28,13 @@ export enum CodeActionAutoApply {
Never = 3
}
export interface CodeActionFilter {
readonly kind?: CodeActionKind;
readonly includeSourceActions?: boolean;
}
export interface CodeActionTrigger {
type: 'auto' | 'manual';
kind?: CodeActionKind;
autoApply?: CodeActionAutoApply;
readonly type: 'auto' | 'manual';
readonly filter?: CodeActionFilter;
readonly autoApply?: CodeActionAutoApply;
}

View File

@@ -3,20 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { always } from 'vs/base/common/async';
import { getDomNodePagePosition } from 'vs/base/browser/dom';
import { Position } from 'vs/editor/common/core/position';
import { Action } from 'vs/base/common/actions';
import { always } from 'vs/base/common/async';
import { canceled } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { CodeAction } from 'vs/editor/common/modes';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
import Event, { Emitter } from 'vs/base/common/event';
import { ScrollType } from 'vs/editor/common/editorCommon';
export class QuickFixContextMenu {
export class CodeActionContextMenu {
private _visible: boolean;
private _onDidExecuteCodeAction = new Emitter<void>();
@@ -39,6 +38,12 @@ export class QuickFixContextMenu {
() => this._onDidExecuteCodeAction.fire(undefined));
});
});
}).then(actions => {
if (!this._editor.getDomNode()) {
// cancel when editor went off-dom
return TPromise.wrapError<any>(canceled());
}
return actions;
});
this._contextMenuService.showContextMenu({

View File

@@ -2,17 +2,16 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./lightBulbWidget';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor';
import * as dom from 'vs/base/browser/dom';
import { ICodeEditor, IContentWidget, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { QuickFixComputeEvent } from './quickFixModel';
import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import 'vs/css!./lightBulbWidget';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { TextModel } from 'vs/editor/common/model/textModel';
import { CodeActionsComputeEvent } from './codeActionModel';
export class LightBulbWidget implements IDisposable, IContentWidget {
@@ -26,7 +25,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
readonly onClick: Event<{ x: number, y: number }> = this._onClick.event;
private _position: IContentWidgetPosition;
private _model: QuickFixComputeEvent;
private _model: CodeActionsComputeEvent;
private _futureFixes = new CancellationTokenSource();
constructor(editor: ICodeEditor) {
@@ -45,6 +44,8 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
}
}));
this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => {
// Make sure that focus / cursor location is not lost when clicking widget icon
this._editor.focus();
// a bit of extra work to make sure the menu
// doesn't cover the line-text
const { top, height } = dom.getDomNodePagePosition(this._domNode);
@@ -98,7 +99,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
return this._position;
}
set model(value: QuickFixComputeEvent) {
set model(value: CodeActionsComputeEvent) {
if (this._position && (!value.position || this._position.position.lineNumber !== value.position.lineNumber)) {
// hide when getting a 'hide'-request or when currently
@@ -113,7 +114,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
const { token } = this._futureFixes;
this._model = value;
this._model.fixes.done(fixes => {
this._model.actions.done(fixes => {
if (!token.isCancellationRequested && fixes && fixes.length > 0) {
this._show();
} else {
@@ -124,7 +125,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
});
}
get model(): QuickFixComputeEvent {
get model(): CodeActionsComputeEvent {
return this._model;
}

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