b42beeed35267a341efa93282fba6904d9a560afaf9ae1803f332046127baefe60be6ef32696535b8acd21cd31a6e290e54bd9eadb1e34031825d7a8c78236 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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 { Position } from '../core/position.js';
  6. import { Range } from '../core/range.js';
  7. import { countEOL } from '../core/eolCounter.js';
  8. /**
  9. * Represents sparse tokens over a contiguous range of lines.
  10. */
  11. export class SparseMultilineTokens {
  12. constructor(startLineNumber, tokens) {
  13. this._startLineNumber = startLineNumber;
  14. this._tokens = tokens;
  15. this._endLineNumber = this._startLineNumber + this._tokens.getMaxDeltaLine();
  16. }
  17. static create(startLineNumber, tokens) {
  18. return new SparseMultilineTokens(startLineNumber, new SparseMultilineTokensStorage(tokens));
  19. }
  20. /**
  21. * (Inclusive) start line number for these tokens.
  22. */
  23. get startLineNumber() {
  24. return this._startLineNumber;
  25. }
  26. /**
  27. * (Inclusive) end line number for these tokens.
  28. */
  29. get endLineNumber() {
  30. return this._endLineNumber;
  31. }
  32. toString() {
  33. return this._tokens.toString(this._startLineNumber);
  34. }
  35. _updateEndLineNumber() {
  36. this._endLineNumber = this._startLineNumber + this._tokens.getMaxDeltaLine();
  37. }
  38. isEmpty() {
  39. return this._tokens.isEmpty();
  40. }
  41. getLineTokens(lineNumber) {
  42. if (this._startLineNumber <= lineNumber && lineNumber <= this._endLineNumber) {
  43. return this._tokens.getLineTokens(lineNumber - this._startLineNumber);
  44. }
  45. return null;
  46. }
  47. getRange() {
  48. const deltaRange = this._tokens.getRange();
  49. if (!deltaRange) {
  50. return deltaRange;
  51. }
  52. return new Range(this._startLineNumber + deltaRange.startLineNumber, deltaRange.startColumn, this._startLineNumber + deltaRange.endLineNumber, deltaRange.endColumn);
  53. }
  54. removeTokens(range) {
  55. const startLineIndex = range.startLineNumber - this._startLineNumber;
  56. const endLineIndex = range.endLineNumber - this._startLineNumber;
  57. this._startLineNumber += this._tokens.removeTokens(startLineIndex, range.startColumn - 1, endLineIndex, range.endColumn - 1);
  58. this._updateEndLineNumber();
  59. }
  60. split(range) {
  61. // split tokens to two:
  62. // a) all the tokens before `range`
  63. // b) all the tokens after `range`
  64. const startLineIndex = range.startLineNumber - this._startLineNumber;
  65. const endLineIndex = range.endLineNumber - this._startLineNumber;
  66. const [a, b, bDeltaLine] = this._tokens.split(startLineIndex, range.startColumn - 1, endLineIndex, range.endColumn - 1);
  67. return [new SparseMultilineTokens(this._startLineNumber, a), new SparseMultilineTokens(this._startLineNumber + bDeltaLine, b)];
  68. }
  69. applyEdit(range, text) {
  70. const [eolCount, firstLineLength, lastLineLength] = countEOL(text);
  71. this.acceptEdit(range, eolCount, firstLineLength, lastLineLength, text.length > 0 ? text.charCodeAt(0) : 0 /* CharCode.Null */);
  72. }
  73. acceptEdit(range, eolCount, firstLineLength, lastLineLength, firstCharCode) {
  74. this._acceptDeleteRange(range);
  75. this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength, lastLineLength, firstCharCode);
  76. this._updateEndLineNumber();
  77. }
  78. _acceptDeleteRange(range) {
  79. if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) {
  80. // Nothing to delete
  81. return;
  82. }
  83. const firstLineIndex = range.startLineNumber - this._startLineNumber;
  84. const lastLineIndex = range.endLineNumber - this._startLineNumber;
  85. if (lastLineIndex < 0) {
  86. // this deletion occurs entirely before this block, so we only need to adjust line numbers
  87. const deletedLinesCount = lastLineIndex - firstLineIndex;
  88. this._startLineNumber -= deletedLinesCount;
  89. return;
  90. }
  91. const tokenMaxDeltaLine = this._tokens.getMaxDeltaLine();
  92. if (firstLineIndex >= tokenMaxDeltaLine + 1) {
  93. // this deletion occurs entirely after this block, so there is nothing to do
  94. return;
  95. }
  96. if (firstLineIndex < 0 && lastLineIndex >= tokenMaxDeltaLine + 1) {
  97. // this deletion completely encompasses this block
  98. this._startLineNumber = 0;
  99. this._tokens.clear();
  100. return;
  101. }
  102. if (firstLineIndex < 0) {
  103. const deletedBefore = -firstLineIndex;
  104. this._startLineNumber -= deletedBefore;
  105. this._tokens.acceptDeleteRange(range.startColumn - 1, 0, 0, lastLineIndex, range.endColumn - 1);
  106. }
  107. else {
  108. this._tokens.acceptDeleteRange(0, firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1);
  109. }
  110. }
  111. _acceptInsertText(position, eolCount, firstLineLength, lastLineLength, firstCharCode) {
  112. if (eolCount === 0 && firstLineLength === 0) {
  113. // Nothing to insert
  114. return;
  115. }
  116. const lineIndex = position.lineNumber - this._startLineNumber;
  117. if (lineIndex < 0) {
  118. // this insertion occurs before this block, so we only need to adjust line numbers
  119. this._startLineNumber += eolCount;
  120. return;
  121. }
  122. const tokenMaxDeltaLine = this._tokens.getMaxDeltaLine();
  123. if (lineIndex >= tokenMaxDeltaLine + 1) {
  124. // this insertion occurs after this block, so there is nothing to do
  125. return;
  126. }
  127. this._tokens.acceptInsertText(lineIndex, position.column - 1, eolCount, firstLineLength, lastLineLength, firstCharCode);
  128. }
  129. }
  130. class SparseMultilineTokensStorage {
  131. constructor(tokens) {
  132. this._tokens = tokens;
  133. this._tokenCount = tokens.length / 4;
  134. }
  135. toString(startLineNumber) {
  136. const pieces = [];
  137. for (let i = 0; i < this._tokenCount; i++) {
  138. pieces.push(`(${this._getDeltaLine(i) + startLineNumber},${this._getStartCharacter(i)}-${this._getEndCharacter(i)})`);
  139. }
  140. return `[${pieces.join(',')}]`;
  141. }
  142. getMaxDeltaLine() {
  143. const tokenCount = this._getTokenCount();
  144. if (tokenCount === 0) {
  145. return -1;
  146. }
  147. return this._getDeltaLine(tokenCount - 1);
  148. }
  149. getRange() {
  150. const tokenCount = this._getTokenCount();
  151. if (tokenCount === 0) {
  152. return null;
  153. }
  154. const startChar = this._getStartCharacter(0);
  155. const maxDeltaLine = this._getDeltaLine(tokenCount - 1);
  156. const endChar = this._getEndCharacter(tokenCount - 1);
  157. return new Range(0, startChar + 1, maxDeltaLine, endChar + 1);
  158. }
  159. _getTokenCount() {
  160. return this._tokenCount;
  161. }
  162. _getDeltaLine(tokenIndex) {
  163. return this._tokens[4 * tokenIndex];
  164. }
  165. _getStartCharacter(tokenIndex) {
  166. return this._tokens[4 * tokenIndex + 1];
  167. }
  168. _getEndCharacter(tokenIndex) {
  169. return this._tokens[4 * tokenIndex + 2];
  170. }
  171. isEmpty() {
  172. return (this._getTokenCount() === 0);
  173. }
  174. getLineTokens(deltaLine) {
  175. let low = 0;
  176. let high = this._getTokenCount() - 1;
  177. while (low < high) {
  178. const mid = low + Math.floor((high - low) / 2);
  179. const midDeltaLine = this._getDeltaLine(mid);
  180. if (midDeltaLine < deltaLine) {
  181. low = mid + 1;
  182. }
  183. else if (midDeltaLine > deltaLine) {
  184. high = mid - 1;
  185. }
  186. else {
  187. let min = mid;
  188. while (min > low && this._getDeltaLine(min - 1) === deltaLine) {
  189. min--;
  190. }
  191. let max = mid;
  192. while (max < high && this._getDeltaLine(max + 1) === deltaLine) {
  193. max++;
  194. }
  195. return new SparseLineTokens(this._tokens.subarray(4 * min, 4 * max + 4));
  196. }
  197. }
  198. if (this._getDeltaLine(low) === deltaLine) {
  199. return new SparseLineTokens(this._tokens.subarray(4 * low, 4 * low + 4));
  200. }
  201. return null;
  202. }
  203. clear() {
  204. this._tokenCount = 0;
  205. }
  206. removeTokens(startDeltaLine, startChar, endDeltaLine, endChar) {
  207. const tokens = this._tokens;
  208. const tokenCount = this._tokenCount;
  209. let newTokenCount = 0;
  210. let hasDeletedTokens = false;
  211. let firstDeltaLine = 0;
  212. for (let i = 0; i < tokenCount; i++) {
  213. const srcOffset = 4 * i;
  214. const tokenDeltaLine = tokens[srcOffset];
  215. const tokenStartCharacter = tokens[srcOffset + 1];
  216. const tokenEndCharacter = tokens[srcOffset + 2];
  217. const tokenMetadata = tokens[srcOffset + 3];
  218. if ((tokenDeltaLine > startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter >= startChar))
  219. && (tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter <= endChar))) {
  220. hasDeletedTokens = true;
  221. }
  222. else {
  223. if (newTokenCount === 0) {
  224. firstDeltaLine = tokenDeltaLine;
  225. }
  226. if (hasDeletedTokens) {
  227. // must move the token to the left
  228. const destOffset = 4 * newTokenCount;
  229. tokens[destOffset] = tokenDeltaLine - firstDeltaLine;
  230. tokens[destOffset + 1] = tokenStartCharacter;
  231. tokens[destOffset + 2] = tokenEndCharacter;
  232. tokens[destOffset + 3] = tokenMetadata;
  233. }
  234. newTokenCount++;
  235. }
  236. }
  237. this._tokenCount = newTokenCount;
  238. return firstDeltaLine;
  239. }
  240. split(startDeltaLine, startChar, endDeltaLine, endChar) {
  241. const tokens = this._tokens;
  242. const tokenCount = this._tokenCount;
  243. const aTokens = [];
  244. const bTokens = [];
  245. let destTokens = aTokens;
  246. let destOffset = 0;
  247. let destFirstDeltaLine = 0;
  248. for (let i = 0; i < tokenCount; i++) {
  249. const srcOffset = 4 * i;
  250. const tokenDeltaLine = tokens[srcOffset];
  251. const tokenStartCharacter = tokens[srcOffset + 1];
  252. const tokenEndCharacter = tokens[srcOffset + 2];
  253. const tokenMetadata = tokens[srcOffset + 3];
  254. if ((tokenDeltaLine > startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter >= startChar))) {
  255. if ((tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter <= endChar))) {
  256. // this token is touching the range
  257. continue;
  258. }
  259. else {
  260. // this token is after the range
  261. if (destTokens !== bTokens) {
  262. // this token is the first token after the range
  263. destTokens = bTokens;
  264. destOffset = 0;
  265. destFirstDeltaLine = tokenDeltaLine;
  266. }
  267. }
  268. }
  269. destTokens[destOffset++] = tokenDeltaLine - destFirstDeltaLine;
  270. destTokens[destOffset++] = tokenStartCharacter;
  271. destTokens[destOffset++] = tokenEndCharacter;
  272. destTokens[destOffset++] = tokenMetadata;
  273. }
  274. return [new SparseMultilineTokensStorage(new Uint32Array(aTokens)), new SparseMultilineTokensStorage(new Uint32Array(bTokens)), destFirstDeltaLine];
  275. }
  276. acceptDeleteRange(horizontalShiftForFirstLineTokens, startDeltaLine, startCharacter, endDeltaLine, endCharacter) {
  277. // This is a bit complex, here are the cases I used to think about this:
  278. //
  279. // 1. The token starts before the deletion range
  280. // 1a. The token is completely before the deletion range
  281. // -----------
  282. // xxxxxxxxxxx
  283. // 1b. The token starts before, the deletion range ends after the token
  284. // -----------
  285. // xxxxxxxxxxx
  286. // 1c. The token starts before, the deletion range ends precisely with the token
  287. // ---------------
  288. // xxxxxxxx
  289. // 1d. The token starts before, the deletion range is inside the token
  290. // ---------------
  291. // xxxxx
  292. //
  293. // 2. The token starts at the same position with the deletion range
  294. // 2a. The token starts at the same position, and ends inside the deletion range
  295. // -------
  296. // xxxxxxxxxxx
  297. // 2b. The token starts at the same position, and ends at the same position as the deletion range
  298. // ----------
  299. // xxxxxxxxxx
  300. // 2c. The token starts at the same position, and ends after the deletion range
  301. // -------------
  302. // xxxxxxx
  303. //
  304. // 3. The token starts inside the deletion range
  305. // 3a. The token is inside the deletion range
  306. // -------
  307. // xxxxxxxxxxxxx
  308. // 3b. The token starts inside the deletion range, and ends at the same position as the deletion range
  309. // ----------
  310. // xxxxxxxxxxxxx
  311. // 3c. The token starts inside the deletion range, and ends after the deletion range
  312. // ------------
  313. // xxxxxxxxxxx
  314. //
  315. // 4. The token starts after the deletion range
  316. // -----------
  317. // xxxxxxxx
  318. //
  319. const tokens = this._tokens;
  320. const tokenCount = this._tokenCount;
  321. const deletedLineCount = (endDeltaLine - startDeltaLine);
  322. let newTokenCount = 0;
  323. let hasDeletedTokens = false;
  324. for (let i = 0; i < tokenCount; i++) {
  325. const srcOffset = 4 * i;
  326. let tokenDeltaLine = tokens[srcOffset];
  327. let tokenStartCharacter = tokens[srcOffset + 1];
  328. let tokenEndCharacter = tokens[srcOffset + 2];
  329. const tokenMetadata = tokens[srcOffset + 3];
  330. if (tokenDeltaLine < startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter <= startCharacter)) {
  331. // 1a. The token is completely before the deletion range
  332. // => nothing to do
  333. newTokenCount++;
  334. continue;
  335. }
  336. else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter < startCharacter) {
  337. // 1b, 1c, 1d
  338. // => the token survives, but it needs to shrink
  339. if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) {
  340. // 1d. The token starts before, the deletion range is inside the token
  341. // => the token shrinks by the deletion character count
  342. tokenEndCharacter -= (endCharacter - startCharacter);
  343. }
  344. else {
  345. // 1b. The token starts before, the deletion range ends after the token
  346. // 1c. The token starts before, the deletion range ends precisely with the token
  347. // => the token shrinks its ending to the deletion start
  348. tokenEndCharacter = startCharacter;
  349. }
  350. }
  351. else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter === startCharacter) {
  352. // 2a, 2b, 2c
  353. if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) {
  354. // 2c. The token starts at the same position, and ends after the deletion range
  355. // => the token shrinks by the deletion character count
  356. tokenEndCharacter -= (endCharacter - startCharacter);
  357. }
  358. else {
  359. // 2a. The token starts at the same position, and ends inside the deletion range
  360. // 2b. The token starts at the same position, and ends at the same position as the deletion range
  361. // => the token is deleted
  362. hasDeletedTokens = true;
  363. continue;
  364. }
  365. }
  366. else if (tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter < endCharacter)) {
  367. // 3a, 3b, 3c
  368. if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) {
  369. // 3c. The token starts inside the deletion range, and ends after the deletion range
  370. // => the token moves left and shrinks
  371. if (tokenDeltaLine === startDeltaLine) {
  372. // the deletion started on the same line as the token
  373. // => the token moves left and shrinks
  374. tokenStartCharacter = startCharacter;
  375. tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter);
  376. }
  377. else {
  378. // the deletion started on a line above the token
  379. // => the token moves to the beginning of the line
  380. tokenStartCharacter = 0;
  381. tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter);
  382. }
  383. }
  384. else {
  385. // 3a. The token is inside the deletion range
  386. // 3b. The token starts inside the deletion range, and ends at the same position as the deletion range
  387. // => the token is deleted
  388. hasDeletedTokens = true;
  389. continue;
  390. }
  391. }
  392. else if (tokenDeltaLine > endDeltaLine) {
  393. // 4. (partial) The token starts after the deletion range, on a line below...
  394. if (deletedLineCount === 0 && !hasDeletedTokens) {
  395. // early stop, there is no need to walk all the tokens and do nothing...
  396. newTokenCount = tokenCount;
  397. break;
  398. }
  399. tokenDeltaLine -= deletedLineCount;
  400. }
  401. else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) {
  402. // 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs
  403. if (horizontalShiftForFirstLineTokens && tokenDeltaLine === 0) {
  404. tokenStartCharacter += horizontalShiftForFirstLineTokens;
  405. tokenEndCharacter += horizontalShiftForFirstLineTokens;
  406. }
  407. tokenDeltaLine -= deletedLineCount;
  408. tokenStartCharacter -= (endCharacter - startCharacter);
  409. tokenEndCharacter -= (endCharacter - startCharacter);
  410. }
  411. else {
  412. throw new Error(`Not possible!`);
  413. }
  414. const destOffset = 4 * newTokenCount;
  415. tokens[destOffset] = tokenDeltaLine;
  416. tokens[destOffset + 1] = tokenStartCharacter;
  417. tokens[destOffset + 2] = tokenEndCharacter;
  418. tokens[destOffset + 3] = tokenMetadata;
  419. newTokenCount++;
  420. }
  421. this._tokenCount = newTokenCount;
  422. }
  423. acceptInsertText(deltaLine, character, eolCount, firstLineLength, lastLineLength, firstCharCode) {
  424. // Here are the cases I used to think about this:
  425. //
  426. // 1. The token is completely before the insertion point
  427. // ----------- |
  428. // 2. The token ends precisely at the insertion point
  429. // -----------|
  430. // 3. The token contains the insertion point
  431. // -----|------
  432. // 4. The token starts precisely at the insertion point
  433. // |-----------
  434. // 5. The token is completely after the insertion point
  435. // | -----------
  436. //
  437. const isInsertingPreciselyOneWordCharacter = (eolCount === 0
  438. && firstLineLength === 1
  439. && ((firstCharCode >= 48 /* CharCode.Digit0 */ && firstCharCode <= 57 /* CharCode.Digit9 */)
  440. || (firstCharCode >= 65 /* CharCode.A */ && firstCharCode <= 90 /* CharCode.Z */)
  441. || (firstCharCode >= 97 /* CharCode.a */ && firstCharCode <= 122 /* CharCode.z */)));
  442. const tokens = this._tokens;
  443. const tokenCount = this._tokenCount;
  444. for (let i = 0; i < tokenCount; i++) {
  445. const offset = 4 * i;
  446. let tokenDeltaLine = tokens[offset];
  447. let tokenStartCharacter = tokens[offset + 1];
  448. let tokenEndCharacter = tokens[offset + 2];
  449. if (tokenDeltaLine < deltaLine || (tokenDeltaLine === deltaLine && tokenEndCharacter < character)) {
  450. // 1. The token is completely before the insertion point
  451. // => nothing to do
  452. continue;
  453. }
  454. else if (tokenDeltaLine === deltaLine && tokenEndCharacter === character) {
  455. // 2. The token ends precisely at the insertion point
  456. // => expand the end character only if inserting precisely one character that is a word character
  457. if (isInsertingPreciselyOneWordCharacter) {
  458. tokenEndCharacter += 1;
  459. }
  460. else {
  461. continue;
  462. }
  463. }
  464. else if (tokenDeltaLine === deltaLine && tokenStartCharacter < character && character < tokenEndCharacter) {
  465. // 3. The token contains the insertion point
  466. if (eolCount === 0) {
  467. // => just expand the end character
  468. tokenEndCharacter += firstLineLength;
  469. }
  470. else {
  471. // => cut off the token
  472. tokenEndCharacter = character;
  473. }
  474. }
  475. else {
  476. // 4. or 5.
  477. if (tokenDeltaLine === deltaLine && tokenStartCharacter === character) {
  478. // 4. The token starts precisely at the insertion point
  479. // => grow the token (by keeping its start constant) only if inserting precisely one character that is a word character
  480. // => otherwise behave as in case 5.
  481. if (isInsertingPreciselyOneWordCharacter) {
  482. continue;
  483. }
  484. }
  485. // => the token must move and keep its size constant
  486. if (tokenDeltaLine === deltaLine) {
  487. tokenDeltaLine += eolCount;
  488. // this token is on the line where the insertion is taking place
  489. if (eolCount === 0) {
  490. tokenStartCharacter += firstLineLength;
  491. tokenEndCharacter += firstLineLength;
  492. }
  493. else {
  494. const tokenLength = tokenEndCharacter - tokenStartCharacter;
  495. tokenStartCharacter = lastLineLength + (tokenStartCharacter - character);
  496. tokenEndCharacter = tokenStartCharacter + tokenLength;
  497. }
  498. }
  499. else {
  500. tokenDeltaLine += eolCount;
  501. }
  502. }
  503. tokens[offset] = tokenDeltaLine;
  504. tokens[offset + 1] = tokenStartCharacter;
  505. tokens[offset + 2] = tokenEndCharacter;
  506. }
  507. }
  508. }
  509. export class SparseLineTokens {
  510. constructor(tokens) {
  511. this._tokens = tokens;
  512. }
  513. getCount() {
  514. return this._tokens.length / 4;
  515. }
  516. getStartCharacter(tokenIndex) {
  517. return this._tokens[4 * tokenIndex + 1];
  518. }
  519. getEndCharacter(tokenIndex) {
  520. return this._tokens[4 * tokenIndex + 2];
  521. }
  522. getMetadata(tokenIndex) {
  523. return this._tokens[4 * tokenIndex + 3];
  524. }
  525. }