3c3034b70e4688b53bcd3aca454087681f085faaba3e7072e4faf30f6e552ca748bba999250dcbf6d2cf00d53d8800aac76eb84b424f106bdca39d7a18dc72 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import { IntervalTimer, timeout } from '../../../base/common/async.js';
  24. import { Disposable, dispose, toDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
  25. import { SimpleWorkerClient, logOnceWebWorkerWarning } from '../../../base/common/worker/simpleWorker.js';
  26. import { DefaultWorkerFactory } from '../../../base/browser/defaultWorkerFactory.js';
  27. import { Range } from '../../common/core/range.js';
  28. import { ILanguageConfigurationService } from '../../common/languages/languageConfigurationRegistry.js';
  29. import { EditorSimpleWorker } from '../../common/services/editorSimpleWorker.js';
  30. import { IModelService } from '../../common/services/model.js';
  31. import { ITextResourceConfigurationService } from '../../common/services/textResourceConfiguration.js';
  32. import { regExpFlags } from '../../../base/common/strings.js';
  33. import { isNonEmptyArray } from '../../../base/common/arrays.js';
  34. import { ILogService } from '../../../platform/log/common/log.js';
  35. import { StopWatch } from '../../../base/common/stopwatch.js';
  36. import { canceled } from '../../../base/common/errors.js';
  37. import { ILanguageFeaturesService } from '../../common/services/languageFeatures.js';
  38. /**
  39. * Stop syncing a model to the worker if it was not needed for 1 min.
  40. */
  41. const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000;
  42. /**
  43. * Stop the worker if it was not needed for 5 min.
  44. */
  45. const STOP_WORKER_DELTA_TIME_MS = 5 * 60 * 1000;
  46. function canSyncModel(modelService, resource) {
  47. const model = modelService.getModel(resource);
  48. if (!model) {
  49. return false;
  50. }
  51. if (model.isTooLargeForSyncing()) {
  52. return false;
  53. }
  54. return true;
  55. }
  56. let EditorWorkerService = class EditorWorkerService extends Disposable {
  57. constructor(modelService, configurationService, logService, languageConfigurationService, languageFeaturesService) {
  58. super();
  59. this._modelService = modelService;
  60. this._workerManager = this._register(new WorkerManager(this._modelService, languageConfigurationService));
  61. this._logService = logService;
  62. // register default link-provider and default completions-provider
  63. this._register(languageFeaturesService.linkProvider.register({ language: '*', hasAccessToAllModels: true }, {
  64. provideLinks: (model, token) => {
  65. if (!canSyncModel(this._modelService, model.uri)) {
  66. return Promise.resolve({ links: [] }); // File too large
  67. }
  68. return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => {
  69. return links && { links };
  70. });
  71. }
  72. }));
  73. this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, languageConfigurationService)));
  74. }
  75. dispose() {
  76. super.dispose();
  77. }
  78. canComputeUnicodeHighlights(uri) {
  79. return canSyncModel(this._modelService, uri);
  80. }
  81. computedUnicodeHighlights(uri, options, range) {
  82. return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range));
  83. }
  84. computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime) {
  85. return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime));
  86. }
  87. computeMoreMinimalEdits(resource, edits) {
  88. if (isNonEmptyArray(edits)) {
  89. if (!canSyncModel(this._modelService, resource)) {
  90. return Promise.resolve(edits); // File too large
  91. }
  92. const sw = StopWatch.create(true);
  93. const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits));
  94. result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed()));
  95. return Promise.race([result, timeout(1000).then(() => edits)]);
  96. }
  97. else {
  98. return Promise.resolve(undefined);
  99. }
  100. }
  101. canNavigateValueSet(resource) {
  102. return (canSyncModel(this._modelService, resource));
  103. }
  104. navigateValueSet(resource, range, up) {
  105. return this._workerManager.withWorker().then(client => client.navigateValueSet(resource, range, up));
  106. }
  107. canComputeWordRanges(resource) {
  108. return canSyncModel(this._modelService, resource);
  109. }
  110. computeWordRanges(resource, range) {
  111. return this._workerManager.withWorker().then(client => client.computeWordRanges(resource, range));
  112. }
  113. };
  114. EditorWorkerService = __decorate([
  115. __param(0, IModelService),
  116. __param(1, ITextResourceConfigurationService),
  117. __param(2, ILogService),
  118. __param(3, ILanguageConfigurationService),
  119. __param(4, ILanguageFeaturesService)
  120. ], EditorWorkerService);
  121. export { EditorWorkerService };
  122. class WordBasedCompletionItemProvider {
  123. constructor(workerManager, configurationService, modelService, languageConfigurationService) {
  124. this.languageConfigurationService = languageConfigurationService;
  125. this._debugDisplayName = 'wordbasedCompletions';
  126. this._workerManager = workerManager;
  127. this._configurationService = configurationService;
  128. this._modelService = modelService;
  129. }
  130. provideCompletionItems(model, position) {
  131. return __awaiter(this, void 0, void 0, function* () {
  132. const config = this._configurationService.getValue(model.uri, position, 'editor');
  133. if (!config.wordBasedSuggestions) {
  134. return undefined;
  135. }
  136. const models = [];
  137. if (config.wordBasedSuggestionsMode === 'currentDocument') {
  138. // only current file and only if not too large
  139. if (canSyncModel(this._modelService, model.uri)) {
  140. models.push(model.uri);
  141. }
  142. }
  143. else {
  144. // either all files or files of same language
  145. for (const candidate of this._modelService.getModels()) {
  146. if (!canSyncModel(this._modelService, candidate.uri)) {
  147. continue;
  148. }
  149. if (candidate === model) {
  150. models.unshift(candidate.uri);
  151. }
  152. else if (config.wordBasedSuggestionsMode === 'allDocuments' || candidate.getLanguageId() === model.getLanguageId()) {
  153. models.push(candidate.uri);
  154. }
  155. }
  156. }
  157. if (models.length === 0) {
  158. return undefined; // File too large, no other files
  159. }
  160. const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
  161. const word = model.getWordAtPosition(position);
  162. const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
  163. const insert = replace.setEndPosition(position.lineNumber, position.column);
  164. const client = yield this._workerManager.withWorker();
  165. const data = yield client.textualSuggest(models, word === null || word === void 0 ? void 0 : word.word, wordDefRegExp);
  166. if (!data) {
  167. return undefined;
  168. }
  169. return {
  170. duration: data.duration,
  171. suggestions: data.words.map((word) => {
  172. return {
  173. kind: 18 /* languages.CompletionItemKind.Text */,
  174. label: word,
  175. insertText: word,
  176. range: { insert, replace }
  177. };
  178. }),
  179. };
  180. });
  181. }
  182. }
  183. class WorkerManager extends Disposable {
  184. constructor(modelService, languageConfigurationService) {
  185. super();
  186. this.languageConfigurationService = languageConfigurationService;
  187. this._modelService = modelService;
  188. this._editorWorkerClient = null;
  189. this._lastWorkerUsedTime = (new Date()).getTime();
  190. const stopWorkerInterval = this._register(new IntervalTimer());
  191. stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2));
  192. this._register(this._modelService.onModelRemoved(_ => this._checkStopEmptyWorker()));
  193. }
  194. dispose() {
  195. if (this._editorWorkerClient) {
  196. this._editorWorkerClient.dispose();
  197. this._editorWorkerClient = null;
  198. }
  199. super.dispose();
  200. }
  201. /**
  202. * Check if the model service has no more models and stop the worker if that is the case.
  203. */
  204. _checkStopEmptyWorker() {
  205. if (!this._editorWorkerClient) {
  206. return;
  207. }
  208. const models = this._modelService.getModels();
  209. if (models.length === 0) {
  210. // There are no more models => nothing possible for me to do
  211. this._editorWorkerClient.dispose();
  212. this._editorWorkerClient = null;
  213. }
  214. }
  215. /**
  216. * Check if the worker has been idle for a while and then stop it.
  217. */
  218. _checkStopIdleWorker() {
  219. if (!this._editorWorkerClient) {
  220. return;
  221. }
  222. const timeSinceLastWorkerUsedTime = (new Date()).getTime() - this._lastWorkerUsedTime;
  223. if (timeSinceLastWorkerUsedTime > STOP_WORKER_DELTA_TIME_MS) {
  224. this._editorWorkerClient.dispose();
  225. this._editorWorkerClient = null;
  226. }
  227. }
  228. withWorker() {
  229. this._lastWorkerUsedTime = (new Date()).getTime();
  230. if (!this._editorWorkerClient) {
  231. this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService', this.languageConfigurationService);
  232. }
  233. return Promise.resolve(this._editorWorkerClient);
  234. }
  235. }
  236. class EditorModelManager extends Disposable {
  237. constructor(proxy, modelService, keepIdleModels) {
  238. super();
  239. this._syncedModels = Object.create(null);
  240. this._syncedModelsLastUsedTime = Object.create(null);
  241. this._proxy = proxy;
  242. this._modelService = modelService;
  243. if (!keepIdleModels) {
  244. const timer = new IntervalTimer();
  245. timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2));
  246. this._register(timer);
  247. }
  248. }
  249. dispose() {
  250. for (const modelUrl in this._syncedModels) {
  251. dispose(this._syncedModels[modelUrl]);
  252. }
  253. this._syncedModels = Object.create(null);
  254. this._syncedModelsLastUsedTime = Object.create(null);
  255. super.dispose();
  256. }
  257. ensureSyncedResources(resources, forceLargeModels) {
  258. for (const resource of resources) {
  259. const resourceStr = resource.toString();
  260. if (!this._syncedModels[resourceStr]) {
  261. this._beginModelSync(resource, forceLargeModels);
  262. }
  263. if (this._syncedModels[resourceStr]) {
  264. this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime();
  265. }
  266. }
  267. }
  268. _checkStopModelSync() {
  269. const currentTime = (new Date()).getTime();
  270. const toRemove = [];
  271. for (const modelUrl in this._syncedModelsLastUsedTime) {
  272. const elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl];
  273. if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) {
  274. toRemove.push(modelUrl);
  275. }
  276. }
  277. for (const e of toRemove) {
  278. this._stopModelSync(e);
  279. }
  280. }
  281. _beginModelSync(resource, forceLargeModels) {
  282. const model = this._modelService.getModel(resource);
  283. if (!model) {
  284. return;
  285. }
  286. if (!forceLargeModels && model.isTooLargeForSyncing()) {
  287. return;
  288. }
  289. const modelUrl = resource.toString();
  290. this._proxy.acceptNewModel({
  291. url: model.uri.toString(),
  292. lines: model.getLinesContent(),
  293. EOL: model.getEOL(),
  294. versionId: model.getVersionId()
  295. });
  296. const toDispose = new DisposableStore();
  297. toDispose.add(model.onDidChangeContent((e) => {
  298. this._proxy.acceptModelChanged(modelUrl.toString(), e);
  299. }));
  300. toDispose.add(model.onWillDispose(() => {
  301. this._stopModelSync(modelUrl);
  302. }));
  303. toDispose.add(toDisposable(() => {
  304. this._proxy.acceptRemovedModel(modelUrl);
  305. }));
  306. this._syncedModels[modelUrl] = toDispose;
  307. }
  308. _stopModelSync(modelUrl) {
  309. const toDispose = this._syncedModels[modelUrl];
  310. delete this._syncedModels[modelUrl];
  311. delete this._syncedModelsLastUsedTime[modelUrl];
  312. dispose(toDispose);
  313. }
  314. }
  315. class SynchronousWorkerClient {
  316. constructor(instance) {
  317. this._instance = instance;
  318. this._proxyObj = Promise.resolve(this._instance);
  319. }
  320. dispose() {
  321. this._instance.dispose();
  322. }
  323. getProxyObject() {
  324. return this._proxyObj;
  325. }
  326. }
  327. export class EditorWorkerHost {
  328. constructor(workerClient) {
  329. this._workerClient = workerClient;
  330. }
  331. // foreign host request
  332. fhr(method, args) {
  333. return this._workerClient.fhr(method, args);
  334. }
  335. }
  336. export class EditorWorkerClient extends Disposable {
  337. constructor(modelService, keepIdleModels, label, languageConfigurationService) {
  338. super();
  339. this.languageConfigurationService = languageConfigurationService;
  340. this._disposed = false;
  341. this._modelService = modelService;
  342. this._keepIdleModels = keepIdleModels;
  343. this._workerFactory = new DefaultWorkerFactory(label);
  344. this._worker = null;
  345. this._modelManager = null;
  346. }
  347. // foreign host request
  348. fhr(method, args) {
  349. throw new Error(`Not implemented!`);
  350. }
  351. _getOrCreateWorker() {
  352. if (!this._worker) {
  353. try {
  354. this._worker = this._register(new SimpleWorkerClient(this._workerFactory, 'vs/editor/common/services/editorSimpleWorker', new EditorWorkerHost(this)));
  355. }
  356. catch (err) {
  357. logOnceWebWorkerWarning(err);
  358. this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null));
  359. }
  360. }
  361. return this._worker;
  362. }
  363. _getProxy() {
  364. return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => {
  365. logOnceWebWorkerWarning(err);
  366. this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null));
  367. return this._getOrCreateWorker().getProxyObject();
  368. });
  369. }
  370. _getOrCreateModelManager(proxy) {
  371. if (!this._modelManager) {
  372. this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, this._keepIdleModels));
  373. }
  374. return this._modelManager;
  375. }
  376. _withSyncedResources(resources, forceLargeModels = false) {
  377. return __awaiter(this, void 0, void 0, function* () {
  378. if (this._disposed) {
  379. return Promise.reject(canceled());
  380. }
  381. return this._getProxy().then((proxy) => {
  382. this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels);
  383. return proxy;
  384. });
  385. });
  386. }
  387. computedUnicodeHighlights(uri, options, range) {
  388. return this._withSyncedResources([uri]).then(proxy => {
  389. return proxy.computeUnicodeHighlights(uri.toString(), options, range);
  390. });
  391. }
  392. computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime) {
  393. return this._withSyncedResources([original, modified], /* forceLargeModels */ true).then(proxy => {
  394. return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace, maxComputationTime);
  395. });
  396. }
  397. computeMoreMinimalEdits(resource, edits) {
  398. return this._withSyncedResources([resource]).then(proxy => {
  399. return proxy.computeMoreMinimalEdits(resource.toString(), edits);
  400. });
  401. }
  402. computeLinks(resource) {
  403. return this._withSyncedResources([resource]).then(proxy => {
  404. return proxy.computeLinks(resource.toString());
  405. });
  406. }
  407. textualSuggest(resources, leadingWord, wordDefRegExp) {
  408. return __awaiter(this, void 0, void 0, function* () {
  409. const proxy = yield this._withSyncedResources(resources);
  410. const wordDef = wordDefRegExp.source;
  411. const wordDefFlags = regExpFlags(wordDefRegExp);
  412. return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags);
  413. });
  414. }
  415. computeWordRanges(resource, range) {
  416. return this._withSyncedResources([resource]).then(proxy => {
  417. const model = this._modelService.getModel(resource);
  418. if (!model) {
  419. return Promise.resolve(null);
  420. }
  421. const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
  422. const wordDef = wordDefRegExp.source;
  423. const wordDefFlags = regExpFlags(wordDefRegExp);
  424. return proxy.computeWordRanges(resource.toString(), range, wordDef, wordDefFlags);
  425. });
  426. }
  427. navigateValueSet(resource, range, up) {
  428. return this._withSyncedResources([resource]).then(proxy => {
  429. const model = this._modelService.getModel(resource);
  430. if (!model) {
  431. return null;
  432. }
  433. const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
  434. const wordDef = wordDefRegExp.source;
  435. const wordDefFlags = regExpFlags(wordDefRegExp);
  436. return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags);
  437. });
  438. }
  439. dispose() {
  440. super.dispose();
  441. this._disposed = true;
  442. }
  443. }