indentation.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { DisposableStore } from '../../../base/common/lifecycle.js';
  6. import * as strings from '../../../base/common/strings.js';
  7. import { EditorAction, registerEditorAction, registerEditorContribution } from '../../browser/editorExtensions.js';
  8. import { ShiftCommand } from '../../common/commands/shiftCommand.js';
  9. import { EditOperation } from '../../common/core/editOperation.js';
  10. import { Range } from '../../common/core/range.js';
  11. import { Selection } from '../../common/core/selection.js';
  12. import { EditorContextKeys } from '../../common/editorContextKeys.js';
  13. import { TextModel } from '../../common/model/textModel.js';
  14. import { LanguageConfigurationRegistry } from '../../common/modes/languageConfigurationRegistry.js';
  15. import { IModelService } from '../../common/services/modelService.js';
  16. import * as indentUtils from './indentUtils.js';
  17. import * as nls from '../../../nls.js';
  18. import { IQuickInputService } from '../../../platform/quickinput/common/quickInput.js';
  19. export function getReindentEditOperations(model, startLineNumber, endLineNumber, inheritedIndent) {
  20. if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
  21. // Model is empty
  22. return [];
  23. }
  24. const indentationRules = LanguageConfigurationRegistry.getIndentationRules(model.getLanguageId());
  25. if (!indentationRules) {
  26. return [];
  27. }
  28. endLineNumber = Math.min(endLineNumber, model.getLineCount());
  29. // Skip `unIndentedLinePattern` lines
  30. while (startLineNumber <= endLineNumber) {
  31. if (!indentationRules.unIndentedLinePattern) {
  32. break;
  33. }
  34. let text = model.getLineContent(startLineNumber);
  35. if (!indentationRules.unIndentedLinePattern.test(text)) {
  36. break;
  37. }
  38. startLineNumber++;
  39. }
  40. if (startLineNumber > endLineNumber - 1) {
  41. return [];
  42. }
  43. const { tabSize, indentSize, insertSpaces } = model.getOptions();
  44. const shiftIndent = (indentation, count) => {
  45. count = count || 1;
  46. return ShiftCommand.shiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
  47. };
  48. const unshiftIndent = (indentation, count) => {
  49. count = count || 1;
  50. return ShiftCommand.unshiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
  51. };
  52. let indentEdits = [];
  53. // indentation being passed to lines below
  54. let globalIndent;
  55. // Calculate indentation for the first line
  56. // If there is no passed-in indentation, we use the indentation of the first line as base.
  57. let currentLineText = model.getLineContent(startLineNumber);
  58. let adjustedLineContent = currentLineText;
  59. if (inheritedIndent !== undefined && inheritedIndent !== null) {
  60. globalIndent = inheritedIndent;
  61. let oldIndentation = strings.getLeadingWhitespace(currentLineText);
  62. adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
  63. if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
  64. globalIndent = unshiftIndent(globalIndent);
  65. adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
  66. }
  67. if (currentLineText !== adjustedLineContent) {
  68. indentEdits.push(EditOperation.replaceMove(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces)));
  69. }
  70. }
  71. else {
  72. globalIndent = strings.getLeadingWhitespace(currentLineText);
  73. }
  74. // idealIndentForNextLine doesn't equal globalIndent when there is a line matching `indentNextLinePattern`.
  75. let idealIndentForNextLine = globalIndent;
  76. if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
  77. idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
  78. globalIndent = shiftIndent(globalIndent);
  79. }
  80. else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
  81. idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
  82. }
  83. startLineNumber++;
  84. // Calculate indentation adjustment for all following lines
  85. for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
  86. let text = model.getLineContent(lineNumber);
  87. let oldIndentation = strings.getLeadingWhitespace(text);
  88. let adjustedLineContent = idealIndentForNextLine + text.substring(oldIndentation.length);
  89. if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
  90. idealIndentForNextLine = unshiftIndent(idealIndentForNextLine);
  91. globalIndent = unshiftIndent(globalIndent);
  92. }
  93. if (oldIndentation !== idealIndentForNextLine) {
  94. indentEdits.push(EditOperation.replaceMove(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces)));
  95. }
  96. // calculate idealIndentForNextLine
  97. if (indentationRules.unIndentedLinePattern && indentationRules.unIndentedLinePattern.test(text)) {
  98. // In reindent phase, if the line matches `unIndentedLinePattern` we inherit indentation from above lines
  99. // but don't change globalIndent and idealIndentForNextLine.
  100. continue;
  101. }
  102. else if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
  103. globalIndent = shiftIndent(globalIndent);
  104. idealIndentForNextLine = globalIndent;
  105. }
  106. else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
  107. idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
  108. }
  109. else {
  110. idealIndentForNextLine = globalIndent;
  111. }
  112. }
  113. return indentEdits;
  114. }
  115. export class IndentationToSpacesAction extends EditorAction {
  116. constructor() {
  117. super({
  118. id: IndentationToSpacesAction.ID,
  119. label: nls.localize('indentationToSpaces', "Convert Indentation to Spaces"),
  120. alias: 'Convert Indentation to Spaces',
  121. precondition: EditorContextKeys.writable
  122. });
  123. }
  124. run(accessor, editor) {
  125. let model = editor.getModel();
  126. if (!model) {
  127. return;
  128. }
  129. let modelOpts = model.getOptions();
  130. let selection = editor.getSelection();
  131. if (!selection) {
  132. return;
  133. }
  134. const command = new IndentationToSpacesCommand(selection, modelOpts.tabSize);
  135. editor.pushUndoStop();
  136. editor.executeCommands(this.id, [command]);
  137. editor.pushUndoStop();
  138. model.updateOptions({
  139. insertSpaces: true
  140. });
  141. }
  142. }
  143. IndentationToSpacesAction.ID = 'editor.action.indentationToSpaces';
  144. export class IndentationToTabsAction extends EditorAction {
  145. constructor() {
  146. super({
  147. id: IndentationToTabsAction.ID,
  148. label: nls.localize('indentationToTabs', "Convert Indentation to Tabs"),
  149. alias: 'Convert Indentation to Tabs',
  150. precondition: EditorContextKeys.writable
  151. });
  152. }
  153. run(accessor, editor) {
  154. let model = editor.getModel();
  155. if (!model) {
  156. return;
  157. }
  158. let modelOpts = model.getOptions();
  159. let selection = editor.getSelection();
  160. if (!selection) {
  161. return;
  162. }
  163. const command = new IndentationToTabsCommand(selection, modelOpts.tabSize);
  164. editor.pushUndoStop();
  165. editor.executeCommands(this.id, [command]);
  166. editor.pushUndoStop();
  167. model.updateOptions({
  168. insertSpaces: false
  169. });
  170. }
  171. }
  172. IndentationToTabsAction.ID = 'editor.action.indentationToTabs';
  173. export class ChangeIndentationSizeAction extends EditorAction {
  174. constructor(insertSpaces, opts) {
  175. super(opts);
  176. this.insertSpaces = insertSpaces;
  177. }
  178. run(accessor, editor) {
  179. const quickInputService = accessor.get(IQuickInputService);
  180. const modelService = accessor.get(IModelService);
  181. let model = editor.getModel();
  182. if (!model) {
  183. return;
  184. }
  185. const creationOpts = modelService.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
  186. const picks = [1, 2, 3, 4, 5, 6, 7, 8].map(n => ({
  187. id: n.toString(),
  188. label: n.toString(),
  189. // add description for tabSize value set in the configuration
  190. description: n === creationOpts.tabSize ? nls.localize('configuredTabSize', "Configured Tab Size") : undefined
  191. }));
  192. // auto focus the tabSize set for the current editor
  193. const autoFocusIndex = Math.min(model.getOptions().tabSize - 1, 7);
  194. setTimeout(() => {
  195. quickInputService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File"), activeItem: picks[autoFocusIndex] }).then(pick => {
  196. if (pick) {
  197. if (model && !model.isDisposed()) {
  198. model.updateOptions({
  199. tabSize: parseInt(pick.label, 10),
  200. insertSpaces: this.insertSpaces
  201. });
  202. }
  203. }
  204. });
  205. }, 50 /* quick input is sensitive to being opened so soon after another */);
  206. }
  207. }
  208. export class IndentUsingTabs extends ChangeIndentationSizeAction {
  209. constructor() {
  210. super(false, {
  211. id: IndentUsingTabs.ID,
  212. label: nls.localize('indentUsingTabs', "Indent Using Tabs"),
  213. alias: 'Indent Using Tabs',
  214. precondition: undefined
  215. });
  216. }
  217. }
  218. IndentUsingTabs.ID = 'editor.action.indentUsingTabs';
  219. export class IndentUsingSpaces extends ChangeIndentationSizeAction {
  220. constructor() {
  221. super(true, {
  222. id: IndentUsingSpaces.ID,
  223. label: nls.localize('indentUsingSpaces', "Indent Using Spaces"),
  224. alias: 'Indent Using Spaces',
  225. precondition: undefined
  226. });
  227. }
  228. }
  229. IndentUsingSpaces.ID = 'editor.action.indentUsingSpaces';
  230. export class DetectIndentation extends EditorAction {
  231. constructor() {
  232. super({
  233. id: DetectIndentation.ID,
  234. label: nls.localize('detectIndentation', "Detect Indentation from Content"),
  235. alias: 'Detect Indentation from Content',
  236. precondition: undefined
  237. });
  238. }
  239. run(accessor, editor) {
  240. const modelService = accessor.get(IModelService);
  241. let model = editor.getModel();
  242. if (!model) {
  243. return;
  244. }
  245. const creationOpts = modelService.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
  246. model.detectIndentation(creationOpts.insertSpaces, creationOpts.tabSize);
  247. }
  248. }
  249. DetectIndentation.ID = 'editor.action.detectIndentation';
  250. export class ReindentLinesAction extends EditorAction {
  251. constructor() {
  252. super({
  253. id: 'editor.action.reindentlines',
  254. label: nls.localize('editor.reindentlines', "Reindent Lines"),
  255. alias: 'Reindent Lines',
  256. precondition: EditorContextKeys.writable
  257. });
  258. }
  259. run(accessor, editor) {
  260. let model = editor.getModel();
  261. if (!model) {
  262. return;
  263. }
  264. let edits = getReindentEditOperations(model, 1, model.getLineCount());
  265. if (edits.length > 0) {
  266. editor.pushUndoStop();
  267. editor.executeEdits(this.id, edits);
  268. editor.pushUndoStop();
  269. }
  270. }
  271. }
  272. export class ReindentSelectedLinesAction extends EditorAction {
  273. constructor() {
  274. super({
  275. id: 'editor.action.reindentselectedlines',
  276. label: nls.localize('editor.reindentselectedlines', "Reindent Selected Lines"),
  277. alias: 'Reindent Selected Lines',
  278. precondition: EditorContextKeys.writable
  279. });
  280. }
  281. run(accessor, editor) {
  282. let model = editor.getModel();
  283. if (!model) {
  284. return;
  285. }
  286. let selections = editor.getSelections();
  287. if (selections === null) {
  288. return;
  289. }
  290. let edits = [];
  291. for (let selection of selections) {
  292. let startLineNumber = selection.startLineNumber;
  293. let endLineNumber = selection.endLineNumber;
  294. if (startLineNumber !== endLineNumber && selection.endColumn === 1) {
  295. endLineNumber--;
  296. }
  297. if (startLineNumber === 1) {
  298. if (startLineNumber === endLineNumber) {
  299. continue;
  300. }
  301. }
  302. else {
  303. startLineNumber--;
  304. }
  305. let editOperations = getReindentEditOperations(model, startLineNumber, endLineNumber);
  306. edits.push(...editOperations);
  307. }
  308. if (edits.length > 0) {
  309. editor.pushUndoStop();
  310. editor.executeEdits(this.id, edits);
  311. editor.pushUndoStop();
  312. }
  313. }
  314. }
  315. export class AutoIndentOnPasteCommand {
  316. constructor(edits, initialSelection) {
  317. this._initialSelection = initialSelection;
  318. this._edits = [];
  319. this._selectionId = null;
  320. for (let edit of edits) {
  321. if (edit.range && typeof edit.text === 'string') {
  322. this._edits.push(edit);
  323. }
  324. }
  325. }
  326. getEditOperations(model, builder) {
  327. for (let edit of this._edits) {
  328. builder.addEditOperation(Range.lift(edit.range), edit.text);
  329. }
  330. let selectionIsSet = false;
  331. if (Array.isArray(this._edits) && this._edits.length === 1 && this._initialSelection.isEmpty()) {
  332. if (this._edits[0].range.startColumn === this._initialSelection.endColumn &&
  333. this._edits[0].range.startLineNumber === this._initialSelection.endLineNumber) {
  334. selectionIsSet = true;
  335. this._selectionId = builder.trackSelection(this._initialSelection, true);
  336. }
  337. else if (this._edits[0].range.endColumn === this._initialSelection.startColumn &&
  338. this._edits[0].range.endLineNumber === this._initialSelection.startLineNumber) {
  339. selectionIsSet = true;
  340. this._selectionId = builder.trackSelection(this._initialSelection, false);
  341. }
  342. }
  343. if (!selectionIsSet) {
  344. this._selectionId = builder.trackSelection(this._initialSelection);
  345. }
  346. }
  347. computeCursorState(model, helper) {
  348. return helper.getTrackedSelection(this._selectionId);
  349. }
  350. }
  351. export class AutoIndentOnPaste {
  352. constructor(editor) {
  353. this.callOnDispose = new DisposableStore();
  354. this.callOnModel = new DisposableStore();
  355. this.editor = editor;
  356. this.callOnDispose.add(editor.onDidChangeConfiguration(() => this.update()));
  357. this.callOnDispose.add(editor.onDidChangeModel(() => this.update()));
  358. this.callOnDispose.add(editor.onDidChangeModelLanguage(() => this.update()));
  359. }
  360. update() {
  361. // clean up
  362. this.callOnModel.clear();
  363. // we are disabled
  364. if (this.editor.getOption(9 /* autoIndent */) < 4 /* Full */ || this.editor.getOption(47 /* formatOnPaste */)) {
  365. return;
  366. }
  367. // no model
  368. if (!this.editor.hasModel()) {
  369. return;
  370. }
  371. this.callOnModel.add(this.editor.onDidPaste(({ range }) => {
  372. this.trigger(range);
  373. }));
  374. }
  375. trigger(range) {
  376. let selections = this.editor.getSelections();
  377. if (selections === null || selections.length > 1) {
  378. return;
  379. }
  380. const model = this.editor.getModel();
  381. if (!model) {
  382. return;
  383. }
  384. if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) {
  385. return;
  386. }
  387. const autoIndent = this.editor.getOption(9 /* autoIndent */);
  388. const { tabSize, indentSize, insertSpaces } = model.getOptions();
  389. let textEdits = [];
  390. let indentConverter = {
  391. shiftIndent: (indentation) => {
  392. return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
  393. },
  394. unshiftIndent: (indentation) => {
  395. return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
  396. }
  397. };
  398. let startLineNumber = range.startLineNumber;
  399. while (startLineNumber <= range.endLineNumber) {
  400. if (this.shouldIgnoreLine(model, startLineNumber)) {
  401. startLineNumber++;
  402. continue;
  403. }
  404. break;
  405. }
  406. if (startLineNumber > range.endLineNumber) {
  407. return;
  408. }
  409. let firstLineText = model.getLineContent(startLineNumber);
  410. if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) {
  411. const indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, model, model.getLanguageId(), startLineNumber, indentConverter);
  412. if (indentOfFirstLine !== null) {
  413. let oldIndentation = strings.getLeadingWhitespace(firstLineText);
  414. let newSpaceCnt = indentUtils.getSpaceCnt(indentOfFirstLine, tabSize);
  415. let oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
  416. if (newSpaceCnt !== oldSpaceCnt) {
  417. let newIndent = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
  418. textEdits.push({
  419. range: new Range(startLineNumber, 1, startLineNumber, oldIndentation.length + 1),
  420. text: newIndent
  421. });
  422. firstLineText = newIndent + firstLineText.substr(oldIndentation.length);
  423. }
  424. else {
  425. let indentMetadata = LanguageConfigurationRegistry.getIndentMetadata(model, startLineNumber);
  426. if (indentMetadata === 0 || indentMetadata === 8 /* UNINDENT_MASK */) {
  427. // we paste content into a line where only contains whitespaces
  428. // after pasting, the indentation of the first line is already correct
  429. // the first line doesn't match any indentation rule
  430. // then no-op.
  431. return;
  432. }
  433. }
  434. }
  435. }
  436. const firstLineNumber = startLineNumber;
  437. // ignore empty or ignored lines
  438. while (startLineNumber < range.endLineNumber) {
  439. if (!/\S/.test(model.getLineContent(startLineNumber + 1))) {
  440. startLineNumber++;
  441. continue;
  442. }
  443. break;
  444. }
  445. if (startLineNumber !== range.endLineNumber) {
  446. let virtualModel = {
  447. getLineTokens: (lineNumber) => {
  448. return model.getLineTokens(lineNumber);
  449. },
  450. getLanguageId: () => {
  451. return model.getLanguageId();
  452. },
  453. getLanguageIdAtPosition: (lineNumber, column) => {
  454. return model.getLanguageIdAtPosition(lineNumber, column);
  455. },
  456. getLineContent: (lineNumber) => {
  457. if (lineNumber === firstLineNumber) {
  458. return firstLineText;
  459. }
  460. else {
  461. return model.getLineContent(lineNumber);
  462. }
  463. }
  464. };
  465. let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, virtualModel, model.getLanguageId(), startLineNumber + 1, indentConverter);
  466. if (indentOfSecondLine !== null) {
  467. let newSpaceCntOfSecondLine = indentUtils.getSpaceCnt(indentOfSecondLine, tabSize);
  468. let oldSpaceCntOfSecondLine = indentUtils.getSpaceCnt(strings.getLeadingWhitespace(model.getLineContent(startLineNumber + 1)), tabSize);
  469. if (newSpaceCntOfSecondLine !== oldSpaceCntOfSecondLine) {
  470. let spaceCntOffset = newSpaceCntOfSecondLine - oldSpaceCntOfSecondLine;
  471. for (let i = startLineNumber + 1; i <= range.endLineNumber; i++) {
  472. let lineContent = model.getLineContent(i);
  473. let originalIndent = strings.getLeadingWhitespace(lineContent);
  474. let originalSpacesCnt = indentUtils.getSpaceCnt(originalIndent, tabSize);
  475. let newSpacesCnt = originalSpacesCnt + spaceCntOffset;
  476. let newIndent = indentUtils.generateIndent(newSpacesCnt, tabSize, insertSpaces);
  477. if (newIndent !== originalIndent) {
  478. textEdits.push({
  479. range: new Range(i, 1, i, originalIndent.length + 1),
  480. text: newIndent
  481. });
  482. }
  483. }
  484. }
  485. }
  486. }
  487. if (textEdits.length > 0) {
  488. this.editor.pushUndoStop();
  489. let cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection());
  490. this.editor.executeCommand('autoIndentOnPaste', cmd);
  491. this.editor.pushUndoStop();
  492. }
  493. }
  494. shouldIgnoreLine(model, lineNumber) {
  495. model.forceTokenization(lineNumber);
  496. let nonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
  497. if (nonWhitespaceColumn === 0) {
  498. return true;
  499. }
  500. let tokens = model.getLineTokens(lineNumber);
  501. if (tokens.getCount() > 0) {
  502. let firstNonWhitespaceTokenIndex = tokens.findTokenIndexAtOffset(nonWhitespaceColumn);
  503. if (firstNonWhitespaceTokenIndex >= 0 && tokens.getStandardTokenType(firstNonWhitespaceTokenIndex) === 1 /* Comment */) {
  504. return true;
  505. }
  506. }
  507. return false;
  508. }
  509. dispose() {
  510. this.callOnDispose.dispose();
  511. this.callOnModel.dispose();
  512. }
  513. }
  514. AutoIndentOnPaste.ID = 'editor.contrib.autoIndentOnPaste';
  515. function getIndentationEditOperations(model, builder, tabSize, tabsToSpaces) {
  516. if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
  517. // Model is empty
  518. return;
  519. }
  520. let spaces = '';
  521. for (let i = 0; i < tabSize; i++) {
  522. spaces += ' ';
  523. }
  524. let spacesRegExp = new RegExp(spaces, 'gi');
  525. for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) {
  526. let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
  527. if (lastIndentationColumn === 0) {
  528. lastIndentationColumn = model.getLineMaxColumn(lineNumber);
  529. }
  530. if (lastIndentationColumn === 1) {
  531. continue;
  532. }
  533. const originalIndentationRange = new Range(lineNumber, 1, lineNumber, lastIndentationColumn);
  534. const originalIndentation = model.getValueInRange(originalIndentationRange);
  535. const newIndentation = (tabsToSpaces
  536. ? originalIndentation.replace(/\t/ig, spaces)
  537. : originalIndentation.replace(spacesRegExp, '\t'));
  538. builder.addEditOperation(originalIndentationRange, newIndentation);
  539. }
  540. }
  541. export class IndentationToSpacesCommand {
  542. constructor(selection, tabSize) {
  543. this.selection = selection;
  544. this.tabSize = tabSize;
  545. this.selectionId = null;
  546. }
  547. getEditOperations(model, builder) {
  548. this.selectionId = builder.trackSelection(this.selection);
  549. getIndentationEditOperations(model, builder, this.tabSize, true);
  550. }
  551. computeCursorState(model, helper) {
  552. return helper.getTrackedSelection(this.selectionId);
  553. }
  554. }
  555. export class IndentationToTabsCommand {
  556. constructor(selection, tabSize) {
  557. this.selection = selection;
  558. this.tabSize = tabSize;
  559. this.selectionId = null;
  560. }
  561. getEditOperations(model, builder) {
  562. this.selectionId = builder.trackSelection(this.selection);
  563. getIndentationEditOperations(model, builder, this.tabSize, false);
  564. }
  565. computeCursorState(model, helper) {
  566. return helper.getTrackedSelection(this.selectionId);
  567. }
  568. }
  569. registerEditorContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
  570. registerEditorAction(IndentationToSpacesAction);
  571. registerEditorAction(IndentationToTabsAction);
  572. registerEditorAction(IndentUsingTabs);
  573. registerEditorAction(IndentUsingSpaces);
  574. registerEditorAction(DetectIndentation);
  575. registerEditorAction(ReindentLinesAction);
  576. registerEditorAction(ReindentSelectedLinesAction);