editStack.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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 * as nls from '../../../nls.js';
  6. import { onUnexpectedError } from '../../../base/common/errors.js';
  7. import { Selection } from '../core/selection.js';
  8. import { URI } from '../../../base/common/uri.js';
  9. import { TextChange, compressConsecutiveTextChanges } from './textChange.js';
  10. import * as buffer from '../../../base/common/buffer.js';
  11. import { basename } from '../../../base/common/resources.js';
  12. function uriGetComparisonKey(resource) {
  13. return resource.toString();
  14. }
  15. export class SingleModelEditStackData {
  16. constructor(beforeVersionId, afterVersionId, beforeEOL, afterEOL, beforeCursorState, afterCursorState, changes) {
  17. this.beforeVersionId = beforeVersionId;
  18. this.afterVersionId = afterVersionId;
  19. this.beforeEOL = beforeEOL;
  20. this.afterEOL = afterEOL;
  21. this.beforeCursorState = beforeCursorState;
  22. this.afterCursorState = afterCursorState;
  23. this.changes = changes;
  24. }
  25. static create(model, beforeCursorState) {
  26. const alternativeVersionId = model.getAlternativeVersionId();
  27. const eol = getModelEOL(model);
  28. return new SingleModelEditStackData(alternativeVersionId, alternativeVersionId, eol, eol, beforeCursorState, beforeCursorState, []);
  29. }
  30. append(model, textChanges, afterEOL, afterVersionId, afterCursorState) {
  31. if (textChanges.length > 0) {
  32. this.changes = compressConsecutiveTextChanges(this.changes, textChanges);
  33. }
  34. this.afterEOL = afterEOL;
  35. this.afterVersionId = afterVersionId;
  36. this.afterCursorState = afterCursorState;
  37. }
  38. static _writeSelectionsSize(selections) {
  39. return 4 + 4 * 4 * (selections ? selections.length : 0);
  40. }
  41. static _writeSelections(b, selections, offset) {
  42. buffer.writeUInt32BE(b, (selections ? selections.length : 0), offset);
  43. offset += 4;
  44. if (selections) {
  45. for (const selection of selections) {
  46. buffer.writeUInt32BE(b, selection.selectionStartLineNumber, offset);
  47. offset += 4;
  48. buffer.writeUInt32BE(b, selection.selectionStartColumn, offset);
  49. offset += 4;
  50. buffer.writeUInt32BE(b, selection.positionLineNumber, offset);
  51. offset += 4;
  52. buffer.writeUInt32BE(b, selection.positionColumn, offset);
  53. offset += 4;
  54. }
  55. }
  56. return offset;
  57. }
  58. static _readSelections(b, offset, dest) {
  59. const count = buffer.readUInt32BE(b, offset);
  60. offset += 4;
  61. for (let i = 0; i < count; i++) {
  62. const selectionStartLineNumber = buffer.readUInt32BE(b, offset);
  63. offset += 4;
  64. const selectionStartColumn = buffer.readUInt32BE(b, offset);
  65. offset += 4;
  66. const positionLineNumber = buffer.readUInt32BE(b, offset);
  67. offset += 4;
  68. const positionColumn = buffer.readUInt32BE(b, offset);
  69. offset += 4;
  70. dest.push(new Selection(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn));
  71. }
  72. return offset;
  73. }
  74. serialize() {
  75. let necessarySize = (+4 // beforeVersionId
  76. + 4 // afterVersionId
  77. + 1 // beforeEOL
  78. + 1 // afterEOL
  79. + SingleModelEditStackData._writeSelectionsSize(this.beforeCursorState)
  80. + SingleModelEditStackData._writeSelectionsSize(this.afterCursorState)
  81. + 4 // change count
  82. );
  83. for (const change of this.changes) {
  84. necessarySize += change.writeSize();
  85. }
  86. const b = new Uint8Array(necessarySize);
  87. let offset = 0;
  88. buffer.writeUInt32BE(b, this.beforeVersionId, offset);
  89. offset += 4;
  90. buffer.writeUInt32BE(b, this.afterVersionId, offset);
  91. offset += 4;
  92. buffer.writeUInt8(b, this.beforeEOL, offset);
  93. offset += 1;
  94. buffer.writeUInt8(b, this.afterEOL, offset);
  95. offset += 1;
  96. offset = SingleModelEditStackData._writeSelections(b, this.beforeCursorState, offset);
  97. offset = SingleModelEditStackData._writeSelections(b, this.afterCursorState, offset);
  98. buffer.writeUInt32BE(b, this.changes.length, offset);
  99. offset += 4;
  100. for (const change of this.changes) {
  101. offset = change.write(b, offset);
  102. }
  103. return b.buffer;
  104. }
  105. static deserialize(source) {
  106. const b = new Uint8Array(source);
  107. let offset = 0;
  108. const beforeVersionId = buffer.readUInt32BE(b, offset);
  109. offset += 4;
  110. const afterVersionId = buffer.readUInt32BE(b, offset);
  111. offset += 4;
  112. const beforeEOL = buffer.readUInt8(b, offset);
  113. offset += 1;
  114. const afterEOL = buffer.readUInt8(b, offset);
  115. offset += 1;
  116. const beforeCursorState = [];
  117. offset = SingleModelEditStackData._readSelections(b, offset, beforeCursorState);
  118. const afterCursorState = [];
  119. offset = SingleModelEditStackData._readSelections(b, offset, afterCursorState);
  120. const changeCount = buffer.readUInt32BE(b, offset);
  121. offset += 4;
  122. const changes = [];
  123. for (let i = 0; i < changeCount; i++) {
  124. offset = TextChange.read(b, offset, changes);
  125. }
  126. return new SingleModelEditStackData(beforeVersionId, afterVersionId, beforeEOL, afterEOL, beforeCursorState, afterCursorState, changes);
  127. }
  128. }
  129. export class SingleModelEditStackElement {
  130. constructor(model, beforeCursorState) {
  131. this.model = model;
  132. this._data = SingleModelEditStackData.create(model, beforeCursorState);
  133. }
  134. get type() {
  135. return 0 /* Resource */;
  136. }
  137. get resource() {
  138. if (URI.isUri(this.model)) {
  139. return this.model;
  140. }
  141. return this.model.uri;
  142. }
  143. get label() {
  144. return nls.localize('edit', "Typing");
  145. }
  146. toString() {
  147. const data = (this._data instanceof SingleModelEditStackData ? this._data : SingleModelEditStackData.deserialize(this._data));
  148. return data.changes.map(change => change.toString()).join(', ');
  149. }
  150. matchesResource(resource) {
  151. const uri = (URI.isUri(this.model) ? this.model : this.model.uri);
  152. return (uri.toString() === resource.toString());
  153. }
  154. setModel(model) {
  155. this.model = model;
  156. }
  157. canAppend(model) {
  158. return (this.model === model && this._data instanceof SingleModelEditStackData);
  159. }
  160. append(model, textChanges, afterEOL, afterVersionId, afterCursorState) {
  161. if (this._data instanceof SingleModelEditStackData) {
  162. this._data.append(model, textChanges, afterEOL, afterVersionId, afterCursorState);
  163. }
  164. }
  165. close() {
  166. if (this._data instanceof SingleModelEditStackData) {
  167. this._data = this._data.serialize();
  168. }
  169. }
  170. open() {
  171. if (!(this._data instanceof SingleModelEditStackData)) {
  172. this._data = SingleModelEditStackData.deserialize(this._data);
  173. }
  174. }
  175. undo() {
  176. if (URI.isUri(this.model)) {
  177. // don't have a model
  178. throw new Error(`Invalid SingleModelEditStackElement`);
  179. }
  180. if (this._data instanceof SingleModelEditStackData) {
  181. this._data = this._data.serialize();
  182. }
  183. const data = SingleModelEditStackData.deserialize(this._data);
  184. this.model._applyUndo(data.changes, data.beforeEOL, data.beforeVersionId, data.beforeCursorState);
  185. }
  186. redo() {
  187. if (URI.isUri(this.model)) {
  188. // don't have a model
  189. throw new Error(`Invalid SingleModelEditStackElement`);
  190. }
  191. if (this._data instanceof SingleModelEditStackData) {
  192. this._data = this._data.serialize();
  193. }
  194. const data = SingleModelEditStackData.deserialize(this._data);
  195. this.model._applyRedo(data.changes, data.afterEOL, data.afterVersionId, data.afterCursorState);
  196. }
  197. heapSize() {
  198. if (this._data instanceof SingleModelEditStackData) {
  199. this._data = this._data.serialize();
  200. }
  201. return this._data.byteLength + 168 /*heap overhead*/;
  202. }
  203. }
  204. export class MultiModelEditStackElement {
  205. constructor(label, editStackElements) {
  206. this.type = 1 /* Workspace */;
  207. this.label = label;
  208. this._isOpen = true;
  209. this._editStackElementsArr = editStackElements.slice(0);
  210. this._editStackElementsMap = new Map();
  211. for (const editStackElement of this._editStackElementsArr) {
  212. const key = uriGetComparisonKey(editStackElement.resource);
  213. this._editStackElementsMap.set(key, editStackElement);
  214. }
  215. this._delegate = null;
  216. }
  217. get resources() {
  218. return this._editStackElementsArr.map(editStackElement => editStackElement.resource);
  219. }
  220. prepareUndoRedo() {
  221. if (this._delegate) {
  222. return this._delegate.prepareUndoRedo(this);
  223. }
  224. }
  225. matchesResource(resource) {
  226. const key = uriGetComparisonKey(resource);
  227. return (this._editStackElementsMap.has(key));
  228. }
  229. setModel(model) {
  230. const key = uriGetComparisonKey(URI.isUri(model) ? model : model.uri);
  231. if (this._editStackElementsMap.has(key)) {
  232. this._editStackElementsMap.get(key).setModel(model);
  233. }
  234. }
  235. canAppend(model) {
  236. if (!this._isOpen) {
  237. return false;
  238. }
  239. const key = uriGetComparisonKey(model.uri);
  240. if (this._editStackElementsMap.has(key)) {
  241. const editStackElement = this._editStackElementsMap.get(key);
  242. return editStackElement.canAppend(model);
  243. }
  244. return false;
  245. }
  246. append(model, textChanges, afterEOL, afterVersionId, afterCursorState) {
  247. const key = uriGetComparisonKey(model.uri);
  248. const editStackElement = this._editStackElementsMap.get(key);
  249. editStackElement.append(model, textChanges, afterEOL, afterVersionId, afterCursorState);
  250. }
  251. close() {
  252. this._isOpen = false;
  253. }
  254. open() {
  255. // cannot reopen
  256. }
  257. undo() {
  258. this._isOpen = false;
  259. for (const editStackElement of this._editStackElementsArr) {
  260. editStackElement.undo();
  261. }
  262. }
  263. redo() {
  264. for (const editStackElement of this._editStackElementsArr) {
  265. editStackElement.redo();
  266. }
  267. }
  268. heapSize(resource) {
  269. const key = uriGetComparisonKey(resource);
  270. if (this._editStackElementsMap.has(key)) {
  271. const editStackElement = this._editStackElementsMap.get(key);
  272. return editStackElement.heapSize();
  273. }
  274. return 0;
  275. }
  276. split() {
  277. return this._editStackElementsArr;
  278. }
  279. toString() {
  280. let result = [];
  281. for (const editStackElement of this._editStackElementsArr) {
  282. result.push(`${basename(editStackElement.resource)}: ${editStackElement}`);
  283. }
  284. return `{${result.join(', ')}}`;
  285. }
  286. }
  287. function getModelEOL(model) {
  288. const eol = model.getEOL();
  289. if (eol === '\n') {
  290. return 0 /* LF */;
  291. }
  292. else {
  293. return 1 /* CRLF */;
  294. }
  295. }
  296. export function isEditStackElement(element) {
  297. if (!element) {
  298. return false;
  299. }
  300. return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement));
  301. }
  302. export class EditStack {
  303. constructor(model, undoRedoService) {
  304. this._model = model;
  305. this._undoRedoService = undoRedoService;
  306. }
  307. pushStackElement() {
  308. const lastElement = this._undoRedoService.getLastElement(this._model.uri);
  309. if (isEditStackElement(lastElement)) {
  310. lastElement.close();
  311. }
  312. }
  313. popStackElement() {
  314. const lastElement = this._undoRedoService.getLastElement(this._model.uri);
  315. if (isEditStackElement(lastElement)) {
  316. lastElement.open();
  317. }
  318. }
  319. clear() {
  320. this._undoRedoService.removeElements(this._model.uri);
  321. }
  322. _getOrCreateEditStackElement(beforeCursorState) {
  323. const lastElement = this._undoRedoService.getLastElement(this._model.uri);
  324. if (isEditStackElement(lastElement) && lastElement.canAppend(this._model)) {
  325. return lastElement;
  326. }
  327. const newElement = new SingleModelEditStackElement(this._model, beforeCursorState);
  328. this._undoRedoService.pushElement(newElement);
  329. return newElement;
  330. }
  331. pushEOL(eol) {
  332. const editStackElement = this._getOrCreateEditStackElement(null);
  333. this._model.setEOL(eol);
  334. editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null);
  335. }
  336. pushEditOperation(beforeCursorState, editOperations, cursorStateComputer) {
  337. const editStackElement = this._getOrCreateEditStackElement(beforeCursorState);
  338. const inverseEditOperations = this._model.applyEdits(editOperations, true);
  339. const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
  340. const textChanges = inverseEditOperations.map((op, index) => ({ index: index, textChange: op.textChange }));
  341. textChanges.sort((a, b) => {
  342. if (a.textChange.oldPosition === b.textChange.oldPosition) {
  343. return a.index - b.index;
  344. }
  345. return a.textChange.oldPosition - b.textChange.oldPosition;
  346. });
  347. editStackElement.append(this._model, textChanges.map(op => op.textChange), getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState);
  348. return afterCursorState;
  349. }
  350. static _computeCursorState(cursorStateComputer, inverseEditOperations) {
  351. try {
  352. return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null;
  353. }
  354. catch (e) {
  355. onUnexpectedError(e);
  356. return null;
  357. }
  358. }
  359. }