提交 d119c924 编写于 作者: T Tim Hutt 提交者: Tim Hutt

Add atomic tabs option

This treats tabs faked using spaces as if they were real tabs meaning that you can't select the middle of them.
上级 78db6d01
......@@ -14,6 +14,7 @@ import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import * as platform from 'vs/base/common/platform';
import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations';
export interface IMouseDispatchData {
position: Position;
......@@ -128,24 +129,35 @@ export class ViewController {
}
public dispatchMouse(data: IMouseDispatchData): void {
let position = data.position;
if (this.viewModel.getTextModelOptions().atomicSoftTabs) {
const minColumn = this.viewModel.getLineMinColumn(position.lineNumber);
const lineContent = this.viewModel.getLineContent(position.lineNumber);
const { tabSize } = this.viewModel.getTextModelOptions();
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - minColumn, tabSize, Direction.Nearest);
if (newPosition !== -1) {
position = new Position(position.lineNumber, newPosition + minColumn);
}
}
const options = this.configuration.options;
const selectionClipboardIsOn = (platform.isLinux && options.get(EditorOption.selectionClipboard));
const columnSelection = options.get(EditorOption.columnSelection);
if (data.middleButton && !selectionClipboardIsOn) {
this._columnSelect(data.position, data.mouseColumn, data.inSelectionMode);
this._columnSelect(position, data.mouseColumn, data.inSelectionMode);
} 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) {
this._lastCursorLineSelect(data.position);
this._lastCursorLineSelect(position);
} else {
this._createCursor(data.position, true);
this._createCursor(position, true);
}
} else {
if (data.inSelectionMode) {
this._lineSelectDrag(data.position);
this._lineSelectDrag(position);
} else {
this._lineSelect(data.position);
this._lineSelect(position);
}
}
} else if (data.mouseDownCount >= 4) {
......@@ -153,54 +165,54 @@ export class ViewController {
} else if (data.mouseDownCount === 3) {
if (this._hasMulticursorModifier(data)) {
if (data.inSelectionMode) {
this._lastCursorLineSelectDrag(data.position);
this._lastCursorLineSelectDrag(position);
} else {
this._lastCursorLineSelect(data.position);
this._lastCursorLineSelect(position);
}
} else {
if (data.inSelectionMode) {
this._lineSelectDrag(data.position);
this._lineSelectDrag(position);
} else {
this._lineSelect(data.position);
this._lineSelect(position);
}
}
} else if (data.mouseDownCount === 2) {
if (this._hasMulticursorModifier(data)) {
this._lastCursorWordSelect(data.position);
this._lastCursorWordSelect(position);
} else {
if (data.inSelectionMode) {
this._wordSelectDrag(data.position);
this._wordSelectDrag(position);
} else {
this._wordSelect(data.position);
this._wordSelect(position);
}
}
} else {
if (this._hasMulticursorModifier(data)) {
if (!this._hasNonMulticursorModifier(data)) {
if (data.shiftKey) {
this._columnSelect(data.position, data.mouseColumn, true);
this._columnSelect(position, data.mouseColumn, true);
} else {
// Do multi-cursor operations only when purely alt is pressed
if (data.inSelectionMode) {
this._lastCursorMoveToSelect(data.position);
this._lastCursorMoveToSelect(position);
} else {
this._createCursor(data.position, false);
this._createCursor(position, false);
}
}
}
} else {
if (data.inSelectionMode) {
if (data.altKey) {
this._columnSelect(data.position, data.mouseColumn, true);
this._columnSelect(position, data.mouseColumn, true);
} else {
if (columnSelection) {
this._columnSelect(data.position, data.mouseColumn, true);
this._columnSelect(position, data.mouseColumn, true);
} else {
this._moveToSelect(data.position);
this._moveToSelect(position);
}
}
} else {
this.moveTo(data.position);
this.moveTo(position);
}
}
}
......
......@@ -482,6 +482,11 @@ const editorConfiguration: IConfigurationNode = {
default: EDITOR_MODEL_DEFAULTS.insertSpaces,
markdownDescription: nls.localize('insertSpaces', "Insert spaces when pressing `Tab`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.")
},
'editor.atomicSoftTabs': {
type: 'boolean',
default: EDITOR_MODEL_DEFAULTS.atomicSoftTabs,
markdownDescription: nls.localize('atomicSoftTabs', "Emulate selection behaviour of hard tabs when using soft tabs (spaces) for indentation. This means selection will snap to indentation boundaries.")
},
'editor.detectIndentation': {
type: 'boolean',
default: EDITOR_MODEL_DEFAULTS.detectIndentation,
......
......@@ -3542,6 +3542,7 @@ export const EDITOR_MODEL_DEFAULTS = {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
atomicSoftTabs: false,
detectIndentation: true,
trimAutoWhitespace: true,
largeFileOptimizations: true
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
export enum Direction {
Left,
Right,
Nearest,
}
export class AtomicTabMoveOperations {
// Get the visible column at the position. If we get to a non-whitespace character first
// or past the end of string then return -1. Note `position` and the return
// value are 0-based.
public static whitespaceVisibleColumn(lineContent: string, position: number, tabSize: number) {
let visibleColumn = 0;
for (let i = 0; i < lineContent.length; ++i) {
if (i === position) {
return visibleColumn;
}
const chCode = lineContent.charCodeAt(i);
switch (chCode) {
case CharCode.Space:
visibleColumn += 1;
break;
case CharCode.Tab:
// Skip to the next multiple of tabSize.
visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
break;
default:
return -1;
}
}
if (position === lineContent.length) {
return visibleColumn;
}
return -1;
}
// Return the position that should result from a move left, right or to the
// nearest tab, if atomic tabs are enabled. Left and right are used for the
// arrow key movements, nearest is used for mouse selection. It returns
// -1 if atomic tabs are not relevant and you should fall back to normal
// behaviour.
//
// Note, `position` and the return value are 0-based.
public static atomicPosition(lineContent: string, position: number, tabSize: number, direction: Direction): number {
// Get the 0-based visible column corresponding to the position, or return
// -1 if it is not in the initial whitespace.
let visibleColumn = AtomicTabMoveOperations.whitespaceVisibleColumn(lineContent, position, tabSize);
if (visibleColumn === -1) {
return -1;
}
// Is the output left or right of the current position. The case for nearest
// where it is the same as the current position is handled in the switch.
let left: boolean;
switch (direction) {
case Direction.Left:
left = true;
break;
case Direction.Right:
left = false;
break;
case Direction.Nearest:
// The code below assumes the output position is either left or right
// of the input position. If it is the same, return immediately.
if (visibleColumn % tabSize === 0) {
return position;
}
// Go to the nearest indentation.
left = visibleColumn % tabSize <= (tabSize / 2);
break;
}
// The code below won't work if visibleColumn is zero and left is true because
// it takes the mod of a negative number, which behaves oddly. In that case
// we already know what to return.
if (left && visibleColumn === 0) {
return -1;
}
const tmp = visibleColumn + (left ? -1 : tabSize);
const targetVisibleColumn = tmp - tmp % tabSize;
// Find the target visible column. If going right we can just continue from
// where whitespaceVisibleColumn got to. If going left it's easiest to start
// from the beginning because the width of tab characters depend on the
// characters to their left. E.g. ' \t' is one tabSize, but so is '\t'.
if (left) {
visibleColumn = 0;
}
for (let i = (left ? 0 : position); i < lineContent.length; ++i) {
if (visibleColumn === targetVisibleColumn) {
// This is the position we want to get to, but we have one more case
// to handle if going left.
if (left) {
// If the direction is left, we need to keep scanning right to ensure
// that targetVisibleColumn + tabSize is before non-whitespace.
// This is so that when we press left at the end of a partial
// indentation it only goes one character. For example ' foo' with
// tabSize 4, should jump from position 6 to position 5, not 4.
for (let k = i; k < lineContent.length; ++k) {
if (visibleColumn === targetVisibleColumn + tabSize) {
// It is a full indentation.
return i;
}
const chCode = lineContent.charCodeAt(k);
switch (chCode) {
case CharCode.Space:
visibleColumn += 1;
break;
case CharCode.Tab:
visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
break;
default:
return -1;
}
}
if (visibleColumn === targetVisibleColumn + tabSize) {
return i;
}
// It must have been a partial indentation.
return -1;
} else {
// If going right then we must have been in a complete indentation.
return i;
}
}
const chCode = lineContent.charCodeAt(i);
switch (chCode) {
case CharCode.Space:
visibleColumn += 1;
break;
case CharCode.Tab:
visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
break;
default:
return -1;
}
}
// This condition handles when the target column is at the end of the line.
if (visibleColumn === targetVisibleColumn) {
return lineContent.length;
}
return -1;
}
}
......@@ -62,6 +62,7 @@ export class CursorConfiguration {
public readonly tabSize: number;
public readonly indentSize: number;
public readonly insertSpaces: boolean;
public readonly atomicSoftTabs: boolean;
public readonly pageSize: number;
public readonly lineHeight: number;
public readonly useTabStops: boolean;
......@@ -114,6 +115,7 @@ export class CursorConfiguration {
this.tabSize = modelOptions.tabSize;
this.indentSize = modelOptions.indentSize;
this.insertSpaces = modelOptions.insertSpaces;
this.atomicSoftTabs = modelOptions.atomicSoftTabs;
this.lineHeight = options.get(EditorOption.lineHeight);
this.pageSize = Math.max(1, Math.floor(layoutInfo.height / this.lineHeight) - 2);
this.useTabStops = options.get(EditorOption.useTabStops);
......@@ -557,14 +559,14 @@ export class CursorColumns {
}
/**
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
* ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
*/
public static prevRenderTabStop(column: number, tabSize: number): number {
return column - 1 - (column - 1) % tabSize;
}
/**
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
* ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
*/
public static prevIndentTabStop(column: number, indentSize: number): number {
return column - 1 - (column - 1) % indentSize;
......
......@@ -8,6 +8,8 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import * as strings from 'vs/base/common/strings';
import { Constants } from 'vs/base/common/uint';
import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations';
export class CursorPosition {
_cursorPositionBrand: void;
......@@ -29,14 +31,34 @@ export class MoveOperations {
if (column > model.getLineMinColumn(lineNumber)) {
column = column - strings.prevCharLength(model.getLineContent(lineNumber), column - 1);
} else if (lineNumber > 1) {
lineNumber = lineNumber - 1;
lineNumber -= 1;
column = model.getLineMaxColumn(lineNumber);
}
return new Position(lineNumber, column);
}
public static leftPositionatomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number): Position {
const minColumn = model.getLineMinColumn(lineNumber);
if (column > minColumn) {
const lineContent = model.getLineContent(lineNumber);
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Left);
if (newPosition !== -1) {
column = minColumn + newPosition;
} else {
column -= strings.prevCharLength(model.getLineContent(lineNumber), column - 1);
}
} else if (lineNumber > 1) {
lineNumber -= 1;
column = model.getLineMaxColumn(lineNumber);
}
return new Position(lineNumber, column);
}
public static left(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {
const pos = MoveOperations.leftPosition(model, lineNumber, column);
const pos = config.atomicSoftTabs
? MoveOperations.leftPositionatomicSoftTabs(model, lineNumber, column, config.tabSize)
: MoveOperations.leftPosition(model, lineNumber, column);
return new CursorPosition(pos.lineNumber, pos.column, 0);
}
......@@ -61,14 +83,34 @@ export class MoveOperations {
if (column < model.getLineMaxColumn(lineNumber)) {
column = column + strings.nextCharLength(model.getLineContent(lineNumber), column - 1);
} else if (lineNumber < model.getLineCount()) {
lineNumber = lineNumber + 1;
lineNumber += 1;
column = model.getLineMinColumn(lineNumber);
}
return new Position(lineNumber, column);
}
public static rightPositionatomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number, indentSize: number): Position {
const minColumn = model.getLineMinColumn(lineNumber);
if (column < model.getLineMaxColumn(lineNumber)) {
const lineContent = model.getLineContent(lineNumber);
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Right);
if (newPosition !== -1) {
column = minColumn + newPosition;
} else {
column += strings.nextCharLength(model.getLineContent(lineNumber), column - 1);
}
} else if (lineNumber < model.getLineCount()) {
lineNumber += 1;
column = minColumn;
}
return new Position(lineNumber, column);
}
public static right(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {
const pos = MoveOperations.rightPosition(model, lineNumber, column);
const pos = config.atomicSoftTabs
? MoveOperations.rightPositionatomicSoftTabs(model, lineNumber, column, config.tabSize, config.indentSize)
: MoveOperations.rightPosition(model, lineNumber, column);
return new CursorPosition(pos.lineNumber, pos.column, 0);
}
......
......@@ -400,6 +400,7 @@ export class TextModelResolvedOptions {
readonly tabSize: number;
readonly indentSize: number;
readonly insertSpaces: boolean;
readonly atomicSoftTabs: boolean;
readonly defaultEOL: DefaultEndOfLine;
readonly trimAutoWhitespace: boolean;
......@@ -410,12 +411,14 @@ export class TextModelResolvedOptions {
tabSize: number;
indentSize: number;
insertSpaces: boolean;
atomicSoftTabs: boolean;
defaultEOL: DefaultEndOfLine;
trimAutoWhitespace: boolean;
}) {
this.tabSize = Math.max(1, src.tabSize | 0);
this.indentSize = src.tabSize | 0;
this.insertSpaces = Boolean(src.insertSpaces);
this.atomicSoftTabs = Boolean(src.atomicSoftTabs);
this.defaultEOL = src.defaultEOL | 0;
this.trimAutoWhitespace = Boolean(src.trimAutoWhitespace);
}
......@@ -428,6 +431,7 @@ export class TextModelResolvedOptions {
this.tabSize === other.tabSize
&& this.indentSize === other.indentSize
&& this.insertSpaces === other.insertSpaces
&& this.atomicSoftTabs === other.atomicSoftTabs
&& this.defaultEOL === other.defaultEOL
&& this.trimAutoWhitespace === other.trimAutoWhitespace
);
......@@ -441,6 +445,7 @@ export class TextModelResolvedOptions {
tabSize: this.tabSize !== newOpts.tabSize,
indentSize: this.indentSize !== newOpts.indentSize,
insertSpaces: this.insertSpaces !== newOpts.insertSpaces,
atomicSoftTabs: this.atomicSoftTabs !== newOpts.atomicSoftTabs,
trimAutoWhitespace: this.trimAutoWhitespace !== newOpts.trimAutoWhitespace,
};
}
......@@ -453,6 +458,7 @@ export interface ITextModelCreationOptions {
tabSize: number;
indentSize: number;
insertSpaces: boolean;
atomicSoftTabs: boolean;
detectIndentation: boolean;
trimAutoWhitespace: boolean;
defaultEOL: DefaultEndOfLine;
......@@ -464,6 +470,7 @@ export interface ITextModelUpdateOptions {
tabSize?: number;
indentSize?: number;
insertSpaces?: boolean;
atomicSoftTabs?: boolean;
trimAutoWhitespace?: boolean;
}
......
......@@ -197,6 +197,7 @@ export class TextModel extends Disposable implements model.ITextModel {
tabSize: EDITOR_MODEL_DEFAULTS.tabSize,
indentSize: EDITOR_MODEL_DEFAULTS.indentSize,
insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces,
atomicSoftTabs: EDITOR_MODEL_DEFAULTS.atomicSoftTabs,
detectIndentation: false,
defaultEOL: model.DefaultEndOfLine.LF,
trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace,
......@@ -210,6 +211,7 @@ export class TextModel extends Disposable implements model.ITextModel {
tabSize: guessedIndentation.tabSize,
indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize
insertSpaces: guessedIndentation.insertSpaces,
atomicSoftTabs: options.atomicSoftTabs,
trimAutoWhitespace: options.trimAutoWhitespace,
defaultEOL: options.defaultEOL
});
......@@ -219,6 +221,7 @@ export class TextModel extends Disposable implements model.ITextModel {
tabSize: options.tabSize,
indentSize: options.indentSize,
insertSpaces: options.insertSpaces,
atomicSoftTabs: options.atomicSoftTabs,
trimAutoWhitespace: options.trimAutoWhitespace,
defaultEOL: options.defaultEOL
});
......@@ -619,12 +622,14 @@ export class TextModel extends Disposable implements model.ITextModel {
let tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize;
let indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.indentSize;
let insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces;
let atomicSoftTabs = (typeof _newOpts.atomicSoftTabs !== 'undefined') ? _newOpts.atomicSoftTabs : this._options.atomicSoftTabs;
let trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace;
let newOpts = new model.TextModelResolvedOptions({
tabSize: tabSize,
indentSize: indentSize,
insertSpaces: insertSpaces,
atomicSoftTabs: atomicSoftTabs,
defaultEOL: this._options.defaultEOL,
trimAutoWhitespace: trimAutoWhitespace
});
......
......@@ -104,6 +104,7 @@ export interface IModelOptionsChangedEvent {
readonly tabSize: boolean;
readonly indentSize: boolean;
readonly insertSpaces: boolean;
readonly atomicSoftTabs: boolean;
readonly trimAutoWhitespace: boolean;
}
......
......@@ -100,6 +100,7 @@ interface IRawEditorConfig {
tabSize?: any;
indentSize?: any;
insertSpaces?: any;
atomicSoftTabs?: any;
detectIndentation?: any;
trimAutoWhitespace?: any;
creationOptions?: any;
......@@ -213,6 +214,11 @@ export class ModelServiceImpl extends Disposable implements IModelService {
insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces));
}
let atomicSoftTabs = EDITOR_MODEL_DEFAULTS.atomicSoftTabs;
if (config.editor && typeof config.editor.atomicSoftTabs !== 'undefined') {
atomicSoftTabs = (config.editor.atomicSoftTabs === 'false' ? false : Boolean(config.editor.atomicSoftTabs));
}
let newDefaultEOL = DEFAULT_EOL;
const eol = config.eol;
if (eol === '\r\n') {
......@@ -241,6 +247,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
tabSize: tabSize,
indentSize: indentSize,
insertSpaces: insertSpaces,
atomicSoftTabs: atomicSoftTabs,
detectIndentation: detectIndentation,
defaultEOL: newDefaultEOL,
trimAutoWhitespace: trimAutoWhitespace,
......
......@@ -93,6 +93,11 @@ export interface IGlobalEditorOptions {
* Defaults to true.
*/
insertSpaces?: boolean;
/**
* Treat soft tabs like hard tabs.
* Defaults to false.
*/
atomicSoftTabs?: boolean;
/**
* Controls whether `tabSize` and `insertSpaces` will be automatically detected when a file is opened based on the file contents.
* Defaults to true.
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations';
suite('Cursor move command test', () => {
test('Test whitespaceVisibleColumn', () => {
const testCases = [
{
lineContent: ' ',
tabSize: 4,
expected: [0, 1, 2, 3, 4, 5, 6, 7, 8, -1],
},
{
lineContent: ' ',
tabSize: 4,
expected: [0, 1, 2, -1],
},
{
lineContent: '\t',
tabSize: 4,
expected: [0, 4, -1],
},
{
lineContent: '\t ',
tabSize: 4,
expected: [0, 4, 5, -1],
},
{
lineContent: ' \t\t ',
tabSize: 4,
expected: [0, 1, 4, 8, 9, -1],
},
{
lineContent: ' \tA',
tabSize: 4,
expected: [0, 1, 4, -1, -1],
},
{
lineContent: 'A',
tabSize: 4,
expected: [0, -1, -1],
},
{
lineContent: '',
tabSize: 4,
expected: [0, -1],
},
];
for (const testCase of testCases) {
const actual = testCase.expected.map((_, i) => AtomicTabMoveOperations.whitespaceVisibleColumn(testCase.lineContent, i, testCase.tabSize));
assert.deepStrictEqual(actual, testCase.expected);
}
});
test('Test atomicPosition', () => {
const testCases = [
{
lineContent: ' ',
tabSize: 4,
expectedLeft: [-1, 0, 0, 0, 0, 4, 4, 4, 4, -1],
expectedRight: [4, 4, 4, 4, 8, 8, 8, 8, -1, -1],
expectedNearest: [0, 0, 0, 4, 4, 4, 4, 8, 8, -1],
},
{
lineContent: ' \t',
tabSize: 4,
expectedLeft: [-1, 0, 0, -1],
expectedRight: [2, 2, -1, -1],
expectedNearest: [0, 0, 2, -1],
},
{
lineContent: '\t ',
tabSize: 4,
expectedLeft: [-1, 0, -1, -1],
expectedRight: [1, -1, -1, -1],
expectedNearest: [0, 1, -1, -1],
},
{
lineContent: ' \t ',
tabSize: 4,
expectedLeft: [-1, 0, 0, -1, -1],
expectedRight: [2, 2, -1, -1, -1],
expectedNearest: [0, 0, 2, -1, -1],
},
{
lineContent: ' A',
tabSize: 4,
expectedLeft: [-1, 0, 0, 0, 0, 4, 4, 4, 4, -1, -1],
expectedRight: [4, 4, 4, 4, 8, 8, 8, 8, -1, -1, -1],
expectedNearest: [0, 0, 0, 4, 4, 4, 4, 8, 8, -1, -1],
},
{
lineContent: ' foo',
tabSize: 4,
expectedLeft: [-1, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1],
expectedRight: [4, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1],
expectedNearest: [0, 0, 0, 4, 4, -1, -1, -1, -1, -1, -1],
},
];
for (const testCase of testCases) {
for (const { direction, expected } of [
{
direction: Direction.Left,
expected: testCase.expectedLeft,
},
{
direction: Direction.Right,
expected: testCase.expectedRight,
},
{
direction: Direction.Nearest,
expected: testCase.expectedNearest,
},
]) {
const actual = expected.map((_, i) => AtomicTabMoveOperations.atomicPosition(testCase.lineContent, i, testCase.tabSize, direction));
assert.deepStrictEqual(actual, expected);
}
}
});
});
......@@ -21,6 +21,7 @@ export interface IRelaxedTextModelCreationOptions {
tabSize?: number;
indentSize?: number;
insertSpaces?: boolean;
atomicSoftTabs?: boolean;
detectIndentation?: boolean;
trimAutoWhitespace?: boolean;
defaultEOL?: DefaultEndOfLine;
......@@ -33,6 +34,7 @@ export function createTextModel(text: string, _options: IRelaxedTextModelCreatio
tabSize: (typeof _options.tabSize === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.tabSize : _options.tabSize),
indentSize: (typeof _options.indentSize === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.indentSize : _options.indentSize),
insertSpaces: (typeof _options.insertSpaces === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.insertSpaces : _options.insertSpaces),
atomicSoftTabs: (typeof _options.atomicSoftTabs === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.atomicSoftTabs : _options.atomicSoftTabs),
detectIndentation: (typeof _options.detectIndentation === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.detectIndentation : _options.detectIndentation),
trimAutoWhitespace: (typeof _options.trimAutoWhitespace === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.trimAutoWhitespace : _options.trimAutoWhitespace),
defaultEOL: (typeof _options.defaultEOL === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.defaultEOL : _options.defaultEOL),
......
......@@ -1095,6 +1095,11 @@ declare namespace monaco.editor {
* Defaults to true.
*/
insertSpaces?: boolean;
/**
* Treat soft tabs like hard tabs.
* Defaults to false.
*/
atomicSoftTabs?: boolean;
/**
* Controls whether `tabSize` and `insertSpaces` will be automatically detected when a file is opened based on the file contents.
* Defaults to true.
......@@ -1575,6 +1580,7 @@ declare namespace monaco.editor {
readonly tabSize: number;
readonly indentSize: number;
readonly insertSpaces: boolean;
readonly atomicSoftTabs: boolean;
readonly defaultEOL: DefaultEndOfLine;
readonly trimAutoWhitespace: boolean;
}
......@@ -1583,6 +1589,7 @@ declare namespace monaco.editor {
tabSize?: number;
indentSize?: number;
insertSpaces?: boolean;
atomicSoftTabs?: boolean;
trimAutoWhitespace?: boolean;
}
......@@ -2452,6 +2459,7 @@ declare namespace monaco.editor {
readonly tabSize: boolean;
readonly indentSize: boolean;
readonly insertSpaces: boolean;
readonly atomicSoftTabs: boolean;
readonly trimAutoWhitespace: boolean;
}
......
......@@ -655,6 +655,12 @@ declare module 'vscode' {
*/
insertSpaces?: boolean | string;
/**
* When navigating treat soft tabs like hard tabs. This means you can't position
* the cursor in the middle of an indentation, even if it is made of space.
*/
atomicSoftTabs?: boolean;
/**
* The rendering style of the cursor in this editor.
* When getting a text editor's options, this property will always be present.
......
......@@ -80,6 +80,7 @@ export class MainThreadTextEditorProperties {
insertSpaces: modelOptions.insertSpaces,
tabSize: modelOptions.tabSize,
indentSize: modelOptions.indentSize,
atomicSoftTabs: modelOptions.atomicSoftTabs,
cursorStyle: cursorStyle,
lineNumbers: lineNumbers
};
......@@ -148,6 +149,7 @@ export class MainThreadTextEditorProperties {
a.tabSize === b.tabSize
&& a.indentSize === b.indentSize
&& a.insertSpaces === b.insertSpaces
&& a.atomicSoftTabs === b.atomicSoftTabs
&& a.cursorStyle === b.cursorStyle
&& a.lineNumbers === b.lineNumbers
);
......@@ -374,6 +376,9 @@ export class MainThreadTextEditor {
if (typeof newConfiguration.insertSpaces !== 'undefined') {
newOpts.insertSpaces = newConfiguration.insertSpaces;
}
if (typeof newConfiguration.atomicSoftTabs !== 'undefined') {
newOpts.atomicSoftTabs = newConfiguration.atomicSoftTabs;
}
if (typeof newConfiguration.tabSize !== 'undefined') {
newOpts.tabSize = newConfiguration.tabSize;
}
......
......@@ -233,6 +233,7 @@ export interface ITextEditorConfigurationUpdate {
tabSize?: number | 'auto';
indentSize?: number | 'tabSize';
insertSpaces?: boolean | 'auto';
atomicSoftTabs?: boolean;
cursorStyle?: TextEditorCursorStyle;
lineNumbers?: RenderLineNumbersType;
}
......@@ -241,6 +242,7 @@ export interface IResolvedTextEditorConfiguration {
tabSize: number;
indentSize: number;
insertSpaces: boolean;
atomicSoftTabs: boolean;
cursorStyle: TextEditorCursorStyle;
lineNumbers: RenderLineNumbersType;
}
......
......@@ -143,6 +143,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
private _tabSize!: number;
private _indentSize!: number;
private _insertSpaces!: boolean;
private _atomicSoftTabs!: boolean;
private _cursorStyle!: TextEditorCursorStyle;
private _lineNumbers!: TextEditorLineNumbersStyle;
......@@ -157,6 +158,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
this._tabSize = source.tabSize;
this._indentSize = source.indentSize;
this._insertSpaces = source.insertSpaces;
this._atomicSoftTabs = source.atomicSoftTabs;
this._cursorStyle = source.cursorStyle;
this._lineNumbers = TypeConverters.TextEditorLineNumbersStyle.to(source.lineNumbers);
}
......@@ -269,6 +271,23 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
}));
}
public get atomicSoftTabs(): boolean {
return this._atomicSoftTabs;
}
public set atomicSoftTabs(value: boolean) {
const atomicSoftTabs = value;
if (this._atomicSoftTabs === atomicSoftTabs) {
// nothing to do
return;
}
// reflect the new atomicSoftTabs value immediately
this._atomicSoftTabs = atomicSoftTabs;
this._warnOnError(this._proxy.$trySetOptions(this._id, {
atomicSoftTabs
}));
}
public get cursorStyle(): TextEditorCursorStyle {
return this._cursorStyle;
}
......@@ -659,4 +678,3 @@ export class ExtHostTextEditor implements vscode.TextEditor {
});
}
}
......@@ -20,7 +20,7 @@ suite('ExtHostTextEditor', () => {
], '\n', 1, 'text', false);
setup(() => {
editor = new ExtHostTextEditor('fake', null!, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
editor = new ExtHostTextEditor('fake', null!, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, atomicSoftTabs: false, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
});
test('disposed editor', () => {
......@@ -47,7 +47,7 @@ suite('ExtHostTextEditor', () => {
applyCount += 1;
return Promise.resolve(true);
}
}, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
}, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, atomicSoftTabs: false, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
await editor.edit(edit => { });
assert.equal(applyCount, 0);
......@@ -91,6 +91,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
}, new NullLogService());
......@@ -106,6 +107,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: opts.tabSize,
indentSize: opts.indentSize,
insertSpaces: opts.insertSpaces,
atomicSoftTabs: opts.atomicSoftTabs,
cursorStyle: opts.cursorStyle,
lineNumbers: opts.lineNumbers
};
......@@ -118,6 +120,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -130,6 +133,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 1,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -142,6 +146,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 2,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -154,6 +159,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 2,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -166,6 +172,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -178,6 +185,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -190,6 +198,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -202,6 +211,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -214,6 +224,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -226,6 +237,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -238,6 +250,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 1,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -250,6 +263,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 2,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -262,6 +276,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 2,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -274,6 +289,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -286,6 +302,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -298,6 +315,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -310,6 +328,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -322,6 +341,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -334,6 +354,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -346,6 +367,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -358,6 +380,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -370,6 +393,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -382,6 +406,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -394,6 +419,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -406,6 +432,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -418,6 +445,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Block,
lineNumbers: RenderLineNumbersType.On
});
......@@ -430,6 +458,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -442,6 +471,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.Off
});
......@@ -452,6 +482,7 @@ suite('ExtHostTextEditorOptions', () => {
opts.assign({
tabSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: TextEditorLineNumbersStyle.On
});
......@@ -459,6 +490,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -474,6 +506,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -489,6 +522,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 3,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
......@@ -504,6 +538,7 @@ suite('ExtHostTextEditorOptions', () => {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
atomicSoftTabs: false,
cursorStyle: TextEditorCursorStyle.Block,
lineNumbers: RenderLineNumbersType.Relative
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册