/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { stringDiff } from '../../../base/common/diff/diff.js'; import { globals } from '../../../base/common/platform.js'; import { URI } from '../../../base/common/uri.js'; import { Position } from '../core/position.js'; import { Range } from '../core/range.js'; import { DiffComputer } from '../diff/diffComputer.js'; import { MirrorTextModel as BaseMirrorModel } from '../model/mirrorTextModel.js'; import { ensureValidWordDefinition, getWordAtText } from '../core/wordHelper.js'; import { computeLinks } from '../languages/linkComputer.js'; import { BasicInplaceReplace } from '../languages/supports/inplaceReplaceSupport.js'; import { createMonacoBaseAPI } from './editorBaseApi.js'; import * as types from '../../../base/common/types.js'; import { StopWatch } from '../../../base/common/stopwatch.js'; import { UnicodeTextModelHighlighter } from './unicodeTextModelHighlighter.js'; /** * @internal */ export class MirrorModel extends BaseMirrorModel { get uri() { return this._uri; } get eol() { return this._eol; } getValue() { return this.getText(); } getLinesContent() { return this._lines.slice(0); } getLineCount() { return this._lines.length; } getLineContent(lineNumber) { return this._lines[lineNumber - 1]; } getWordAtPosition(position, wordDefinition) { const wordAtText = getWordAtText(position.column, ensureValidWordDefinition(wordDefinition), this._lines[position.lineNumber - 1], 0); if (wordAtText) { return new Range(position.lineNumber, wordAtText.startColumn, position.lineNumber, wordAtText.endColumn); } return null; } words(wordDefinition) { const lines = this._lines; const wordenize = this._wordenize.bind(this); let lineNumber = 0; let lineText = ''; let wordRangesIdx = 0; let wordRanges = []; return { *[Symbol.iterator]() { while (true) { if (wordRangesIdx < wordRanges.length) { const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); wordRangesIdx += 1; yield value; } else { if (lineNumber < lines.length) { lineText = lines[lineNumber]; wordRanges = wordenize(lineText, wordDefinition); wordRangesIdx = 0; lineNumber += 1; } else { break; } } } } }; } getLineWords(lineNumber, wordDefinition) { const content = this._lines[lineNumber - 1]; const ranges = this._wordenize(content, wordDefinition); const words = []; for (const range of ranges) { words.push({ word: content.substring(range.start, range.end), startColumn: range.start + 1, endColumn: range.end + 1 }); } return words; } _wordenize(content, wordDefinition) { const result = []; let match; wordDefinition.lastIndex = 0; // reset lastIndex just to be sure while (match = wordDefinition.exec(content)) { if (match[0].length === 0) { // it did match the empty string break; } result.push({ start: match.index, end: match.index + match[0].length }); } return result; } getValueInRange(range) { range = this._validateRange(range); if (range.startLineNumber === range.endLineNumber) { return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1); } const lineEnding = this._eol; const startLineIndex = range.startLineNumber - 1; const endLineIndex = range.endLineNumber - 1; const resultLines = []; resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1)); for (let i = startLineIndex + 1; i < endLineIndex; i++) { resultLines.push(this._lines[i]); } resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1)); return resultLines.join(lineEnding); } offsetAt(position) { position = this._validatePosition(position); this._ensureLineStarts(); return this._lineStarts.getPrefixSum(position.lineNumber - 2) + (position.column - 1); } positionAt(offset) { offset = Math.floor(offset); offset = Math.max(0, offset); this._ensureLineStarts(); const out = this._lineStarts.getIndexOf(offset); const lineLength = this._lines[out.index].length; // Ensure we return a valid position return { lineNumber: 1 + out.index, column: 1 + Math.min(out.remainder, lineLength) }; } _validateRange(range) { const start = this._validatePosition({ lineNumber: range.startLineNumber, column: range.startColumn }); const end = this._validatePosition({ lineNumber: range.endLineNumber, column: range.endColumn }); if (start.lineNumber !== range.startLineNumber || start.column !== range.startColumn || end.lineNumber !== range.endLineNumber || end.column !== range.endColumn) { return { startLineNumber: start.lineNumber, startColumn: start.column, endLineNumber: end.lineNumber, endColumn: end.column }; } return range; } _validatePosition(position) { if (!Position.isIPosition(position)) { throw new Error('bad position'); } let { lineNumber, column } = position; let hasChanged = false; if (lineNumber < 1) { lineNumber = 1; column = 1; hasChanged = true; } else if (lineNumber > this._lines.length) { lineNumber = this._lines.length; column = this._lines[lineNumber - 1].length + 1; hasChanged = true; } else { const maxCharacter = this._lines[lineNumber - 1].length + 1; if (column < 1) { column = 1; hasChanged = true; } else if (column > maxCharacter) { column = maxCharacter; hasChanged = true; } } if (!hasChanged) { return position; } else { return { lineNumber, column }; } } } /** * @internal */ export class EditorSimpleWorker { constructor(host, foreignModuleFactory) { this._host = host; this._models = Object.create(null); this._foreignModuleFactory = foreignModuleFactory; this._foreignModule = null; } dispose() { this._models = Object.create(null); } _getModel(uri) { return this._models[uri]; } _getModels() { const all = []; Object.keys(this._models).forEach((key) => all.push(this._models[key])); return all; } acceptNewModel(data) { this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId); } acceptModelChanged(strURL, e) { if (!this._models[strURL]) { return; } const model = this._models[strURL]; model.onEvents(e); } acceptRemovedModel(strURL) { if (!this._models[strURL]) { return; } delete this._models[strURL]; } computeUnicodeHighlights(url, options, range) { return __awaiter(this, void 0, void 0, function* () { const model = this._getModel(url); if (!model) { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; } return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range); }); } // ---- BEGIN diff -------------------------------------------------------------------------- computeDiff(originalUrl, modifiedUrl, ignoreTrimWhitespace, maxComputationTime) { return __awaiter(this, void 0, void 0, function* () { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); if (!original || !modified) { return null; } return EditorSimpleWorker.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime); }); } static computeDiff(originalTextModel, modifiedTextModel, ignoreTrimWhitespace, maxComputationTime) { const originalLines = originalTextModel.getLinesContent(); const modifiedLines = modifiedTextModel.getLinesContent(); const diffComputer = new DiffComputer(originalLines, modifiedLines, { shouldComputeCharChanges: true, shouldPostProcessCharChanges: true, shouldIgnoreTrimWhitespace: ignoreTrimWhitespace, shouldMakePrettyDiff: true, maxComputationTime: maxComputationTime }); const diffResult = diffComputer.computeDiff(); const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(originalTextModel, modifiedTextModel)); return { quitEarly: diffResult.quitEarly, identical: identical, changes: diffResult.changes }; } static _modelsAreIdentical(original, modified) { const originalLineCount = original.getLineCount(); const modifiedLineCount = modified.getLineCount(); if (originalLineCount !== modifiedLineCount) { return false; } for (let line = 1; line <= originalLineCount; line++) { const originalLine = original.getLineContent(line); const modifiedLine = modified.getLineContent(line); if (originalLine !== modifiedLine) { return false; } } return true; } computeMoreMinimalEdits(modelUrl, edits) { return __awaiter(this, void 0, void 0, function* () { const model = this._getModel(modelUrl); if (!model) { return edits; } const result = []; let lastEol = undefined; edits = edits.slice(0).sort((a, b) => { if (a.range && b.range) { return Range.compareRangesUsingStarts(a.range, b.range); } // eol only changes should go to the end const aRng = a.range ? 0 : 1; const bRng = b.range ? 0 : 1; return aRng - bRng; }); for (let { range, text, eol } of edits) { if (typeof eol === 'number') { lastEol = eol; } if (Range.isEmpty(range) && !text) { // empty change continue; } const original = model.getValueInRange(range); text = text.replace(/\r\n|\n|\r/g, model.eol); if (original === text) { // noop continue; } // make sure diff won't take too long if (Math.max(text.length, original.length) > EditorSimpleWorker._diffLimit) { result.push({ range, text }); continue; } // compute diff between original and edit.text const changes = stringDiff(original, text, false); const editOffset = model.offsetAt(Range.lift(range).getStartPosition()); for (const change of changes) { const start = model.positionAt(editOffset + change.originalStart); const end = model.positionAt(editOffset + change.originalStart + change.originalLength); const newEdit = { text: text.substr(change.modifiedStart, change.modifiedLength), range: { startLineNumber: start.lineNumber, startColumn: start.column, endLineNumber: end.lineNumber, endColumn: end.column } }; if (model.getValueInRange(newEdit.range) !== newEdit.text) { result.push(newEdit); } } } if (typeof lastEol === 'number') { result.push({ eol: lastEol, text: '', range: { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 } }); } return result; }); } // ---- END minimal edits --------------------------------------------------------------- computeLinks(modelUrl) { return __awaiter(this, void 0, void 0, function* () { const model = this._getModel(modelUrl); if (!model) { return null; } return computeLinks(model); }); } textualSuggest(modelUrls, leadingWord, wordDef, wordDefFlags) { return __awaiter(this, void 0, void 0, function* () { const sw = new StopWatch(true); const wordDefRegExp = new RegExp(wordDef, wordDefFlags); const seen = new Set(); outer: for (const url of modelUrls) { const model = this._getModel(url); if (!model) { continue; } for (const word of model.words(wordDefRegExp)) { if (word === leadingWord || !isNaN(Number(word))) { continue; } seen.add(word); if (seen.size > EditorSimpleWorker._suggestionsLimit) { break outer; } } } return { words: Array.from(seen), duration: sw.elapsed() }; }); } // ---- END suggest -------------------------------------------------------------------------- //#region -- word ranges -- computeWordRanges(modelUrl, range, wordDef, wordDefFlags) { return __awaiter(this, void 0, void 0, function* () { const model = this._getModel(modelUrl); if (!model) { return Object.create(null); } const wordDefRegExp = new RegExp(wordDef, wordDefFlags); const result = Object.create(null); for (let line = range.startLineNumber; line < range.endLineNumber; line++) { const words = model.getLineWords(line, wordDefRegExp); for (const word of words) { if (!isNaN(Number(word.word))) { continue; } let array = result[word.word]; if (!array) { array = []; result[word.word] = array; } array.push({ startLineNumber: line, startColumn: word.startColumn, endLineNumber: line, endColumn: word.endColumn }); } } return result; }); } //#endregion navigateValueSet(modelUrl, range, up, wordDef, wordDefFlags) { return __awaiter(this, void 0, void 0, function* () { const model = this._getModel(modelUrl); if (!model) { return null; } const wordDefRegExp = new RegExp(wordDef, wordDefFlags); if (range.startColumn === range.endColumn) { range = { startLineNumber: range.startLineNumber, startColumn: range.startColumn, endLineNumber: range.endLineNumber, endColumn: range.endColumn + 1 }; } const selectionText = model.getValueInRange(range); const wordRange = model.getWordAtPosition({ lineNumber: range.startLineNumber, column: range.startColumn }, wordDefRegExp); if (!wordRange) { return null; } const word = model.getValueInRange(wordRange); const result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up); return result; }); } // ---- BEGIN foreign module support -------------------------------------------------------------------------- loadForeignModule(moduleId, createData, foreignHostMethods) { const proxyMethodRequest = (method, args) => { return this._host.fhr(method, args); }; const foreignHost = types.createProxyObject(foreignHostMethods, proxyMethodRequest); const ctx = { host: foreignHost, getMirrorModels: () => { return this._getModels(); } }; if (this._foreignModuleFactory) { this._foreignModule = this._foreignModuleFactory(ctx, createData); // static foreing module return Promise.resolve(types.getAllMethodNames(this._foreignModule)); } // ESM-comment-begin // return new Promise((resolve, reject) => { // require([moduleId], (foreignModule: { create: IForeignModuleFactory }) => { // this._foreignModule = foreignModule.create(ctx, createData); // // resolve(types.getAllMethodNames(this._foreignModule)); // // }, reject); // }); // ESM-comment-end // ESM-uncomment-begin return Promise.reject(new Error(`Unexpected usage`)); // ESM-uncomment-end } // foreign method request fmr(method, args) { if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') { return Promise.reject(new Error('Missing requestHandler or method: ' + method)); } try { return Promise.resolve(this._foreignModule[method].apply(this._foreignModule, args)); } catch (e) { return Promise.reject(e); } } } // ---- END diff -------------------------------------------------------------------------- // ---- BEGIN minimal edits --------------------------------------------------------------- EditorSimpleWorker._diffLimit = 100000; // ---- BEGIN suggest -------------------------------------------------------------------------- EditorSimpleWorker._suggestionsLimit = 10000; /** * Called on the worker side * @internal */ export function create(host) { return new EditorSimpleWorker(host, null); } if (typeof importScripts === 'function') { // Running in a web worker globals.monaco = createMonacoBaseAPI(); }