fe33f425ec4ac0f3bfaa32b867199b807987cb4ffc062c31be358088388b019b74452b301923dfe26b49c54fc785f443cb7a1ee6ec35fb2b60b31ef1d3b4f9 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. import * as strings from '../../../../base/common/strings.js';
  15. import { ShiftCommand } from '../../../common/commands/shiftCommand.js';
  16. import { Range } from '../../../common/core/range.js';
  17. import { Selection } from '../../../common/core/selection.js';
  18. import { IndentAction } from '../../../common/languages/languageConfiguration.js';
  19. import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
  20. import * as indentUtils from '../../indentation/browser/indentUtils.js';
  21. import { getGoodIndentForLine, getIndentMetadata } from '../../../common/languages/autoIndent.js';
  22. import { getEnterAction } from '../../../common/languages/enterAction.js';
  23. let MoveLinesCommand = class MoveLinesCommand {
  24. constructor(selection, isMovingDown, autoIndent, _languageConfigurationService) {
  25. this._languageConfigurationService = _languageConfigurationService;
  26. this._selection = selection;
  27. this._isMovingDown = isMovingDown;
  28. this._autoIndent = autoIndent;
  29. this._selectionId = null;
  30. this._moveEndLineSelectionShrink = false;
  31. }
  32. getEditOperations(model, builder) {
  33. const modelLineCount = model.getLineCount();
  34. if (this._isMovingDown && this._selection.endLineNumber === modelLineCount) {
  35. this._selectionId = builder.trackSelection(this._selection);
  36. return;
  37. }
  38. if (!this._isMovingDown && this._selection.startLineNumber === 1) {
  39. this._selectionId = builder.trackSelection(this._selection);
  40. return;
  41. }
  42. this._moveEndPositionDown = false;
  43. let s = this._selection;
  44. if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) {
  45. this._moveEndPositionDown = true;
  46. s = s.setEndPosition(s.endLineNumber - 1, model.getLineMaxColumn(s.endLineNumber - 1));
  47. }
  48. const { tabSize, indentSize, insertSpaces } = model.getOptions();
  49. const indentConverter = this.buildIndentConverter(tabSize, indentSize, insertSpaces);
  50. const virtualModel = {
  51. tokenization: {
  52. getLineTokens: (lineNumber) => {
  53. return model.tokenization.getLineTokens(lineNumber);
  54. },
  55. getLanguageId: () => {
  56. return model.getLanguageId();
  57. },
  58. getLanguageIdAtPosition: (lineNumber, column) => {
  59. return model.getLanguageIdAtPosition(lineNumber, column);
  60. },
  61. },
  62. getLineContent: null,
  63. };
  64. if (s.startLineNumber === s.endLineNumber && model.getLineMaxColumn(s.startLineNumber) === 1) {
  65. // Current line is empty
  66. const lineNumber = s.startLineNumber;
  67. const otherLineNumber = (this._isMovingDown ? lineNumber + 1 : lineNumber - 1);
  68. if (model.getLineMaxColumn(otherLineNumber) === 1) {
  69. // Other line number is empty too, so no editing is needed
  70. // Add a no-op to force running by the model
  71. builder.addEditOperation(new Range(1, 1, 1, 1), null);
  72. }
  73. else {
  74. // Type content from other line number on line number
  75. builder.addEditOperation(new Range(lineNumber, 1, lineNumber, 1), model.getLineContent(otherLineNumber));
  76. // Remove content from other line number
  77. builder.addEditOperation(new Range(otherLineNumber, 1, otherLineNumber, model.getLineMaxColumn(otherLineNumber)), null);
  78. }
  79. // Track selection at the other line number
  80. s = new Selection(otherLineNumber, 1, otherLineNumber, 1);
  81. }
  82. else {
  83. let movingLineNumber;
  84. let movingLineText;
  85. if (this._isMovingDown) {
  86. movingLineNumber = s.endLineNumber + 1;
  87. movingLineText = model.getLineContent(movingLineNumber);
  88. // Delete line that needs to be moved
  89. builder.addEditOperation(new Range(movingLineNumber - 1, model.getLineMaxColumn(movingLineNumber - 1), movingLineNumber, model.getLineMaxColumn(movingLineNumber)), null);
  90. let insertingText = movingLineText;
  91. if (this.shouldAutoIndent(model, s)) {
  92. const movingLineMatchResult = this.matchEnterRule(model, indentConverter, tabSize, movingLineNumber, s.startLineNumber - 1);
  93. // if s.startLineNumber - 1 matches onEnter rule, we still honor that.
  94. if (movingLineMatchResult !== null) {
  95. const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber));
  96. const newSpaceCnt = movingLineMatchResult + indentUtils.getSpaceCnt(oldIndentation, tabSize);
  97. const newIndentation = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
  98. insertingText = newIndentation + this.trimLeft(movingLineText);
  99. }
  100. else {
  101. // no enter rule matches, let's check indentatin rules then.
  102. virtualModel.getLineContent = (lineNumber) => {
  103. if (lineNumber === s.startLineNumber) {
  104. return model.getLineContent(movingLineNumber);
  105. }
  106. else {
  107. return model.getLineContent(lineNumber);
  108. }
  109. };
  110. const indentOfMovingLine = getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(movingLineNumber, 1), s.startLineNumber, indentConverter, this._languageConfigurationService);
  111. if (indentOfMovingLine !== null) {
  112. const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber));
  113. const newSpaceCnt = indentUtils.getSpaceCnt(indentOfMovingLine, tabSize);
  114. const oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
  115. if (newSpaceCnt !== oldSpaceCnt) {
  116. const newIndentation = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
  117. insertingText = newIndentation + this.trimLeft(movingLineText);
  118. }
  119. }
  120. }
  121. // add edit operations for moving line first to make sure it's executed after we make indentation change
  122. // to s.startLineNumber
  123. builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), insertingText + '\n');
  124. const ret = this.matchEnterRuleMovingDown(model, indentConverter, tabSize, s.startLineNumber, movingLineNumber, insertingText);
  125. // check if the line being moved before matches onEnter rules, if so let's adjust the indentation by onEnter rules.
  126. if (ret !== null) {
  127. if (ret !== 0) {
  128. this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, ret);
  129. }
  130. }
  131. else {
  132. // it doesn't match onEnter rules, let's check indentation rules then.
  133. virtualModel.getLineContent = (lineNumber) => {
  134. if (lineNumber === s.startLineNumber) {
  135. return insertingText;
  136. }
  137. else if (lineNumber >= s.startLineNumber + 1 && lineNumber <= s.endLineNumber + 1) {
  138. return model.getLineContent(lineNumber - 1);
  139. }
  140. else {
  141. return model.getLineContent(lineNumber);
  142. }
  143. };
  144. const newIndentatOfMovingBlock = getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(movingLineNumber, 1), s.startLineNumber + 1, indentConverter, this._languageConfigurationService);
  145. if (newIndentatOfMovingBlock !== null) {
  146. const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber));
  147. const newSpaceCnt = indentUtils.getSpaceCnt(newIndentatOfMovingBlock, tabSize);
  148. const oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
  149. if (newSpaceCnt !== oldSpaceCnt) {
  150. const spaceCntOffset = newSpaceCnt - oldSpaceCnt;
  151. this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, spaceCntOffset);
  152. }
  153. }
  154. }
  155. }
  156. else {
  157. // Insert line that needs to be moved before
  158. builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), insertingText + '\n');
  159. }
  160. }
  161. else {
  162. movingLineNumber = s.startLineNumber - 1;
  163. movingLineText = model.getLineContent(movingLineNumber);
  164. // Delete line that needs to be moved
  165. builder.addEditOperation(new Range(movingLineNumber, 1, movingLineNumber + 1, 1), null);
  166. // Insert line that needs to be moved after
  167. builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber, model.getLineMaxColumn(s.endLineNumber)), '\n' + movingLineText);
  168. if (this.shouldAutoIndent(model, s)) {
  169. virtualModel.getLineContent = (lineNumber) => {
  170. if (lineNumber === movingLineNumber) {
  171. return model.getLineContent(s.startLineNumber);
  172. }
  173. else {
  174. return model.getLineContent(lineNumber);
  175. }
  176. };
  177. const ret = this.matchEnterRule(model, indentConverter, tabSize, s.startLineNumber, s.startLineNumber - 2);
  178. // check if s.startLineNumber - 2 matches onEnter rules, if so adjust the moving block by onEnter rules.
  179. if (ret !== null) {
  180. if (ret !== 0) {
  181. this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, ret);
  182. }
  183. }
  184. else {
  185. // it doesn't match any onEnter rule, let's check indentation rules then.
  186. const indentOfFirstLine = getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter, this._languageConfigurationService);
  187. if (indentOfFirstLine !== null) {
  188. // adjust the indentation of the moving block
  189. const oldIndent = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber));
  190. const newSpaceCnt = indentUtils.getSpaceCnt(indentOfFirstLine, tabSize);
  191. const oldSpaceCnt = indentUtils.getSpaceCnt(oldIndent, tabSize);
  192. if (newSpaceCnt !== oldSpaceCnt) {
  193. const spaceCntOffset = newSpaceCnt - oldSpaceCnt;
  194. this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, spaceCntOffset);
  195. }
  196. }
  197. }
  198. }
  199. }
  200. }
  201. this._selectionId = builder.trackSelection(s);
  202. }
  203. buildIndentConverter(tabSize, indentSize, insertSpaces) {
  204. return {
  205. shiftIndent: (indentation) => {
  206. return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
  207. },
  208. unshiftIndent: (indentation) => {
  209. return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
  210. }
  211. };
  212. }
  213. parseEnterResult(model, indentConverter, tabSize, line, enter) {
  214. if (enter) {
  215. let enterPrefix = enter.indentation;
  216. if (enter.indentAction === IndentAction.None) {
  217. enterPrefix = enter.indentation + enter.appendText;
  218. }
  219. else if (enter.indentAction === IndentAction.Indent) {
  220. enterPrefix = enter.indentation + enter.appendText;
  221. }
  222. else if (enter.indentAction === IndentAction.IndentOutdent) {
  223. enterPrefix = enter.indentation;
  224. }
  225. else if (enter.indentAction === IndentAction.Outdent) {
  226. enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enter.appendText;
  227. }
  228. const movingLineText = model.getLineContent(line);
  229. if (this.trimLeft(movingLineText).indexOf(this.trimLeft(enterPrefix)) >= 0) {
  230. const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(line));
  231. let newIndentation = strings.getLeadingWhitespace(enterPrefix);
  232. const indentMetadataOfMovelingLine = getIndentMetadata(model, line, this._languageConfigurationService);
  233. if (indentMetadataOfMovelingLine !== null && indentMetadataOfMovelingLine & 2 /* IndentConsts.DECREASE_MASK */) {
  234. newIndentation = indentConverter.unshiftIndent(newIndentation);
  235. }
  236. const newSpaceCnt = indentUtils.getSpaceCnt(newIndentation, tabSize);
  237. const oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
  238. return newSpaceCnt - oldSpaceCnt;
  239. }
  240. }
  241. return null;
  242. }
  243. /**
  244. *
  245. * @param model
  246. * @param indentConverter
  247. * @param tabSize
  248. * @param line the line moving down
  249. * @param futureAboveLineNumber the line which will be at the `line` position
  250. * @param futureAboveLineText
  251. */
  252. matchEnterRuleMovingDown(model, indentConverter, tabSize, line, futureAboveLineNumber, futureAboveLineText) {
  253. if (strings.lastNonWhitespaceIndex(futureAboveLineText) >= 0) {
  254. // break
  255. const maxColumn = model.getLineMaxColumn(futureAboveLineNumber);
  256. const enter = getEnterAction(this._autoIndent, model, new Range(futureAboveLineNumber, maxColumn, futureAboveLineNumber, maxColumn), this._languageConfigurationService);
  257. return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
  258. }
  259. else {
  260. // go upwards, starting from `line - 1`
  261. let validPrecedingLine = line - 1;
  262. while (validPrecedingLine >= 1) {
  263. const lineContent = model.getLineContent(validPrecedingLine);
  264. const nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent);
  265. if (nonWhitespaceIdx >= 0) {
  266. break;
  267. }
  268. validPrecedingLine--;
  269. }
  270. if (validPrecedingLine < 1 || line > model.getLineCount()) {
  271. return null;
  272. }
  273. const maxColumn = model.getLineMaxColumn(validPrecedingLine);
  274. const enter = getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn), this._languageConfigurationService);
  275. return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
  276. }
  277. }
  278. matchEnterRule(model, indentConverter, tabSize, line, oneLineAbove, previousLineText) {
  279. let validPrecedingLine = oneLineAbove;
  280. while (validPrecedingLine >= 1) {
  281. // ship empty lines as empty lines just inherit indentation
  282. let lineContent;
  283. if (validPrecedingLine === oneLineAbove && previousLineText !== undefined) {
  284. lineContent = previousLineText;
  285. }
  286. else {
  287. lineContent = model.getLineContent(validPrecedingLine);
  288. }
  289. const nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent);
  290. if (nonWhitespaceIdx >= 0) {
  291. break;
  292. }
  293. validPrecedingLine--;
  294. }
  295. if (validPrecedingLine < 1 || line > model.getLineCount()) {
  296. return null;
  297. }
  298. const maxColumn = model.getLineMaxColumn(validPrecedingLine);
  299. const enter = getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn), this._languageConfigurationService);
  300. return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
  301. }
  302. trimLeft(str) {
  303. return str.replace(/^\s+/, '');
  304. }
  305. shouldAutoIndent(model, selection) {
  306. if (this._autoIndent < 4 /* EditorAutoIndentStrategy.Full */) {
  307. return false;
  308. }
  309. // if it's not easy to tokenize, we stop auto indent.
  310. if (!model.tokenization.isCheapToTokenize(selection.startLineNumber)) {
  311. return false;
  312. }
  313. const languageAtSelectionStart = model.getLanguageIdAtPosition(selection.startLineNumber, 1);
  314. const languageAtSelectionEnd = model.getLanguageIdAtPosition(selection.endLineNumber, 1);
  315. if (languageAtSelectionStart !== languageAtSelectionEnd) {
  316. return false;
  317. }
  318. if (this._languageConfigurationService.getLanguageConfiguration(languageAtSelectionStart).indentRulesSupport === null) {
  319. return false;
  320. }
  321. return true;
  322. }
  323. getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, offset) {
  324. for (let i = s.startLineNumber; i <= s.endLineNumber; i++) {
  325. const lineContent = model.getLineContent(i);
  326. const originalIndent = strings.getLeadingWhitespace(lineContent);
  327. const originalSpacesCnt = indentUtils.getSpaceCnt(originalIndent, tabSize);
  328. const newSpacesCnt = originalSpacesCnt + offset;
  329. const newIndent = indentUtils.generateIndent(newSpacesCnt, tabSize, insertSpaces);
  330. if (newIndent !== originalIndent) {
  331. builder.addEditOperation(new Range(i, 1, i, originalIndent.length + 1), newIndent);
  332. if (i === s.endLineNumber && s.endColumn <= originalIndent.length + 1 && newIndent === '') {
  333. // as users select part of the original indent white spaces
  334. // when we adjust the indentation of endLine, we should adjust the cursor position as well.
  335. this._moveEndLineSelectionShrink = true;
  336. }
  337. }
  338. }
  339. }
  340. computeCursorState(model, helper) {
  341. let result = helper.getTrackedSelection(this._selectionId);
  342. if (this._moveEndPositionDown) {
  343. result = result.setEndPosition(result.endLineNumber + 1, 1);
  344. }
  345. if (this._moveEndLineSelectionShrink && result.startLineNumber < result.endLineNumber) {
  346. result = result.setEndPosition(result.endLineNumber, 2);
  347. }
  348. return result;
  349. }
  350. };
  351. MoveLinesCommand = __decorate([
  352. __param(3, ILanguageConfigurationService)
  353. ], MoveLinesCommand);
  354. export { MoveLinesCommand };