languageFeatureRegistry.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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 { Emitter } from '../../base/common/event.js';
  6. import { toDisposable } from '../../base/common/lifecycle.js';
  7. import { shouldSynchronizeModel } from './model.js';
  8. import { score } from './languageSelector.js';
  9. function isExclusive(selector) {
  10. if (typeof selector === 'string') {
  11. return false;
  12. }
  13. else if (Array.isArray(selector)) {
  14. return selector.every(isExclusive);
  15. }
  16. else {
  17. return !!selector.exclusive; // TODO: microsoft/TypeScript#42768
  18. }
  19. }
  20. class MatchCandidate {
  21. constructor(uri, languageId, notebookUri, notebookType) {
  22. this.uri = uri;
  23. this.languageId = languageId;
  24. this.notebookUri = notebookUri;
  25. this.notebookType = notebookType;
  26. }
  27. equals(other) {
  28. var _a, _b;
  29. return this.notebookType === other.notebookType
  30. && this.languageId === other.languageId
  31. && this.uri.toString() === other.uri.toString()
  32. && ((_a = this.notebookUri) === null || _a === void 0 ? void 0 : _a.toString()) === ((_b = other.notebookUri) === null || _b === void 0 ? void 0 : _b.toString());
  33. }
  34. }
  35. export class LanguageFeatureRegistry {
  36. constructor(_notebookInfoResolver) {
  37. this._notebookInfoResolver = _notebookInfoResolver;
  38. this._clock = 0;
  39. this._entries = [];
  40. this._onDidChange = new Emitter();
  41. this.onDidChange = this._onDidChange.event;
  42. }
  43. register(selector, provider) {
  44. let entry = {
  45. selector,
  46. provider,
  47. _score: -1,
  48. _time: this._clock++
  49. };
  50. this._entries.push(entry);
  51. this._lastCandidate = undefined;
  52. this._onDidChange.fire(this._entries.length);
  53. return toDisposable(() => {
  54. if (entry) {
  55. const idx = this._entries.indexOf(entry);
  56. if (idx >= 0) {
  57. this._entries.splice(idx, 1);
  58. this._lastCandidate = undefined;
  59. this._onDidChange.fire(this._entries.length);
  60. entry = undefined;
  61. }
  62. }
  63. });
  64. }
  65. has(model) {
  66. return this.all(model).length > 0;
  67. }
  68. all(model) {
  69. if (!model) {
  70. return [];
  71. }
  72. this._updateScores(model);
  73. const result = [];
  74. // from registry
  75. for (const entry of this._entries) {
  76. if (entry._score > 0) {
  77. result.push(entry.provider);
  78. }
  79. }
  80. return result;
  81. }
  82. ordered(model) {
  83. const result = [];
  84. this._orderedForEach(model, entry => result.push(entry.provider));
  85. return result;
  86. }
  87. orderedGroups(model) {
  88. const result = [];
  89. let lastBucket;
  90. let lastBucketScore;
  91. this._orderedForEach(model, entry => {
  92. if (lastBucket && lastBucketScore === entry._score) {
  93. lastBucket.push(entry.provider);
  94. }
  95. else {
  96. lastBucketScore = entry._score;
  97. lastBucket = [entry.provider];
  98. result.push(lastBucket);
  99. }
  100. });
  101. return result;
  102. }
  103. _orderedForEach(model, callback) {
  104. this._updateScores(model);
  105. for (const entry of this._entries) {
  106. if (entry._score > 0) {
  107. callback(entry);
  108. }
  109. }
  110. }
  111. _updateScores(model) {
  112. var _a, _b;
  113. const notebookInfo = (_a = this._notebookInfoResolver) === null || _a === void 0 ? void 0 : _a.call(this, model.uri);
  114. // use the uri (scheme, pattern) of the notebook info iff we have one
  115. // otherwise it's the model's/document's uri
  116. const candidate = notebookInfo
  117. ? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type)
  118. : new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined);
  119. if ((_b = this._lastCandidate) === null || _b === void 0 ? void 0 : _b.equals(candidate)) {
  120. // nothing has changed
  121. return;
  122. }
  123. this._lastCandidate = candidate;
  124. for (const entry of this._entries) {
  125. entry._score = score(entry.selector, candidate.uri, candidate.languageId, shouldSynchronizeModel(model), candidate.notebookUri, candidate.notebookType);
  126. if (isExclusive(entry.selector) && entry._score > 0) {
  127. // support for one exclusive selector that overwrites
  128. // any other selector
  129. for (const entry of this._entries) {
  130. entry._score = 0;
  131. }
  132. entry._score = 1000;
  133. break;
  134. }
  135. }
  136. // needs sorting
  137. this._entries.sort(LanguageFeatureRegistry._compareByScoreAndTime);
  138. }
  139. static _compareByScoreAndTime(a, b) {
  140. if (a._score < b._score) {
  141. return 1;
  142. }
  143. else if (a._score > b._score) {
  144. return -1;
  145. }
  146. else if (a._time < b._time) {
  147. return 1;
  148. }
  149. else if (a._time > b._time) {
  150. return -1;
  151. }
  152. else {
  153. return 0;
  154. }
  155. }
  156. }