提交 fb5b68d1 编写于 作者: A Alex Dima

Merge pull request #5423 from outcoldman/vscode and improve the proposed changes

- add `editor.useTabStops` for tab/backspace behaviour
- add `editor.trimAutoWhitespace` for auto inserted whitespace cleanup
上级 c2f0492c
......@@ -303,8 +303,8 @@ export function getLeadingWhitespace(str: string): string {
* Returns last index of the string that is not whitespace.
* If string is empty or contains only whitespaces, returns -1
*/
export function lastNonWhitespaceIndex(str: string): number {
for (let i = str.length - 1; i >= 0; i--) {
export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number {
for (let i = startIndex; i >= 0; i--) {
if (str.charAt(i) !== ' ' && str.charAt(i) !== '\t') {
return i;
}
......
......@@ -157,4 +157,15 @@ suite('Strings', () => {
assert.strictEqual(strings.repeat(' ', 0), '');
assert.strictEqual(strings.repeat('abc', 2), 'abcabc');
});
test('lastNonWhitespaceIndex', () => {
assert.strictEqual(strings.lastNonWhitespaceIndex('abc \t \t '), 2);
assert.strictEqual(strings.lastNonWhitespaceIndex('abc'), 2);
assert.strictEqual(strings.lastNonWhitespaceIndex('abc\t'), 2);
assert.strictEqual(strings.lastNonWhitespaceIndex('abc '), 2);
assert.strictEqual(strings.lastNonWhitespaceIndex('abc \t \t '), 2);
assert.strictEqual(strings.lastNonWhitespaceIndex('abc \t \t abc \t \t '), 11);
assert.strictEqual(strings.lastNonWhitespaceIndex('abc \t \t abc \t \t ', 8), 2);
assert.strictEqual(strings.lastNonWhitespaceIndex(' \t \t '), -1);
});
});
\ No newline at end of file
......@@ -107,6 +107,8 @@ export class InternalEditorOptions implements editorCommon.IInternalEditorOption
folding: boolean;
renderWhitespace: boolean;
indentGuides: boolean;
useTabStops: boolean;
trimAutoWhitespace: boolean;
layoutInfo: editorCommon.IEditorLayoutInfo;
fontInfo: editorCommon.FontInfo;
editorClassName: string;
......@@ -173,6 +175,8 @@ export class InternalEditorOptions implements editorCommon.IInternalEditorOption
this.folding = Boolean(input.folding);
this.renderWhitespace = Boolean(input.renderWhitespace);
this.indentGuides = Boolean(input.indentGuides);
this.useTabStops = Boolean(input.useTabStops);
this.trimAutoWhitespace = Boolean(input.trimAutoWhitespace);
this.layoutInfo = {
width: Number(input.layoutInfo.width)|0,
height: Number(input.layoutInfo.height)|0,
......@@ -359,6 +363,8 @@ class InternalEditorOptionsHelper {
folding: toBoolean(opts.folding),
renderWhitespace: toBoolean(opts.renderWhitespace),
indentGuides: toBoolean(opts.indentGuides),
useTabStops: toBoolean(opts.useTabStops),
trimAutoWhitespace: toBoolean(opts.trimAutoWhitespace),
layoutInfo: layoutInfo,
fontInfo: fontInfo,
......@@ -443,9 +449,11 @@ class InternalEditorOptionsHelper {
folding: (prevOpts.folding !== newOpts.folding),
renderWhitespace: (prevOpts.renderWhitespace !== newOpts.renderWhitespace),
indentGuides: (prevOpts.indentGuides !== newOpts.indentGuides),
useTabStops: (prevOpts.useTabStops !== newOpts.useTabStops),
trimAutoWhitespace: (prevOpts.trimAutoWhitespace !== newOpts.trimAutoWhitespace),
layoutInfo: (!EditorLayoutProvider.layoutEqual(prevOpts.layoutInfo, newOpts.layoutInfo)),
fontInfo: (!prevOpts.fontInfo.equals(newOpts.fontInfo)),
fontInfo: (!prevOpts.fontInfo.equals(newOpts.fontInfo)),
editorClassName: (prevOpts.editorClassName !== newOpts.editorClassName),
wrappingInfo: (!this._wrappingInfoEqual(prevOpts.wrappingInfo, newOpts.wrappingInfo)),
observedOuterWidth: (prevOpts.observedOuterWidth !== newOpts.observedOuterWidth),
......@@ -926,6 +934,16 @@ let editorConfiguration:IConfigurationNode = {
'default': DefaultConfig.editor.folding,
'description': nls.localize('folding', "Controls whether the editor has code folding enabled")
},
'editor.useTabStops' : {
'type': 'boolean',
'default': DefaultConfig.editor.useTabStops,
'description': nls.localize('useTabStops', "Inserting and deleting whitespace follows tab stops")
},
'editor.trimAutoWhitespace' : {
'type': 'boolean',
'default': DefaultConfig.editor.trimAutoWhitespace,
'description': nls.localize('trimAutoWhitespace', "Remove trailing auto inserted whitespace")
},
'diffEditor.renderSideBySide' : {
'type': 'boolean',
'default': true,
......
......@@ -90,6 +90,8 @@ class ConfigClass implements IConfiguration {
folding: true,
renderWhitespace: false,
indentGuides: false,
useTabStops: true,
trimAutoWhitespace: true,
fontFamily: (
platform.isMacintosh ? DEFAULT_MAC_FONT_FAMILY : (platform.isLinux ? DEFAULT_LINUX_FONT_FAMILY : DEFAULT_WINDOWS_FONT_FAMILY)
......
......@@ -40,6 +40,7 @@ interface IMultipleCursorOperationContext {
hasExecutedCommands: boolean;
isCursorUndo: boolean;
executeCommands: editorCommon.ICommand[];
isAutoWhitespaceCommand: boolean[];
postOperationRunnables: IPostOperationRunnable[];
requestScrollDeltaLines: number;
setColumnSelectToLineNumber: number;
......@@ -313,6 +314,7 @@ export class Cursor extends EventEmitter {
eventSource: eventSource,
eventData: eventData,
executeCommands: [],
isAutoWhitespaceCommand: [],
hasExecutedCommands: false,
isCursorUndo: false,
postOperationRunnables: [],
......@@ -440,7 +442,7 @@ export class Cursor extends EventEmitter {
this._columnSelectToLineNumber = ctx.setColumnSelectToLineNumber;
this._columnSelectToVisualColumn = ctx.setColumnSelectToVisualColumn;
ctx.hasExecutedCommands = this._internalExecuteCommands(ctx.executeCommands, ctx.postOperationRunnables) || ctx.hasExecutedCommands;
ctx.hasExecutedCommands = this._internalExecuteCommands(ctx.executeCommands, ctx.isAutoWhitespaceCommand, ctx.postOperationRunnables) || ctx.hasExecutedCommands;
ctx.executeCommands = [];
if (ctx.shouldPushStackElementAfter) {
......@@ -480,7 +482,7 @@ export class Cursor extends EventEmitter {
return true;
}
private _getEditOperationsFromCommand(ctx: IExecContext, majorIdentifier: number, command: editorCommon.ICommand): ICommandData {
private _getEditOperationsFromCommand(ctx: IExecContext, majorIdentifier: number, command: editorCommon.ICommand, isAutoWhitespaceCommand:boolean): ICommandData {
// This method acts as a transaction, if the command fails
// everything it has done is ignored
var operations: editorCommon.IIdentifiedSingleEditOperation[] = [],
......@@ -498,7 +500,8 @@ export class Cursor extends EventEmitter {
},
range: selection,
text: text,
forceMoveMarkers: false
forceMoveMarkers: false,
isAutoWhitespaceEdit: isAutoWhitespaceCommand
});
};
......@@ -560,7 +563,7 @@ export class Cursor extends EventEmitter {
};
}
private _getEditOperations(ctx: IExecContext, commands: editorCommon.ICommand[]): ICommandsData {
private _getEditOperations(ctx: IExecContext, commands: editorCommon.ICommand[], isAutoWhitespaceCommand:boolean[]): ICommandsData {
var oneResult: ICommandData;
var operations: editorCommon.IIdentifiedSingleEditOperation[] = [];
var hadTrackedRanges: boolean[] = [];
......@@ -568,7 +571,7 @@ export class Cursor extends EventEmitter {
for (var i = 0; i < commands.length; i++) {
if (commands[i]) {
oneResult = this._getEditOperationsFromCommand(ctx, i, commands[i]);
oneResult = this._getEditOperationsFromCommand(ctx, i, commands[i], isAutoWhitespaceCommand[i]);
operations = operations.concat(oneResult.operations);
hadTrackedRanges[i] = oneResult.hadTrackedRange;
anyoneHadTrackedRange = anyoneHadTrackedRange || hadTrackedRanges[i];
......@@ -634,7 +637,7 @@ export class Cursor extends EventEmitter {
return loserCursorsMap;
}
private _collapseDeleteCommands(rawCmds: editorCommon.ICommand[], postOperationRunnables: IPostOperationRunnable[]): boolean {
private _collapseDeleteCommands(rawCmds: editorCommon.ICommand[], isAutoWhitespaceCommand:boolean[], postOperationRunnables: IPostOperationRunnable[]): boolean {
if (rawCmds.length === 1) {
return ;
}
......@@ -690,15 +693,15 @@ export class Cursor extends EventEmitter {
}
}
private _internalExecuteCommands(commands: editorCommon.ICommand[], postOperationRunnables: IPostOperationRunnable[]): boolean {
private _internalExecuteCommands(commands: editorCommon.ICommand[], isAutoWhitespaceCommand: boolean[], postOperationRunnables: IPostOperationRunnable[]): boolean {
var ctx:IExecContext = {
selectionStartMarkers: [],
positionMarkers: []
};
this._collapseDeleteCommands(commands, postOperationRunnables);
this._collapseDeleteCommands(commands, isAutoWhitespaceCommand, postOperationRunnables);
var r = this._innerExecuteCommands(ctx, commands, postOperationRunnables);
var r = this._innerExecuteCommands(ctx, commands, isAutoWhitespaceCommand, postOperationRunnables);
for (var i = 0; i < ctx.selectionStartMarkers.length; i++) {
this.model._removeMarker(ctx.selectionStartMarkers[i]);
this.model._removeMarker(ctx.positionMarkers[i]);
......@@ -719,7 +722,7 @@ export class Cursor extends EventEmitter {
return true;
}
private _innerExecuteCommands(ctx: IExecContext, commands: editorCommon.ICommand[], postOperationRunnables: IPostOperationRunnable[]): boolean {
private _innerExecuteCommands(ctx: IExecContext, commands: editorCommon.ICommand[], isAutoWhitespaceCommand: boolean[], postOperationRunnables: IPostOperationRunnable[]): boolean {
if (this.configuration.editor.readOnly) {
return false;
......@@ -731,7 +734,7 @@ export class Cursor extends EventEmitter {
var selectionsBefore = this.cursors.getSelections();
var commandsData = this._getEditOperations(ctx, commands);
var commandsData = this._getEditOperations(ctx, commands, isAutoWhitespaceCommand);
if (commandsData.operations.length === 0 && !commandsData.anyoneHadTrackedRange) {
return false;
}
......@@ -771,6 +774,10 @@ export class Cursor extends EventEmitter {
}
for (var i = 0; i < inverseEditOperations.length; i++) {
var op = inverseEditOperations[i];
if (!op.identifier) {
// perhaps auto whitespace trim edits
continue;
}
groupedInverseEditOperations[op.identifier.major].push(op);
}
var minorBasedSorter = (a:editorCommon.IIdentifiedSingleEditOperation, b:editorCommon.IIdentifiedSingleEditOperation) => {
......@@ -1080,6 +1087,7 @@ export class Cursor extends EventEmitter {
shouldRevealVerticalInCenter: false,
shouldRevealHorizontal: true,
executeCommand: null,
isAutoWhitespaceCommand: false,
postOperationRunnable: null,
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: false,
......@@ -1100,6 +1108,7 @@ export class Cursor extends EventEmitter {
ctx.shouldPushStackElementAfter = ctx.shouldPushStackElementAfter || context.shouldPushStackElementAfter;
ctx.executeCommands[i] = context.executeCommand;
ctx.isAutoWhitespaceCommand[i] = context.isAutoWhitespaceCommand;
ctx.postOperationRunnables[i] = context.postOperationRunnable;
}
......
......@@ -29,6 +29,7 @@ export interface IOneCursorOperationContext {
shouldPushStackElementBefore: boolean;
shouldPushStackElementAfter: boolean;
executeCommand: editorCommon.ICommand;
isAutoWhitespaceCommand: boolean;
postOperationRunnable: IPostOperationRunnable;
requestScrollDeltaLines: number;
}
......@@ -145,7 +146,13 @@ export class OneCursor {
private _selEndMarker: string;
private _selDirection: editorCommon.SelectionDirection;
constructor(editorId: number, model: editorCommon.IModel, configuration: editorCommon.IConfiguration, modeConfiguration: IModeConfiguration, viewModelHelper:IViewModelHelper) {
constructor(
editorId: number,
model: editorCommon.IModel,
configuration: editorCommon.IConfiguration,
modeConfiguration: IModeConfiguration,
viewModelHelper:IViewModelHelper
) {
this.editorId = editorId;
this.model = model;
this.configuration = configuration;
......@@ -527,6 +534,9 @@ export class OneCursor {
public getVisibleColumnFromColumn(lineNumber:number, column:number): number {
return this.helper.visibleColumnFromColumn(this.model, lineNumber, column);
}
public getColumnFromVisibleColumn(lineNumber:number, column:number): number {
return this.helper.columnFromVisibleColumn(this.model, lineNumber, column);
}
public getViewVisibleColumnFromColumn(viewLineNumber:number, viewColumn:number): number {
return this.helper.visibleColumnFromColumn(this.viewModelHelper.viewModel, viewLineNumber, viewColumn);
}
......@@ -1185,6 +1195,7 @@ export class OneCursorOp {
var enterAction = r.enterAction;
var indentation = r.indentation;
ctx.isAutoWhitespaceCommand = true;
if (enterAction.indentAction === IndentAction.None) {
// Nothing special
this.actualType(cursor, '\n' + cursor.model.normalizeIndentation(indentation + enterAction.appendText), keepPosition, ctx, range);
......@@ -1500,6 +1511,8 @@ export class OneCursorOp {
if (selection.isEmpty()) {
ctx.isAutoWhitespaceCommand = true;
let typeText = '';
if (cursor.model.getLineMaxColumn(selection.startLineNumber) === 1) {
......@@ -1626,13 +1639,34 @@ export class OneCursorOp {
if (deleteSelection.isEmpty()) {
var position = cursor.getPosition();
var leftOfPosition = cursor.getLeftOfPosition(position.lineNumber, position.column);
deleteSelection = new Range(
leftOfPosition.lineNumber,
leftOfPosition.column,
position.lineNumber,
position.column
);
if (cursor.configuration.editor.useTabStops && position.column > 1) {
let lineContent = cursor.getLineContent(position.lineNumber);
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
let lastIndentationColumn = (
firstNonWhitespaceIndex === -1
? /* entire string is whitespace */lineContent.length + 1
: firstNonWhitespaceIndex + 1
);
if (position.column <= lastIndentationColumn) {
let fromVisibleColumn = cursor.getVisibleColumnFromColumn(position.lineNumber, position.column);
let toVisibleColumn = CursorMoveHelper.prevTabColumn(fromVisibleColumn, cursor.model.getOptions().tabSize);
let toColumn = cursor.getColumnFromVisibleColumn(position.lineNumber, toVisibleColumn);
deleteSelection = new Range(position.lineNumber, toColumn, position.lineNumber, position.column);
} else {
deleteSelection = new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column);
}
} else {
var leftOfPosition = cursor.getLeftOfPosition(position.lineNumber, position.column);
deleteSelection = new Range(
leftOfPosition.lineNumber,
leftOfPosition.column,
position.lineNumber,
position.column
);
}
}
if (deleteSelection.isEmpty()) {
......@@ -1648,21 +1682,11 @@ export class OneCursorOp {
return true;
}
private static _findLastNonWhitespaceChar(str:string, startIndex:number): number {
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
let ch = str.charAt(chIndex);
if (ch !== ' ' && ch !== '\t') {
return chIndex;
}
}
return -1;
}
private static deleteWordLeftWhitespace(cursor:OneCursor, ctx: IOneCursorOperationContext): boolean {
let position = cursor.getPosition();
let lineContent = cursor.getLineContent(position.lineNumber);
let startIndex = position.column - 2;
let lastNonWhitespace = this._findLastNonWhitespaceChar(lineContent, startIndex);
let lastNonWhitespace = strings.lastNonWhitespaceIndex(lineContent, startIndex);
if (lastNonWhitespace + 1 < startIndex) {
// bingo
ctx.executeCommand = new ReplaceCommand(new Range(position.lineNumber, lastNonWhitespace + 2, position.lineNumber, position.column), '');
......@@ -1982,6 +2006,10 @@ class CursorHelper {
return this.moveHelper.visibleColumnFromColumn(model, lineNumber, column);
}
public columnFromVisibleColumn(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return this.moveHelper.columnFromVisibleColumn(model, lineNumber, column);
}
private _createWord(lineContent: string, wordType:WordType, start: number, end: number): IFindWordResult {
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
return { start: start, end: end, wordType: wordType };
......
......@@ -513,6 +513,14 @@ export interface IEditorOptions {
* Defaults to true.
*/
indentGuides?: boolean;
/**
* Inserting and deleting whitespace follows tab stops.
*/
useTabStops?: boolean;
/**
* Remove trailing auto inserted whitespace.
*/
trimAutoWhitespace?: boolean;
/**
* The font family
*/
......@@ -624,6 +632,8 @@ export interface IInternalEditorOptions {
folding: boolean;
renderWhitespace: boolean;
indentGuides: boolean;
useTabStops: boolean;
trimAutoWhitespace: boolean;
// ---- Options that are computed
......@@ -702,6 +712,8 @@ export interface IConfigurationChangedEvent {
folding: boolean;
renderWhitespace: boolean;
indentGuides: boolean;
useTabStops: boolean;
trimAutoWhitespace: boolean;
// ---- Options that are computed
layoutInfo: boolean;
......@@ -1121,6 +1133,11 @@ export interface IIdentifiedSingleEditOperation {
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
*/
forceMoveMarkers: boolean;
/**
* This indicates that this operation is inserting automatic whitespace
* that can be removed on next model edit operation if `config.trimAutoWhitespace` is true.
*/
isAutoWhitespaceEdit?: boolean;
}
/**
......@@ -1168,23 +1185,27 @@ export interface ITextModelResolvedOptions {
tabSize: number;
insertSpaces: boolean;
defaultEOL: DefaultEndOfLine;
trimAutoWhitespace: boolean;
}
export interface ITextModelCreationOptions {
tabSize: number;
insertSpaces: boolean;
detectIndentation: boolean;
trimAutoWhitespace: boolean;
defaultEOL: DefaultEndOfLine;
}
export interface ITextModelUpdateOptions {
tabSize?: number;
insertSpaces?: boolean;
trimAutoWhitespace?: boolean;
}
export interface IModelOptionsChangedEvent {
tabSize: boolean;
insertSpaces: boolean;
trimAutoWhitespace: boolean;
}
/**
......
......@@ -11,6 +11,7 @@ import {EditStack} from 'vs/editor/common/model/editStack';
import {ILineEdit, ILineMarker, ModelLine} from 'vs/editor/common/model/modelLine';
import {DeferredEventsBuilder, TextModelWithDecorations} from 'vs/editor/common/model/textModelWithDecorations';
import {IMode} from 'vs/editor/common/modes';
import * as strings from 'vs/base/common/strings';
export interface IValidatedEditOperation {
sortIndex: number;
......@@ -19,6 +20,7 @@ export interface IValidatedEditOperation {
rangeLength: number;
lines: string[];
forceMoveMarkers: boolean;
isAutoWhitespaceEdit: boolean;
}
interface IIdentifiedLineEdit extends ILineEdit{
......@@ -37,6 +39,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
private _hasEditableRange:boolean;
private _editableRangeId:string;
private _trimAutoWhitespaceLines: number[];
constructor(allowedEventTypes:string[], rawText:editorCommon.IRawText, modeOrPromise:IMode|TPromise<IMode>) {
allowedEventTypes.push(editorCommon.EventType.ModelContentChanged);
allowedEventTypes.push(editorCommon.EventType.ModelContentChanged2);
......@@ -49,6 +53,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._hasEditableRange = false;
this._editableRangeId = null;
this._trimAutoWhitespaceLines = null;
}
public dispose(): void {
......@@ -63,6 +68,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._commandManager = new EditStack(this);
this._hasEditableRange = false;
this._editableRangeId = null;
this._trimAutoWhitespaceLines = null;
}
public pushStackElement(): void {
......@@ -71,6 +77,61 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public pushEditOperations(beforeCursorState:editorCommon.IEditorSelection[], editOperations:editorCommon.IIdentifiedSingleEditOperation[], cursorStateComputer:editorCommon.ICursorStateComputer): editorCommon.IEditorSelection[] {
return this.deferredEmit(() => {
if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) {
// Go through each saved line number and insert a trim whitespace edit
// if it is safe to do so (no conflicts with other edits).
let incomingEdits = editOperations.map((op) => {
return {
range: this.validateRange(op.range),
text: op.text
};
});
for (let i = 0, len = this._trimAutoWhitespaceLines.length; i < len; i++) {
let trimLineNumber = this._trimAutoWhitespaceLines[i];
let maxLineColumn = this.getLineMaxColumn(trimLineNumber);
let allowTrimLine = true;
for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) {
let editRange = incomingEdits[i].range;
let editText = incomingEdits[i].text;
if (trimLineNumber < editRange.startLineNumber || trimLineNumber > editRange.endLineNumber) {
// `trimLine` is completely outside this edit
continue;
}
// At this point:
// editRange.startLineNumber <= trimLine <= editRange.endLineNumber
if (
trimLineNumber === editRange.startLineNumber && editRange.startColumn === maxLineColumn
&& editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(0) === '\n'
) {
// This edit inserts a new line (and maybe other text) after `trimLine`
continue;
}
// Looks like we can't trim this line as it would interfere with an incoming edit
allowTrimLine = false;
break;
}
if (allowTrimLine) {
editOperations.push({
identifier: null,
range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn),
text: null,
forceMoveMarkers: false,
isAutoWhitespaceEdit: false
});
}
}
this._trimAutoWhitespaceLines = null;
}
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
});
}
......@@ -145,7 +206,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
range: entireEditRange,
rangeLength: this.getValueLengthInRange(entireEditRange),
lines: result.join('').split('\n'),
forceMoveMarkers: forceMoveMarkers
forceMoveMarkers: forceMoveMarkers,
isAutoWhitespaceEdit: false
};
}
......@@ -180,7 +242,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
range: validatedRange,
rangeLength: this.getValueLengthInRange(validatedRange),
lines: op.text ? op.text.split(/\r\n|\r|\n/) : null,
forceMoveMarkers: op.forceMoveMarkers
forceMoveMarkers: op.forceMoveMarkers,
isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false
};
}
......@@ -212,17 +275,61 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
// Delta encode operations
let reverseRanges = EditableTextModel._getInverseEditRanges(operations);
let reverseOperations: editorCommon.IIdentifiedSingleEditOperation[] = [];
let newTrimAutoWhitespaceCandidates: { lineNumber:number,oldContent:string }[] = [];
for (let i = 0; i < operations.length; i++) {
let op = operations[i];
let reverseRange = reverseRanges[i];
reverseOperations[i] = {
identifier: operations[i].identifier,
range: reverseRanges[i],
text: this.getValueInRange(operations[i].range),
forceMoveMarkers: operations[i].forceMoveMarkers
identifier: op.identifier,
range: reverseRange,
text: this.getValueInRange(op.range),
forceMoveMarkers: op.forceMoveMarkers
};
if (this._options.trimAutoWhitespace && 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 });
}
}
}
this._applyEdits(operations);
this._trimAutoWhitespaceLines = null;
if (this._options.trimAutoWhitespace && newTrimAutoWhitespaceCandidates.length > 0) {
// sort line numbers auto whitespace removal candidates for next edit descending
newTrimAutoWhitespaceCandidates.sort((a,b) => b.lineNumber - a.lineNumber);
this._trimAutoWhitespaceLines = [];
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;
}
this._trimAutoWhitespaceLines.push(lineNumber);
}
}
return reverseOperations;
}
......@@ -311,7 +418,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
lineEditsQueue.reverse();
// `lineEditsQueue` now contains edits from smaller (line number,column) to larger (line number,column)
let currentLineNumber = lineEditsQueue[0].lineNumber, currentLineNumberStart = 0;
let currentLineNumber = lineEditsQueue[0].lineNumber;
let currentLineNumberStart = 0;
for (let i = 1, len = lineEditsQueue.length; i < len; i++) {
let lineNumber = lineEditsQueue[i].lineNumber;
......
......@@ -222,7 +222,8 @@ export class MirrorModelEmbedded extends AbstractMirrorModel implements editorCo
tabSize: actualModelOptions.tabSize,
insertSpaces: actualModelOptions.insertSpaces,
detectIndentation: false,
defaultEOL: actualModelOptions.defaultEOL
defaultEOL: actualModelOptions.defaultEOL,
trimAutoWhitespace: actualModelOptions.trimAutoWhitespace
});
}
......
......@@ -11,7 +11,7 @@ import {Range} from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import {ModelLine} from 'vs/editor/common/model/modelLine';
import {guessIndentation} from 'vs/editor/common/model/indentationGuesser';
import {DEFAULT_INDENTATION} from 'vs/editor/common/config/defaultConfig';
import {DEFAULT_INDENTATION, DefaultConfig} from 'vs/editor/common/config/defaultConfig';
var LIMIT_FIND_COUNT = 999;
......@@ -21,6 +21,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
tabSize: DEFAULT_INDENTATION.tabSize,
insertSpaces: DEFAULT_INDENTATION.insertSpaces,
detectIndentation: false,
trimAutoWhitespace: DefaultConfig.editor.trimAutoWhitespace,
defaultEOL: editorCommon.DefaultEndOfLine.LF
};
......@@ -56,7 +57,8 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
let somethingChanged = false;
let changed:editorCommon.IModelOptionsChangedEvent = {
tabSize: false,
insertSpaces: false
insertSpaces: false,
trimAutoWhitespace: false
};
if (typeof newOpts.insertSpaces !== 'undefined') {
......@@ -73,6 +75,13 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
this._options.tabSize = newOpts.tabSize;
}
}
if (typeof newOpts.trimAutoWhitespace !== 'undefined') {
if (this._options.trimAutoWhitespace !== newOpts.trimAutoWhitespace) {
somethingChanged = true;
changed.trimAutoWhitespace = true;
this._options.trimAutoWhitespace = newOpts.trimAutoWhitespace;
}
}
if (somethingChanged) {
this.emit(editorCommon.EventType.ModelOptionsChanged, changed);
......@@ -244,6 +253,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
rawText = TextModel.toRawText(value, {
tabSize: this._options.tabSize,
insertSpaces: this._options.insertSpaces,
trimAutoWhitespace: this._options.trimAutoWhitespace,
detectIndentation: false,
defaultEOL: this._options.defaultEOL
});
......@@ -624,12 +634,14 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
resolvedOpts = {
tabSize: guessedIndentation.tabSize,
insertSpaces: guessedIndentation.insertSpaces,
trimAutoWhitespace: opts.trimAutoWhitespace,
defaultEOL: opts.defaultEOL
};
} else {
resolvedOpts = {
tabSize: opts.tabSize,
insertSpaces: opts.insertSpaces,
trimAutoWhitespace: opts.trimAutoWhitespace,
defaultEOL: opts.defaultEOL
};
}
......@@ -832,6 +844,7 @@ export class RawText {
return TextModel.toRawText(rawText, {
tabSize: opts.tabSize,
insertSpaces: opts.insertSpaces,
trimAutoWhitespace: opts.trimAutoWhitespace,
detectIndentation: false,
defaultEOL: opts.defaultEOL
});
......
......@@ -443,7 +443,8 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
tabSize: this._options.tabSize,
insertSpaces: this._options.insertSpaces,
detectIndentation: false,
defaultEOL: this._options.defaultEOL
defaultEOL: this._options.defaultEOL,
trimAutoWhitespace: this._options.trimAutoWhitespace
});
}
this.setValueFromRawText(rawText, newModeOrPromise);
......
......@@ -26,7 +26,7 @@ import {IModelService} from 'vs/editor/common/services/modelService';
import {IResourceService} from 'vs/editor/common/services/resourceService';
import * as platform from 'vs/base/common/platform';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {DEFAULT_INDENTATION} from 'vs/editor/common/config/defaultConfig';
import {DEFAULT_INDENTATION, DefaultConfig} from 'vs/editor/common/config/defaultConfig';
import {IMessageService} from 'vs/platform/message/common/message';
export interface IRawModelData {
......@@ -180,6 +180,7 @@ interface IRawConfig {
tabSize?: any;
insertSpaces?: any;
detectIndentation?: any;
trimAutoWhitespace?: any;
};
}
......@@ -219,7 +220,8 @@ export class ModelServiceImpl implements IModelService {
tabSize: DEFAULT_INDENTATION.tabSize,
insertSpaces: DEFAULT_INDENTATION.insertSpaces,
detectIndentation: DEFAULT_INDENTATION.detectIndentation,
defaultEOL: (platform.isLinux || platform.isMacintosh) ? editorCommon.DefaultEndOfLine.LF : editorCommon.DefaultEndOfLine.CRLF
defaultEOL: (platform.isLinux || platform.isMacintosh) ? editorCommon.DefaultEndOfLine.LF : editorCommon.DefaultEndOfLine.CRLF,
trimAutoWhitespace: DefaultConfig.editor.trimAutoWhitespace
};
this._threadService = threadService;
this._markerService = markerService;
......@@ -240,7 +242,6 @@ export class ModelServiceImpl implements IModelService {
}
let readConfig = (config: IRawConfig) => {
const eol = config.files && config.files.eol;
let shouldShowMigrationMessage = false;
......@@ -260,12 +261,18 @@ export class ModelServiceImpl implements IModelService {
}
let newDefaultEOL = this._modelCreationOptions.defaultEOL;
const eol = config.files && config.files.eol;
if (eol === '\r\n') {
newDefaultEOL = editorCommon.DefaultEndOfLine.CRLF;
} else if (eol === '\n') {
newDefaultEOL = editorCommon.DefaultEndOfLine.LF;
}
let trimAutoWhitespace = this._modelCreationOptions.trimAutoWhitespace;
if (config.editor && typeof config.editor.trimAutoWhitespace !== 'undefined') {
trimAutoWhitespace = (config.editor.trimAutoWhitespace === 'false' ? false : Boolean(config.editor.trimAutoWhitespace));
}
let detectIndentation = DEFAULT_INDENTATION.detectIndentation;
if (config.editor && typeof config.editor.detectIndentation !== 'undefined') {
detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation));
......@@ -275,7 +282,8 @@ export class ModelServiceImpl implements IModelService {
tabSize: tabSize,
insertSpaces: insertSpaces,
detectIndentation: detectIndentation,
defaultEOL: newDefaultEOL
defaultEOL: newDefaultEOL,
trimAutoWhitespace: trimAutoWhitespace
});
......@@ -300,6 +308,7 @@ export class ModelServiceImpl implements IModelService {
(this._modelCreationOptions.detectIndentation === newOpts.detectIndentation)
&& (this._modelCreationOptions.insertSpaces === newOpts.insertSpaces)
&& (this._modelCreationOptions.tabSize === newOpts.tabSize)
&& (this._modelCreationOptions.trimAutoWhitespace === newOpts.trimAutoWhitespace)
) {
// Same indent opts, no need to touch created models
this._modelCreationOptions = newOpts;
......@@ -315,10 +324,14 @@ export class ModelServiceImpl implements IModelService {
if (this._modelCreationOptions.detectIndentation) {
modelData.model.detectIndentation(this._modelCreationOptions.insertSpaces, this._modelCreationOptions.tabSize);
modelData.model.updateOptions({
trimAutoWhitespace: this._modelCreationOptions.trimAutoWhitespace
});
} else {
modelData.model.updateOptions({
insertSpaces: this._modelCreationOptions.insertSpaces,
tabSize: this._modelCreationOptions.tabSize
tabSize: this._modelCreationOptions.tabSize,
trimAutoWhitespace: this._modelCreationOptions.trimAutoWhitespace
});
}
}
......
......@@ -1111,7 +1111,8 @@ suite('Editor Controller - Regression tests', () => {
defaultEOL: DefaultEndOfLine.LF,
detectIndentation: false,
insertSpaces: false,
tabSize: 4
tabSize: 4,
trimAutoWhitespace: false
}
}, (model, cursor) => {
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
......@@ -1198,7 +1199,8 @@ suite('Editor Controller - Regression tests', () => {
defaultEOL: DefaultEndOfLine.LF,
detectIndentation: false,
insertSpaces: false,
tabSize: 4
tabSize: 4,
trimAutoWhitespace: true
},
mode: new OnEnterMode(IndentAction.Indent),
}, (model, cursor) => {
......@@ -1219,7 +1221,8 @@ suite('Editor Controller - Regression tests', () => {
defaultEOL: DefaultEndOfLine.LF,
detectIndentation: false,
insertSpaces: false,
tabSize: 4
tabSize: 4,
trimAutoWhitespace: true
},
}, (model, cursor) => {
moveTo(cursor, 1, 2, false);
......@@ -1240,7 +1243,7 @@ suite('Editor Controller - Regression tests', () => {
' function baz() {'
],
mode: new OnEnterMode(IndentAction.IndentOutdent),
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 1, 6, false);
cursorEqual(cursor, 1, 6, 1, 6);
......@@ -1256,7 +1259,7 @@ suite('Editor Controller - Regression tests', () => {
text: [
' '
],
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 1, 7, false);
cursorEqual(cursor, 1, 7, 1, 7);
......@@ -1282,7 +1285,8 @@ suite('Editor Controller - Regression tests', () => {
defaultEOL: DefaultEndOfLine.LF,
detectIndentation: false,
insertSpaces: false,
tabSize: 4
tabSize: 4,
trimAutoWhitespace: true
},
}, (model, cursor) => {
moveTo(cursor, 7, 1, false);
......@@ -1342,7 +1346,7 @@ suite('Editor Controller - Regression tests', () => {
'hello'
],
mode: new SurroundingMode(),
modelOpts: { tabSize: 4, insertSpaces: true, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { tabSize: 4, insertSpaces: true, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 1, 3, false);
moveTo(cursor, 1, 5, true);
......@@ -1364,7 +1368,7 @@ suite('Editor Controller - Regression tests', () => {
'};'
],
mode: new SurroundingMode(),
modelOpts: { tabSize: 4, insertSpaces: true, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { tabSize: 4, insertSpaces: true, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 3, 2, false);
moveTo(cursor, 1, 14, true);
......@@ -1413,7 +1417,7 @@ suite('Editor Controller - Regression tests', () => {
'just some text',
],
mode: null,
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 1, 1, false);
moveTo(cursor, 3, 4, true);
......@@ -1456,7 +1460,7 @@ suite('Editor Controller - Regression tests', () => {
'\t};',
'}',
],
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 3, 2, false);
cursorCommand(cursor, H.Tab);
......@@ -1871,7 +1875,7 @@ suite('Editor Controller - Cursor Configuration', () => {
'',
'1'
],
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
cursorCommand(cursor, H.MoveTo, { position: new Position(1, 21) }, null, 'keyboard');
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
......@@ -1889,7 +1893,7 @@ suite('Editor Controller - Cursor Configuration', () => {
'',
'1'
],
modelOpts: { insertSpaces: true, tabSize: 13, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 13, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
// Tab on column 1
cursorCommand(cursor, H.MoveTo, { position: new Position(2, 1) }, null, 'keyboard');
......@@ -1953,7 +1957,7 @@ suite('Editor Controller - Cursor Configuration', () => {
'\thello'
],
mode: new OnEnterMode(IndentAction.Indent),
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 1, 7, false);
cursorEqual(cursor, 1, 7, 1, 7);
......@@ -1969,7 +1973,7 @@ suite('Editor Controller - Cursor Configuration', () => {
'\thello'
],
mode: new OnEnterMode(IndentAction.None),
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 1, 7, false);
cursorEqual(cursor, 1, 7, 1, 7);
......@@ -1985,7 +1989,7 @@ suite('Editor Controller - Cursor Configuration', () => {
'\thell()'
],
mode: new OnEnterMode(IndentAction.IndentOutdent),
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF }
modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true }
}, (model, cursor) => {
moveTo(cursor, 1, 7, false);
cursorEqual(cursor, 1, 7, 1, 7);
......@@ -2078,6 +2082,324 @@ suite('Editor Controller - Cursor Configuration', () => {
assert.equal(model.getLineContent(4), '');
});
});
test('removeAutoWhitespace off', () => {
usingCursor({
text: [
' some line abc '
],
modelOpts: {
insertSpaces: true,
tabSize: 4,
detectIndentation: false,
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: false
}
}, (model, cursor) => {
// Move cursor to the end, verify that we do not trim whitespaces if line has values
moveTo(cursor, 1, model.getLineContent(1).length + 1);
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' some line abc ');
assert.equal(model.getLineContent(2), ' ');
// Try to enter again, we should trimmed previous line
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' some line abc ');
assert.equal(model.getLineContent(2), ' ');
assert.equal(model.getLineContent(3), ' ');
});
});
test('removeAutoWhitespace on: removes only whitespace the cursor added 1', () => {
usingCursor({
text: [
' '
],
modelOpts: {
insertSpaces: true,
tabSize: 4,
detectIndentation: false,
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: true
}
}, (model, cursor) => {
moveTo(cursor, 1, model.getLineContent(1).length + 1);
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' ');
assert.equal(model.getLineContent(2), ' ');
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' ');
assert.equal(model.getLineContent(2), '');
assert.equal(model.getLineContent(3), ' ');
});
});
test('removeAutoWhitespace on: removes only whitespace the cursor added 2', () => {
usingCursor({
text: [
' if (a) {',
' ',
'',
'',
' }'
],
modelOpts: {
insertSpaces: true,
tabSize: 4,
detectIndentation: false,
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: true
}
}, (model, cursor) => {
moveTo(cursor, 3, 1);
cursorCommand(cursor, H.Tab, null, null, 'keyboard');
assert.equal(model.getLineContent(1), ' if (a) {');
assert.equal(model.getLineContent(2), ' ');
assert.equal(model.getLineContent(3), ' ');
assert.equal(model.getLineContent(4), '');
assert.equal(model.getLineContent(5), ' }');
moveTo(cursor, 4, 1);
cursorCommand(cursor, H.Tab, null, null, 'keyboard');
assert.equal(model.getLineContent(1), ' if (a) {');
assert.equal(model.getLineContent(2), ' ');
assert.equal(model.getLineContent(3), '');
assert.equal(model.getLineContent(4), ' ');
assert.equal(model.getLineContent(5), ' }');
moveTo(cursor, 5, model.getLineMaxColumn(5));
cursorCommand(cursor, H.Type, { text: 'something' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' if (a) {');
assert.equal(model.getLineContent(2), ' ');
assert.equal(model.getLineContent(3), '');
assert.equal(model.getLineContent(4), '');
assert.equal(model.getLineContent(5), ' }something');
});
});
test('removeAutoWhitespace on: test 1', () => {
usingCursor({
text: [
' some line abc '
],
modelOpts: {
insertSpaces: true,
tabSize: 4,
detectIndentation: false,
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: true
}
}, (model, cursor) => {
// Move cursor to the end, verify that we do not trim whitespaces if line has values
moveTo(cursor, 1, model.getLineContent(1).length + 1);
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' some line abc ');
assert.equal(model.getLineContent(2), ' ');
// Try to enter again, we should trimmed previous line
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' some line abc ');
assert.equal(model.getLineContent(2), '');
assert.equal(model.getLineContent(3), ' ');
// More whitespaces
cursorCommand(cursor, H.Tab, null, null, 'keyboard');
assert.equal(model.getLineContent(1), ' some line abc ');
assert.equal(model.getLineContent(2), '');
assert.equal(model.getLineContent(3), ' ');
// Enter and verify that trimmed again
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' some line abc ');
assert.equal(model.getLineContent(2), '');
assert.equal(model.getLineContent(3), '');
assert.equal(model.getLineContent(4), ' ');
// Trimmed if we will keep only text
moveTo(cursor, 1, 5);
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' ');
assert.equal(model.getLineContent(2), ' some line abc ');
assert.equal(model.getLineContent(3), '');
assert.equal(model.getLineContent(4), '');
assert.equal(model.getLineContent(5), '');
// Trimmed if we will keep only text by selection
moveTo(cursor, 2, 5);
moveTo(cursor, 3, 1, true);
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getLineContent(1), ' ');
assert.equal(model.getLineContent(2), ' ');
assert.equal(model.getLineContent(3), '');
assert.equal(model.getLineContent(4), '');
assert.equal(model.getLineContent(5), '');
});
});
test('UseTabStops is off', () => {
usingCursor({
text: [
' x',
' a ',
' '
],
modelOpts: {
insertSpaces: true,
tabSize: 4,
detectIndentation: false,
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: true
}
}, (model, cursor) => {
cursor.configuration.editor.useTabStops = false;
// DeleteLeft removes just one whitespace
moveTo(cursor, 2, 9);
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(2), ' a ');
});
});
test('Backspace removes whitespaces with tab size', () => {
usingCursor({
text: [
' \t \t x',
' a ',
' '
],
modelOpts: {
insertSpaces: true,
tabSize: 4,
detectIndentation: false,
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: true
}
}, (model, cursor) => {
cursor.configuration.editor.useTabStops = true;
// DeleteLeft does not remove tab size, because some text exists before
moveTo(cursor, 2, model.getLineContent(2).length + 1);
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(2), ' a ');
// DeleteLeft removes tab size = 4
moveTo(cursor, 2, 9);
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(2), ' a ');
// DeleteLeft removes tab size = 4
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(2), 'a ');
// Undo DeleteLeft - get us back to original indentation
cursorCommand(cursor, H.Undo, {});
assert.equal(model.getLineContent(2), ' a ');
// Nothing is broken when cursor is in (1,1)
moveTo(cursor, 1, 1);
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(1), ' \t \t x');
// DeleteLeft stops at tab stops even in mixed whitespace case
moveTo(cursor, 1, 10);
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(1), ' \t \t x');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(1), ' \t \tx');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(1), ' \tx');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(1), 'x');
// DeleteLeft on last line
moveTo(cursor, 3, model.getLineContent(3).length + 1);
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(3), '');
// DeleteLeft with removing new line symbol
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getValue(EndOfLinePreference.LF), 'x\n a ');
// In case of selection DeleteLeft only deletes selected text
moveTo(cursor, 2, 3);
moveTo(cursor, 2, 4, true);
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getLineContent(2), ' a ');
});
});
test('PR #5423: Auto indent + undo + redo is funky', () => {
usingCursor({
text: [
''
],
modelOpts: {
defaultEOL: DefaultEndOfLine.LF,
detectIndentation: false,
insertSpaces: false,
tabSize: 4,
trimAutoWhitespace: true
}
}, (model, cursor) => {
cursorCommand(cursor, H.Type, { text: '\n' }, null, 'keyboard');
assert.equal(model.getValue(EndOfLinePreference.LF), '\n', 'assert1');
cursorCommand(cursor, H.Tab, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t', 'assert2');
cursorCommand(cursor, H.Type, { text: 'y'}, null, 'keyboard');
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty', 'assert2');
cursorCommand(cursor, H.Type, { text: '\n'}, null, 'keyboard');
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\t', 'assert3');
cursorCommand(cursor, H.Type, { text: 'x' });
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert4');
cursorCommand(cursor, H.CursorLeft, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert5');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert6');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\tyx', 'assert7');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\tx', 'assert8');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert9');
cursorCommand(cursor, H.DeleteLeft, {});
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert10');
cursorCommand(cursor, H.Undo, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert11');
cursorCommand(cursor, H.Undo, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert12');
cursorCommand(cursor, H.Undo, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert13');
cursorCommand(cursor, H.Redo, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert14');
cursorCommand(cursor, H.Redo, {});
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert15');
cursorCommand(cursor, H.Redo, {});
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert16');
});
});
});
interface ICursorOpts {
......
......@@ -21,7 +21,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeLength: rangeLength,
lines: text,
forceMoveMarkers: false
forceMoveMarkers: false,
isAutoWhitespaceEdit: false
};
}
......@@ -270,7 +271,8 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeLength: rangeLength,
lines: text,
forceMoveMarkers: false
forceMoveMarkers: false,
isAutoWhitespaceEdit: false
};
}
......
......@@ -106,6 +106,7 @@ suite('Editor Model - MirrorModel', () => {
options: {
tabSize: 4,
insertSpaces: true,
trimAutoWhitespace: true,
defaultEOL: editorCommon.DefaultEndOfLine.LF
}
})]));
......@@ -336,6 +337,7 @@ suite('Editor Model - MirrorModel Eventing', () => {
options: {
tabSize: 4,
insertSpaces: true,
trimAutoWhitespace: true,
defaultEOL: editorCommon.DefaultEndOfLine.LF
}
})]));
......
......@@ -15,7 +15,8 @@ function testGuessIndentation(defaultInsertSpaces:boolean, defaultTabSize:number
tabSize: defaultTabSize,
insertSpaces: defaultInsertSpaces,
detectIndentation: true,
defaultEOL: DefaultEndOfLine.LF
defaultEOL: DefaultEndOfLine.LF,
trimAutoWhitespace: true
}));
var r = m.getOptions();
m.dispose();
......@@ -473,6 +474,7 @@ suite('Editor Model - TextModel', () => {
options: {
tabSize: 4,
insertSpaces: false,
trimAutoWhitespace: true,
defaultEOL: DefaultEndOfLine.LF
}
});
......@@ -511,6 +513,7 @@ suite('Editor Model - TextModel', () => {
options: {
tabSize: 4,
insertSpaces: true,
trimAutoWhitespace: true,
defaultEOL: DefaultEndOfLine.LF
}
});
......
......@@ -89,6 +89,7 @@ suite('ExtHostLanguageFeatureCommands', function() {
options: {
tabSize: 4,
insertSpaces: true,
trimAutoWhitespace: true,
defaultEOL: EditorCommon.DefaultEndOfLine.LF
}
},
......
......@@ -77,6 +77,7 @@ suite('ExtHostLanguageFeatures', function() {
options: {
tabSize: 4,
insertSpaces: true,
trimAutoWhitespace: true,
defaultEOL: EditorCommon.DefaultEndOfLine.LF
}
},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册