Path: blob/main/src/vs/editor/test/browser/commands/shiftCommand.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';8import { ShiftCommand } from '../../../common/commands/shiftCommand.js';9import { EditorAutoIndentStrategy } from '../../../common/config/editorOptions.js';10import { ISingleEditOperation } from '../../../common/core/editOperation.js';11import { Range } from '../../../common/core/range.js';12import { Selection } from '../../../common/core/selection.js';13import { ILanguageService } from '../../../common/languages/language.js';14import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';15import { getEditOperation, testCommand } from '../testCommand.js';16import { javascriptOnEnterRules } from '../../common/modes/supports/onEnterRules.js';17import { TestLanguageConfigurationService } from '../../common/modes/testLanguageConfigurationService.js';18import { withEditorModel } from '../../common/testTextModel.js';19import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';2021/**22* Create single edit operation23*/24function createSingleEditOp(text: string, positionLineNumber: number, positionColumn: number, selectionLineNumber: number = positionLineNumber, selectionColumn: number = positionColumn): ISingleEditOperation {25return {26range: new Range(selectionLineNumber, selectionColumn, positionLineNumber, positionColumn),27text: text,28forceMoveMarkers: false29};30}3132class DocBlockCommentMode extends Disposable {3334public static languageId = 'commentMode';35public readonly languageId = DocBlockCommentMode.languageId;3637constructor(38@ILanguageService languageService: ILanguageService,39@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService40) {41super();42this._register(languageService.registerLanguage({ id: this.languageId }));43this._register(languageConfigurationService.register(this.languageId, {44brackets: [45['(', ')'],46['{', '}'],47['[', ']']48],4950onEnterRules: javascriptOnEnterRules51}));52}53}5455function testShiftCommand(lines: string[], languageId: string | null, useTabStops: boolean, selection: Selection, expectedLines: string[], expectedSelection: Selection, prepare?: (accessor: ServicesAccessor, disposables: DisposableStore) => void): void {56testCommand(lines, languageId, selection, (accessor, sel) => new ShiftCommand(sel, {57isUnshift: false,58tabSize: 4,59indentSize: 4,60insertSpaces: false,61useTabStops: useTabStops,62autoIndent: EditorAutoIndentStrategy.Full,63}, accessor.get(ILanguageConfigurationService)), expectedLines, expectedSelection, undefined, prepare);64}6566function testUnshiftCommand(lines: string[], languageId: string | null, useTabStops: boolean, selection: Selection, expectedLines: string[], expectedSelection: Selection, prepare?: (accessor: ServicesAccessor, disposables: DisposableStore) => void): void {67testCommand(lines, languageId, selection, (accessor, sel) => new ShiftCommand(sel, {68isUnshift: true,69tabSize: 4,70indentSize: 4,71insertSpaces: false,72useTabStops: useTabStops,73autoIndent: EditorAutoIndentStrategy.Full,74}, accessor.get(ILanguageConfigurationService)), expectedLines, expectedSelection, undefined, prepare);75}7677function prepareDocBlockCommentLanguage(accessor: ServicesAccessor, disposables: DisposableStore) {78const languageConfigurationService = accessor.get(ILanguageConfigurationService);79const languageService = accessor.get(ILanguageService);80disposables.add(new DocBlockCommentMode(languageService, languageConfigurationService));81}8283suite('Editor Commands - ShiftCommand', () => {8485ensureNoDisposablesAreLeakedInTestSuite();8687// --------- shift8889test('Bug 9503: Shifting without any selection', () => {90testShiftCommand(91[92'My First Line',93'\t\tMy Second Line',94' Third Line',95'',96'123'97],98null,99true,100new Selection(1, 1, 1, 1),101[102'\tMy First Line',103'\t\tMy Second Line',104' Third Line',105'',106'123'107],108new Selection(1, 2, 1, 2)109);110});111112test('shift on single line selection 1', () => {113testShiftCommand(114[115'My First Line',116'\t\tMy Second Line',117' Third Line',118'',119'123'120],121null,122true,123new Selection(1, 3, 1, 1),124[125'\tMy First Line',126'\t\tMy Second Line',127' Third Line',128'',129'123'130],131new Selection(1, 4, 1, 1)132);133});134135test('shift on single line selection 2', () => {136testShiftCommand(137[138'My First Line',139'\t\tMy Second Line',140' Third Line',141'',142'123'143],144null,145true,146new Selection(1, 1, 1, 3),147[148'\tMy First Line',149'\t\tMy Second Line',150' Third Line',151'',152'123'153],154new Selection(1, 1, 1, 4)155);156});157158test('simple shift', () => {159testShiftCommand(160[161'My First Line',162'\t\tMy Second Line',163' Third Line',164'',165'123'166],167null,168true,169new Selection(1, 1, 2, 1),170[171'\tMy First Line',172'\t\tMy Second Line',173' Third Line',174'',175'123'176],177new Selection(1, 1, 2, 1)178);179});180181test('shifting on two separate lines', () => {182testShiftCommand(183[184'My First Line',185'\t\tMy Second Line',186' Third Line',187'',188'123'189],190null,191true,192new Selection(1, 1, 2, 1),193[194'\tMy First Line',195'\t\tMy Second Line',196' Third Line',197'',198'123'199],200new Selection(1, 1, 2, 1)201);202203testShiftCommand(204[205'\tMy First Line',206'\t\tMy Second Line',207' Third Line',208'',209'123'210],211null,212true,213new Selection(2, 1, 3, 1),214[215'\tMy First Line',216'\t\t\tMy Second Line',217' Third Line',218'',219'123'220],221new Selection(2, 1, 3, 1)222);223});224225test('shifting on two lines', () => {226testShiftCommand(227[228'My First Line',229'\t\tMy Second Line',230' Third Line',231'',232'123'233],234null,235true,236new Selection(1, 2, 2, 2),237[238'\tMy First Line',239'\t\t\tMy Second Line',240' Third Line',241'',242'123'243],244new Selection(1, 3, 2, 2)245);246});247248test('shifting on two lines again', () => {249testShiftCommand(250[251'My First Line',252'\t\tMy Second Line',253' Third Line',254'',255'123'256],257null,258true,259new Selection(2, 2, 1, 2),260[261'\tMy First Line',262'\t\t\tMy Second Line',263' Third Line',264'',265'123'266],267new Selection(2, 2, 1, 3)268);269});270271test('shifting at end of file', () => {272testShiftCommand(273[274'My First Line',275'\t\tMy Second Line',276' Third Line',277'',278'123'279],280null,281true,282new Selection(4, 1, 5, 2),283[284'My First Line',285'\t\tMy Second Line',286' Third Line',287'',288'\t123'289],290new Selection(4, 1, 5, 3)291);292});293294test('issue #1120 TAB should not indent empty lines in a multi-line selection', () => {295testShiftCommand(296[297'My First Line',298'\t\tMy Second Line',299' Third Line',300'',301'123'302],303null,304true,305new Selection(1, 1, 5, 2),306[307'\tMy First Line',308'\t\t\tMy Second Line',309'\t\tThird Line',310'',311'\t123'312],313new Selection(1, 1, 5, 3)314);315316testShiftCommand(317[318'My First Line',319'\t\tMy Second Line',320' Third Line',321'',322'123'323],324null,325true,326new Selection(4, 1, 5, 1),327[328'My First Line',329'\t\tMy Second Line',330' Third Line',331'\t',332'123'333],334new Selection(4, 1, 5, 1)335);336});337338// --------- unshift339340test('unshift on single line selection 1', () => {341testShiftCommand(342[343'My First Line',344'\t\tMy Second Line',345' Third Line',346'',347'123'348],349null,350true,351new Selection(2, 3, 2, 1),352[353'My First Line',354'\t\t\tMy Second Line',355' Third Line',356'',357'123'358],359new Selection(2, 3, 2, 1)360);361});362363test('unshift on single line selection 2', () => {364testShiftCommand(365[366'My First Line',367'\t\tMy Second Line',368' Third Line',369'',370'123'371],372null,373true,374new Selection(2, 1, 2, 3),375[376'My First Line',377'\t\t\tMy Second Line',378' Third Line',379'',380'123'381],382new Selection(2, 1, 2, 3)383);384});385386test('simple unshift', () => {387testUnshiftCommand(388[389'My First Line',390'\t\tMy Second Line',391' Third Line',392'',393'123'394],395null,396true,397new Selection(1, 1, 2, 1),398[399'My First Line',400'\t\tMy Second Line',401' Third Line',402'',403'123'404],405new Selection(1, 1, 2, 1)406);407});408409test('unshifting on two lines 1', () => {410testUnshiftCommand(411[412'My First Line',413'\t\tMy Second Line',414' Third Line',415'',416'123'417],418null,419true,420new Selection(1, 2, 2, 2),421[422'My First Line',423'\tMy Second Line',424' Third Line',425'',426'123'427],428new Selection(1, 2, 2, 2)429);430});431432test('unshifting on two lines 2', () => {433testUnshiftCommand(434[435'My First Line',436'\t\tMy Second Line',437' Third Line',438'',439'123'440],441null,442true,443new Selection(2, 3, 2, 1),444[445'My First Line',446'\tMy Second Line',447' Third Line',448'',449'123'450],451new Selection(2, 2, 2, 1)452);453});454455test('unshifting at the end of the file', () => {456testUnshiftCommand(457[458'My First Line',459'\t\tMy Second Line',460' Third Line',461'',462'123'463],464null,465true,466new Selection(4, 1, 5, 2),467[468'My First Line',469'\t\tMy Second Line',470' Third Line',471'',472'123'473],474new Selection(4, 1, 5, 2)475);476});477478test('unshift many times + shift', () => {479testUnshiftCommand(480[481'My First Line',482'\t\tMy Second Line',483' Third Line',484'',485'123'486],487null,488true,489new Selection(1, 1, 5, 4),490[491'My First Line',492'\tMy Second Line',493'Third Line',494'',495'123'496],497new Selection(1, 1, 5, 4)498);499500testUnshiftCommand(501[502'My First Line',503'\tMy Second Line',504'Third Line',505'',506'123'507],508null,509true,510new Selection(1, 1, 5, 4),511[512'My First Line',513'My Second Line',514'Third Line',515'',516'123'517],518new Selection(1, 1, 5, 4)519);520521testShiftCommand(522[523'My First Line',524'My Second Line',525'Third Line',526'',527'123'528],529null,530true,531new Selection(1, 1, 5, 4),532[533'\tMy First Line',534'\tMy Second Line',535'\tThird Line',536'',537'\t123'538],539new Selection(1, 1, 5, 5)540);541});542543test('Bug 9119: Unshift from first column doesn\'t work', () => {544testUnshiftCommand(545[546'My First Line',547'\t\tMy Second Line',548' Third Line',549'',550'123'551],552null,553true,554new Selection(2, 1, 2, 1),555[556'My First Line',557'\tMy Second Line',558' Third Line',559'',560'123'561],562new Selection(2, 1, 2, 1)563);564});565566test('issue #348: indenting around doc block comments', () => {567testShiftCommand(568[569'',570'/**',571' * a doc comment',572' */',573'function hello() {}'574],575DocBlockCommentMode.languageId,576true,577new Selection(1, 1, 5, 20),578[579'',580'\t/**',581'\t * a doc comment',582'\t */',583'\tfunction hello() {}'584],585new Selection(1, 1, 5, 21),586prepareDocBlockCommentLanguage587);588589testUnshiftCommand(590[591'',592'/**',593' * a doc comment',594' */',595'function hello() {}'596],597DocBlockCommentMode.languageId,598true,599new Selection(1, 1, 5, 20),600[601'',602'/**',603' * a doc comment',604' */',605'function hello() {}'606],607new Selection(1, 1, 5, 20),608prepareDocBlockCommentLanguage609);610611testUnshiftCommand(612[613'\t',614'\t/**',615'\t * a doc comment',616'\t */',617'\tfunction hello() {}'618],619DocBlockCommentMode.languageId,620true,621new Selection(1, 1, 5, 21),622[623'',624'/**',625' * a doc comment',626' */',627'function hello() {}'628],629new Selection(1, 1, 5, 20),630prepareDocBlockCommentLanguage631);632});633634test('issue #1609: Wrong indentation of block comments', () => {635testShiftCommand(636[637'',638'/**',639' * test',640' *',641' * @type {number}',642' */',643'var foo = 0;'644],645DocBlockCommentMode.languageId,646true,647new Selection(1, 1, 7, 13),648[649'',650'\t/**',651'\t * test',652'\t *',653'\t * @type {number}',654'\t */',655'\tvar foo = 0;'656],657new Selection(1, 1, 7, 14),658prepareDocBlockCommentLanguage659);660});661662test('issue #1620: a) Line indent doesn\'t handle leading whitespace properly', () => {663testCommand(664[665' Written | Numeric',666' one | 1',667' two | 2',668' three | 3',669' four | 4',670' five | 5',671' six | 6',672' seven | 7',673' eight | 8',674' nine | 9',675' ten | 10',676' eleven | 11',677'',678],679null,680new Selection(1, 1, 13, 1),681(accessor, sel) => new ShiftCommand(sel, {682isUnshift: false,683tabSize: 4,684indentSize: 4,685insertSpaces: true,686useTabStops: false,687autoIndent: EditorAutoIndentStrategy.Full,688}, accessor.get(ILanguageConfigurationService)),689[690' Written | Numeric',691' one | 1',692' two | 2',693' three | 3',694' four | 4',695' five | 5',696' six | 6',697' seven | 7',698' eight | 8',699' nine | 9',700' ten | 10',701' eleven | 11',702'',703],704new Selection(1, 1, 13, 1)705);706});707708test('issue #1620: b) Line indent doesn\'t handle leading whitespace properly', () => {709testCommand(710[711' Written | Numeric',712' one | 1',713' two | 2',714' three | 3',715' four | 4',716' five | 5',717' six | 6',718' seven | 7',719' eight | 8',720' nine | 9',721' ten | 10',722' eleven | 11',723'',724],725null,726new Selection(1, 1, 13, 1),727(accessor, sel) => new ShiftCommand(sel, {728isUnshift: true,729tabSize: 4,730indentSize: 4,731insertSpaces: true,732useTabStops: false,733autoIndent: EditorAutoIndentStrategy.Full,734}, accessor.get(ILanguageConfigurationService)),735[736' Written | Numeric',737' one | 1',738' two | 2',739' three | 3',740' four | 4',741' five | 5',742' six | 6',743' seven | 7',744' eight | 8',745' nine | 9',746' ten | 10',747' eleven | 11',748'',749],750new Selection(1, 1, 13, 1)751);752});753754test('issue #1620: c) Line indent doesn\'t handle leading whitespace properly', () => {755testCommand(756[757' Written | Numeric',758' one | 1',759' two | 2',760' three | 3',761' four | 4',762' five | 5',763' six | 6',764' seven | 7',765' eight | 8',766' nine | 9',767' ten | 10',768' eleven | 11',769'',770],771null,772new Selection(1, 1, 13, 1),773(accessor, sel) => new ShiftCommand(sel, {774isUnshift: true,775tabSize: 4,776indentSize: 4,777insertSpaces: false,778useTabStops: false,779autoIndent: EditorAutoIndentStrategy.Full,780}, accessor.get(ILanguageConfigurationService)),781[782' Written | Numeric',783' one | 1',784' two | 2',785' three | 3',786' four | 4',787' five | 5',788' six | 6',789' seven | 7',790' eight | 8',791' nine | 9',792' ten | 10',793' eleven | 11',794'',795],796new Selection(1, 1, 13, 1)797);798});799800test('issue #1620: d) Line indent doesn\'t handle leading whitespace properly', () => {801testCommand(802[803'\t Written | Numeric',804'\t one | 1',805'\t two | 2',806'\t three | 3',807'\t four | 4',808'\t five | 5',809'\t six | 6',810'\t seven | 7',811'\t eight | 8',812'\t nine | 9',813'\t ten | 10',814'\t eleven | 11',815'',816],817null,818new Selection(1, 1, 13, 1),819(accessor, sel) => new ShiftCommand(sel, {820isUnshift: true,821tabSize: 4,822indentSize: 4,823insertSpaces: true,824useTabStops: false,825autoIndent: EditorAutoIndentStrategy.Full,826}, accessor.get(ILanguageConfigurationService)),827[828' Written | Numeric',829' one | 1',830' two | 2',831' three | 3',832' four | 4',833' five | 5',834' six | 6',835' seven | 7',836' eight | 8',837' nine | 9',838' ten | 10',839' eleven | 11',840'',841],842new Selection(1, 1, 13, 1)843);844});845846test('issue microsoft/monaco-editor#443: Indentation of a single row deletes selected text in some cases', () => {847testCommand(848[849'Hello world!',850'another line'851],852null,853new Selection(1, 1, 1, 13),854(accessor, sel) => new ShiftCommand(sel, {855isUnshift: false,856tabSize: 4,857indentSize: 4,858insertSpaces: false,859useTabStops: true,860autoIndent: EditorAutoIndentStrategy.Full,861}, accessor.get(ILanguageConfigurationService)),862[863'\tHello world!',864'another line'865],866new Selection(1, 1, 1, 14)867);868});869870test('bug #16815:Shift+Tab doesn\'t go back to tabstop', () => {871872const repeatStr = (str: string, cnt: number): string => {873let r = '';874for (let i = 0; i < cnt; i++) {875r += str;876}877return r;878};879880const testOutdent = (tabSize: number, indentSize: number, insertSpaces: boolean, lineText: string, expectedIndents: number) => {881const oneIndent = insertSpaces ? repeatStr(' ', indentSize) : '\t';882const expectedIndent = repeatStr(oneIndent, expectedIndents);883if (lineText.length > 0) {884_assertUnshiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]);885} else {886_assertUnshiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], []);887}888};889890const testIndent = (tabSize: number, indentSize: number, insertSpaces: boolean, lineText: string, expectedIndents: number) => {891const oneIndent = insertSpaces ? repeatStr(' ', indentSize) : '\t';892const expectedIndent = repeatStr(oneIndent, expectedIndents);893_assertShiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]);894};895896const testIndentation = (tabSize: number, indentSize: number, lineText: string, expectedOnOutdent: number, expectedOnIndent: number) => {897testOutdent(tabSize, indentSize, true, lineText, expectedOnOutdent);898testOutdent(tabSize, indentSize, false, lineText, expectedOnOutdent);899900testIndent(tabSize, indentSize, true, lineText, expectedOnIndent);901testIndent(tabSize, indentSize, false, lineText, expectedOnIndent);902};903904// insertSpaces: true905// 0 => 0906testIndentation(4, 4, '', 0, 1);907908// 1 => 0909testIndentation(4, 4, '\t', 0, 2);910testIndentation(4, 4, ' ', 0, 1);911testIndentation(4, 4, ' \t', 0, 2);912testIndentation(4, 4, ' ', 0, 1);913testIndentation(4, 4, ' \t', 0, 2);914testIndentation(4, 4, ' ', 0, 1);915testIndentation(4, 4, ' \t', 0, 2);916testIndentation(4, 4, ' ', 0, 2);917918// 2 => 1919testIndentation(4, 4, '\t\t', 1, 3);920testIndentation(4, 4, '\t ', 1, 2);921testIndentation(4, 4, '\t \t', 1, 3);922testIndentation(4, 4, '\t ', 1, 2);923testIndentation(4, 4, '\t \t', 1, 3);924testIndentation(4, 4, '\t ', 1, 2);925testIndentation(4, 4, '\t \t', 1, 3);926testIndentation(4, 4, '\t ', 1, 3);927testIndentation(4, 4, ' \t\t', 1, 3);928testIndentation(4, 4, ' \t ', 1, 2);929testIndentation(4, 4, ' \t \t', 1, 3);930testIndentation(4, 4, ' \t ', 1, 2);931testIndentation(4, 4, ' \t \t', 1, 3);932testIndentation(4, 4, ' \t ', 1, 2);933testIndentation(4, 4, ' \t \t', 1, 3);934testIndentation(4, 4, ' \t ', 1, 3);935testIndentation(4, 4, ' \t\t', 1, 3);936testIndentation(4, 4, ' \t ', 1, 2);937testIndentation(4, 4, ' \t \t', 1, 3);938testIndentation(4, 4, ' \t ', 1, 2);939testIndentation(4, 4, ' \t \t', 1, 3);940testIndentation(4, 4, ' \t ', 1, 2);941testIndentation(4, 4, ' \t \t', 1, 3);942testIndentation(4, 4, ' \t ', 1, 3);943testIndentation(4, 4, ' \t\t', 1, 3);944testIndentation(4, 4, ' \t ', 1, 2);945testIndentation(4, 4, ' \t \t', 1, 3);946testIndentation(4, 4, ' \t ', 1, 2);947testIndentation(4, 4, ' \t \t', 1, 3);948testIndentation(4, 4, ' \t ', 1, 2);949testIndentation(4, 4, ' \t \t', 1, 3);950testIndentation(4, 4, ' \t ', 1, 3);951testIndentation(4, 4, ' \t', 1, 3);952testIndentation(4, 4, ' ', 1, 2);953testIndentation(4, 4, ' \t', 1, 3);954testIndentation(4, 4, ' ', 1, 2);955testIndentation(4, 4, ' \t', 1, 3);956testIndentation(4, 4, ' ', 1, 2);957testIndentation(4, 4, ' \t', 1, 3);958testIndentation(4, 4, ' ', 1, 3);959960// 3 => 2961testIndentation(4, 4, ' ', 2, 3);962963function _assertUnshiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: ISingleEditOperation[]): void {964return withEditorModel(text, (model) => {965const testLanguageConfigurationService = new TestLanguageConfigurationService();966const op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), {967isUnshift: true,968tabSize: tabSize,969indentSize: indentSize,970insertSpaces: insertSpaces,971useTabStops: true,972autoIndent: EditorAutoIndentStrategy.Full,973}, testLanguageConfigurationService);974const actual = getEditOperation(model, op);975assert.deepStrictEqual(actual, expected);976testLanguageConfigurationService.dispose();977});978}979980function _assertShiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: ISingleEditOperation[]): void {981return withEditorModel(text, (model) => {982const testLanguageConfigurationService = new TestLanguageConfigurationService();983const op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), {984isUnshift: false,985tabSize: tabSize,986indentSize: indentSize,987insertSpaces: insertSpaces,988useTabStops: true,989autoIndent: EditorAutoIndentStrategy.Full,990}, testLanguageConfigurationService);991const actual = getEditOperation(model, op);992assert.deepStrictEqual(actual, expected);993testLanguageConfigurationService.dispose();994});995}996});997998});99910001001