b00f7120dd2334e69a45af7ed23de78272481ba045ae2a69c2911924d9d235db91877cac0cd2e1263d6e5e87aa53120ba637d952146cb28d2b2bd48aabd929 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import { stringDiff } from '../../../base/common/diff/diff.js';
  15. import { globals } from '../../../base/common/platform.js';
  16. import { URI } from '../../../base/common/uri.js';
  17. import { Position } from '../core/position.js';
  18. import { Range } from '../core/range.js';
  19. import { DiffComputer } from '../diff/diffComputer.js';
  20. import { MirrorTextModel as BaseMirrorModel } from '../model/mirrorTextModel.js';
  21. import { ensureValidWordDefinition, getWordAtText } from '../core/wordHelper.js';
  22. import { computeLinks } from '../languages/linkComputer.js';
  23. import { BasicInplaceReplace } from '../languages/supports/inplaceReplaceSupport.js';
  24. import { createMonacoBaseAPI } from './editorBaseApi.js';
  25. import * as types from '../../../base/common/types.js';
  26. import { StopWatch } from '../../../base/common/stopwatch.js';
  27. import { UnicodeTextModelHighlighter } from './unicodeTextModelHighlighter.js';
  28. /**
  29. * @internal
  30. */
  31. export class MirrorModel extends BaseMirrorModel {
  32. get uri() {
  33. return this._uri;
  34. }
  35. get eol() {
  36. return this._eol;
  37. }
  38. getValue() {
  39. return this.getText();
  40. }
  41. getLinesContent() {
  42. return this._lines.slice(0);
  43. }
  44. getLineCount() {
  45. return this._lines.length;
  46. }
  47. getLineContent(lineNumber) {
  48. return this._lines[lineNumber - 1];
  49. }
  50. getWordAtPosition(position, wordDefinition) {
  51. const wordAtText = getWordAtText(position.column, ensureValidWordDefinition(wordDefinition), this._lines[position.lineNumber - 1], 0);
  52. if (wordAtText) {
  53. return new Range(position.lineNumber, wordAtText.startColumn, position.lineNumber, wordAtText.endColumn);
  54. }
  55. return null;
  56. }
  57. words(wordDefinition) {
  58. const lines = this._lines;
  59. const wordenize = this._wordenize.bind(this);
  60. let lineNumber = 0;
  61. let lineText = '';
  62. let wordRangesIdx = 0;
  63. let wordRanges = [];
  64. return {
  65. *[Symbol.iterator]() {
  66. while (true) {
  67. if (wordRangesIdx < wordRanges.length) {
  68. const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end);
  69. wordRangesIdx += 1;
  70. yield value;
  71. }
  72. else {
  73. if (lineNumber < lines.length) {
  74. lineText = lines[lineNumber];
  75. wordRanges = wordenize(lineText, wordDefinition);
  76. wordRangesIdx = 0;
  77. lineNumber += 1;
  78. }
  79. else {
  80. break;
  81. }
  82. }
  83. }
  84. }
  85. };
  86. }
  87. getLineWords(lineNumber, wordDefinition) {
  88. const content = this._lines[lineNumber - 1];
  89. const ranges = this._wordenize(content, wordDefinition);
  90. const words = [];
  91. for (const range of ranges) {
  92. words.push({
  93. word: content.substring(range.start, range.end),
  94. startColumn: range.start + 1,
  95. endColumn: range.end + 1
  96. });
  97. }
  98. return words;
  99. }
  100. _wordenize(content, wordDefinition) {
  101. const result = [];
  102. let match;
  103. wordDefinition.lastIndex = 0; // reset lastIndex just to be sure
  104. while (match = wordDefinition.exec(content)) {
  105. if (match[0].length === 0) {
  106. // it did match the empty string
  107. break;
  108. }
  109. result.push({ start: match.index, end: match.index + match[0].length });
  110. }
  111. return result;
  112. }
  113. getValueInRange(range) {
  114. range = this._validateRange(range);
  115. if (range.startLineNumber === range.endLineNumber) {
  116. return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1);
  117. }
  118. const lineEnding = this._eol;
  119. const startLineIndex = range.startLineNumber - 1;
  120. const endLineIndex = range.endLineNumber - 1;
  121. const resultLines = [];
  122. resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1));
  123. for (let i = startLineIndex + 1; i < endLineIndex; i++) {
  124. resultLines.push(this._lines[i]);
  125. }
  126. resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1));
  127. return resultLines.join(lineEnding);
  128. }
  129. offsetAt(position) {
  130. position = this._validatePosition(position);
  131. this._ensureLineStarts();
  132. return this._lineStarts.getPrefixSum(position.lineNumber - 2) + (position.column - 1);
  133. }
  134. positionAt(offset) {
  135. offset = Math.floor(offset);
  136. offset = Math.max(0, offset);
  137. this._ensureLineStarts();
  138. const out = this._lineStarts.getIndexOf(offset);
  139. const lineLength = this._lines[out.index].length;
  140. // Ensure we return a valid position
  141. return {
  142. lineNumber: 1 + out.index,
  143. column: 1 + Math.min(out.remainder, lineLength)
  144. };
  145. }
  146. _validateRange(range) {
  147. const start = this._validatePosition({ lineNumber: range.startLineNumber, column: range.startColumn });
  148. const end = this._validatePosition({ lineNumber: range.endLineNumber, column: range.endColumn });
  149. if (start.lineNumber !== range.startLineNumber
  150. || start.column !== range.startColumn
  151. || end.lineNumber !== range.endLineNumber
  152. || end.column !== range.endColumn) {
  153. return {
  154. startLineNumber: start.lineNumber,
  155. startColumn: start.column,
  156. endLineNumber: end.lineNumber,
  157. endColumn: end.column
  158. };
  159. }
  160. return range;
  161. }
  162. _validatePosition(position) {
  163. if (!Position.isIPosition(position)) {
  164. throw new Error('bad position');
  165. }
  166. let { lineNumber, column } = position;
  167. let hasChanged = false;
  168. if (lineNumber < 1) {
  169. lineNumber = 1;
  170. column = 1;
  171. hasChanged = true;
  172. }
  173. else if (lineNumber > this._lines.length) {
  174. lineNumber = this._lines.length;
  175. column = this._lines[lineNumber - 1].length + 1;
  176. hasChanged = true;
  177. }
  178. else {
  179. const maxCharacter = this._lines[lineNumber - 1].length + 1;
  180. if (column < 1) {
  181. column = 1;
  182. hasChanged = true;
  183. }
  184. else if (column > maxCharacter) {
  185. column = maxCharacter;
  186. hasChanged = true;
  187. }
  188. }
  189. if (!hasChanged) {
  190. return position;
  191. }
  192. else {
  193. return { lineNumber, column };
  194. }
  195. }
  196. }
  197. /**
  198. * @internal
  199. */
  200. export class EditorSimpleWorker {
  201. constructor(host, foreignModuleFactory) {
  202. this._host = host;
  203. this._models = Object.create(null);
  204. this._foreignModuleFactory = foreignModuleFactory;
  205. this._foreignModule = null;
  206. }
  207. dispose() {
  208. this._models = Object.create(null);
  209. }
  210. _getModel(uri) {
  211. return this._models[uri];
  212. }
  213. _getModels() {
  214. const all = [];
  215. Object.keys(this._models).forEach((key) => all.push(this._models[key]));
  216. return all;
  217. }
  218. acceptNewModel(data) {
  219. this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId);
  220. }
  221. acceptModelChanged(strURL, e) {
  222. if (!this._models[strURL]) {
  223. return;
  224. }
  225. const model = this._models[strURL];
  226. model.onEvents(e);
  227. }
  228. acceptRemovedModel(strURL) {
  229. if (!this._models[strURL]) {
  230. return;
  231. }
  232. delete this._models[strURL];
  233. }
  234. computeUnicodeHighlights(url, options, range) {
  235. return __awaiter(this, void 0, void 0, function* () {
  236. const model = this._getModel(url);
  237. if (!model) {
  238. return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 };
  239. }
  240. return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range);
  241. });
  242. }
  243. // ---- BEGIN diff --------------------------------------------------------------------------
  244. computeDiff(originalUrl, modifiedUrl, ignoreTrimWhitespace, maxComputationTime) {
  245. return __awaiter(this, void 0, void 0, function* () {
  246. const original = this._getModel(originalUrl);
  247. const modified = this._getModel(modifiedUrl);
  248. if (!original || !modified) {
  249. return null;
  250. }
  251. return EditorSimpleWorker.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime);
  252. });
  253. }
  254. static computeDiff(originalTextModel, modifiedTextModel, ignoreTrimWhitespace, maxComputationTime) {
  255. const originalLines = originalTextModel.getLinesContent();
  256. const modifiedLines = modifiedTextModel.getLinesContent();
  257. const diffComputer = new DiffComputer(originalLines, modifiedLines, {
  258. shouldComputeCharChanges: true,
  259. shouldPostProcessCharChanges: true,
  260. shouldIgnoreTrimWhitespace: ignoreTrimWhitespace,
  261. shouldMakePrettyDiff: true,
  262. maxComputationTime: maxComputationTime
  263. });
  264. const diffResult = diffComputer.computeDiff();
  265. const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(originalTextModel, modifiedTextModel));
  266. return {
  267. quitEarly: diffResult.quitEarly,
  268. identical: identical,
  269. changes: diffResult.changes
  270. };
  271. }
  272. static _modelsAreIdentical(original, modified) {
  273. const originalLineCount = original.getLineCount();
  274. const modifiedLineCount = modified.getLineCount();
  275. if (originalLineCount !== modifiedLineCount) {
  276. return false;
  277. }
  278. for (let line = 1; line <= originalLineCount; line++) {
  279. const originalLine = original.getLineContent(line);
  280. const modifiedLine = modified.getLineContent(line);
  281. if (originalLine !== modifiedLine) {
  282. return false;
  283. }
  284. }
  285. return true;
  286. }
  287. computeMoreMinimalEdits(modelUrl, edits) {
  288. return __awaiter(this, void 0, void 0, function* () {
  289. const model = this._getModel(modelUrl);
  290. if (!model) {
  291. return edits;
  292. }
  293. const result = [];
  294. let lastEol = undefined;
  295. edits = edits.slice(0).sort((a, b) => {
  296. if (a.range && b.range) {
  297. return Range.compareRangesUsingStarts(a.range, b.range);
  298. }
  299. // eol only changes should go to the end
  300. const aRng = a.range ? 0 : 1;
  301. const bRng = b.range ? 0 : 1;
  302. return aRng - bRng;
  303. });
  304. for (let { range, text, eol } of edits) {
  305. if (typeof eol === 'number') {
  306. lastEol = eol;
  307. }
  308. if (Range.isEmpty(range) && !text) {
  309. // empty change
  310. continue;
  311. }
  312. const original = model.getValueInRange(range);
  313. text = text.replace(/\r\n|\n|\r/g, model.eol);
  314. if (original === text) {
  315. // noop
  316. continue;
  317. }
  318. // make sure diff won't take too long
  319. if (Math.max(text.length, original.length) > EditorSimpleWorker._diffLimit) {
  320. result.push({ range, text });
  321. continue;
  322. }
  323. // compute diff between original and edit.text
  324. const changes = stringDiff(original, text, false);
  325. const editOffset = model.offsetAt(Range.lift(range).getStartPosition());
  326. for (const change of changes) {
  327. const start = model.positionAt(editOffset + change.originalStart);
  328. const end = model.positionAt(editOffset + change.originalStart + change.originalLength);
  329. const newEdit = {
  330. text: text.substr(change.modifiedStart, change.modifiedLength),
  331. range: { startLineNumber: start.lineNumber, startColumn: start.column, endLineNumber: end.lineNumber, endColumn: end.column }
  332. };
  333. if (model.getValueInRange(newEdit.range) !== newEdit.text) {
  334. result.push(newEdit);
  335. }
  336. }
  337. }
  338. if (typeof lastEol === 'number') {
  339. result.push({ eol: lastEol, text: '', range: { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 } });
  340. }
  341. return result;
  342. });
  343. }
  344. // ---- END minimal edits ---------------------------------------------------------------
  345. computeLinks(modelUrl) {
  346. return __awaiter(this, void 0, void 0, function* () {
  347. const model = this._getModel(modelUrl);
  348. if (!model) {
  349. return null;
  350. }
  351. return computeLinks(model);
  352. });
  353. }
  354. textualSuggest(modelUrls, leadingWord, wordDef, wordDefFlags) {
  355. return __awaiter(this, void 0, void 0, function* () {
  356. const sw = new StopWatch(true);
  357. const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
  358. const seen = new Set();
  359. outer: for (const url of modelUrls) {
  360. const model = this._getModel(url);
  361. if (!model) {
  362. continue;
  363. }
  364. for (const word of model.words(wordDefRegExp)) {
  365. if (word === leadingWord || !isNaN(Number(word))) {
  366. continue;
  367. }
  368. seen.add(word);
  369. if (seen.size > EditorSimpleWorker._suggestionsLimit) {
  370. break outer;
  371. }
  372. }
  373. }
  374. return { words: Array.from(seen), duration: sw.elapsed() };
  375. });
  376. }
  377. // ---- END suggest --------------------------------------------------------------------------
  378. //#region -- word ranges --
  379. computeWordRanges(modelUrl, range, wordDef, wordDefFlags) {
  380. return __awaiter(this, void 0, void 0, function* () {
  381. const model = this._getModel(modelUrl);
  382. if (!model) {
  383. return Object.create(null);
  384. }
  385. const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
  386. const result = Object.create(null);
  387. for (let line = range.startLineNumber; line < range.endLineNumber; line++) {
  388. const words = model.getLineWords(line, wordDefRegExp);
  389. for (const word of words) {
  390. if (!isNaN(Number(word.word))) {
  391. continue;
  392. }
  393. let array = result[word.word];
  394. if (!array) {
  395. array = [];
  396. result[word.word] = array;
  397. }
  398. array.push({
  399. startLineNumber: line,
  400. startColumn: word.startColumn,
  401. endLineNumber: line,
  402. endColumn: word.endColumn
  403. });
  404. }
  405. }
  406. return result;
  407. });
  408. }
  409. //#endregion
  410. navigateValueSet(modelUrl, range, up, wordDef, wordDefFlags) {
  411. return __awaiter(this, void 0, void 0, function* () {
  412. const model = this._getModel(modelUrl);
  413. if (!model) {
  414. return null;
  415. }
  416. const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
  417. if (range.startColumn === range.endColumn) {
  418. range = {
  419. startLineNumber: range.startLineNumber,
  420. startColumn: range.startColumn,
  421. endLineNumber: range.endLineNumber,
  422. endColumn: range.endColumn + 1
  423. };
  424. }
  425. const selectionText = model.getValueInRange(range);
  426. const wordRange = model.getWordAtPosition({ lineNumber: range.startLineNumber, column: range.startColumn }, wordDefRegExp);
  427. if (!wordRange) {
  428. return null;
  429. }
  430. const word = model.getValueInRange(wordRange);
  431. const result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up);
  432. return result;
  433. });
  434. }
  435. // ---- BEGIN foreign module support --------------------------------------------------------------------------
  436. loadForeignModule(moduleId, createData, foreignHostMethods) {
  437. const proxyMethodRequest = (method, args) => {
  438. return this._host.fhr(method, args);
  439. };
  440. const foreignHost = types.createProxyObject(foreignHostMethods, proxyMethodRequest);
  441. const ctx = {
  442. host: foreignHost,
  443. getMirrorModels: () => {
  444. return this._getModels();
  445. }
  446. };
  447. if (this._foreignModuleFactory) {
  448. this._foreignModule = this._foreignModuleFactory(ctx, createData);
  449. // static foreing module
  450. return Promise.resolve(types.getAllMethodNames(this._foreignModule));
  451. }
  452. // ESM-comment-begin
  453. // return new Promise<any>((resolve, reject) => {
  454. // require([moduleId], (foreignModule: { create: IForeignModuleFactory }) => {
  455. // this._foreignModule = foreignModule.create(ctx, createData);
  456. //
  457. // resolve(types.getAllMethodNames(this._foreignModule));
  458. //
  459. // }, reject);
  460. // });
  461. // ESM-comment-end
  462. // ESM-uncomment-begin
  463. return Promise.reject(new Error(`Unexpected usage`));
  464. // ESM-uncomment-end
  465. }
  466. // foreign method request
  467. fmr(method, args) {
  468. if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') {
  469. return Promise.reject(new Error('Missing requestHandler or method: ' + method));
  470. }
  471. try {
  472. return Promise.resolve(this._foreignModule[method].apply(this._foreignModule, args));
  473. }
  474. catch (e) {
  475. return Promise.reject(e);
  476. }
  477. }
  478. }
  479. // ---- END diff --------------------------------------------------------------------------
  480. // ---- BEGIN minimal edits ---------------------------------------------------------------
  481. EditorSimpleWorker._diffLimit = 100000;
  482. // ---- BEGIN suggest --------------------------------------------------------------------------
  483. EditorSimpleWorker._suggestionsLimit = 10000;
  484. /**
  485. * Called on the worker side
  486. * @internal
  487. */
  488. export function create(host) {
  489. return new EditorSimpleWorker(host, null);
  490. }
  491. if (typeof importScripts === 'function') {
  492. // Running in a web worker
  493. globals.monaco = createMonacoBaseAPI();
  494. }