referencesModel.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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 { onUnexpectedError } from '../../../base/common/errors.js';
  15. import { Emitter } from '../../../base/common/event.js';
  16. import { defaultGenerator } from '../../../base/common/idGenerator.js';
  17. import { dispose } from '../../../base/common/lifecycle.js';
  18. import { ResourceMap } from '../../../base/common/map.js';
  19. import { basename, extUri } from '../../../base/common/resources.js';
  20. import * as strings from '../../../base/common/strings.js';
  21. import { Range } from '../../common/core/range.js';
  22. import { localize } from '../../../nls.js';
  23. export class OneReference {
  24. constructor(isProviderFirst, parent, link, _rangeCallback) {
  25. this.isProviderFirst = isProviderFirst;
  26. this.parent = parent;
  27. this.link = link;
  28. this._rangeCallback = _rangeCallback;
  29. this.id = defaultGenerator.nextId();
  30. }
  31. get uri() {
  32. return this.link.uri;
  33. }
  34. get range() {
  35. var _a, _b;
  36. return (_b = (_a = this._range) !== null && _a !== void 0 ? _a : this.link.targetSelectionRange) !== null && _b !== void 0 ? _b : this.link.range;
  37. }
  38. set range(value) {
  39. this._range = value;
  40. this._rangeCallback(this);
  41. }
  42. get ariaMessage() {
  43. var _a;
  44. const preview = (_a = this.parent.getPreview(this)) === null || _a === void 0 ? void 0 : _a.preview(this.range);
  45. if (!preview) {
  46. return localize('aria.oneReference', "symbol in {0} on line {1} at column {2}", basename(this.uri), this.range.startLineNumber, this.range.startColumn);
  47. }
  48. else {
  49. return localize({ key: 'aria.oneReference.preview', comment: ['Placeholders are: 0: filename, 1:line number, 2: column number, 3: preview snippet of source code'] }, "symbol in {0} on line {1} at column {2}, {3}", basename(this.uri), this.range.startLineNumber, this.range.startColumn, preview.value);
  50. }
  51. }
  52. }
  53. export class FilePreview {
  54. constructor(_modelReference) {
  55. this._modelReference = _modelReference;
  56. }
  57. dispose() {
  58. this._modelReference.dispose();
  59. }
  60. preview(range, n = 8) {
  61. const model = this._modelReference.object.textEditorModel;
  62. if (!model) {
  63. return undefined;
  64. }
  65. const { startLineNumber, startColumn, endLineNumber, endColumn } = range;
  66. const word = model.getWordUntilPosition({ lineNumber: startLineNumber, column: startColumn - n });
  67. const beforeRange = new Range(startLineNumber, word.startColumn, startLineNumber, startColumn);
  68. const afterRange = new Range(endLineNumber, endColumn, endLineNumber, 1073741824 /* MAX_SAFE_SMALL_INTEGER */);
  69. const before = model.getValueInRange(beforeRange).replace(/^\s+/, '');
  70. const inside = model.getValueInRange(range);
  71. const after = model.getValueInRange(afterRange).replace(/\s+$/, '');
  72. return {
  73. value: before + inside + after,
  74. highlight: { start: before.length, end: before.length + inside.length }
  75. };
  76. }
  77. }
  78. export class FileReferences {
  79. constructor(parent, uri) {
  80. this.parent = parent;
  81. this.uri = uri;
  82. this.children = [];
  83. this._previews = new ResourceMap();
  84. }
  85. dispose() {
  86. dispose(this._previews.values());
  87. this._previews.clear();
  88. }
  89. getPreview(child) {
  90. return this._previews.get(child.uri);
  91. }
  92. get ariaMessage() {
  93. const len = this.children.length;
  94. if (len === 1) {
  95. return localize('aria.fileReferences.1', "1 symbol in {0}, full path {1}", basename(this.uri), this.uri.fsPath);
  96. }
  97. else {
  98. return localize('aria.fileReferences.N', "{0} symbols in {1}, full path {2}", len, basename(this.uri), this.uri.fsPath);
  99. }
  100. }
  101. resolve(textModelResolverService) {
  102. return __awaiter(this, void 0, void 0, function* () {
  103. if (this._previews.size !== 0) {
  104. return this;
  105. }
  106. for (let child of this.children) {
  107. if (this._previews.has(child.uri)) {
  108. continue;
  109. }
  110. try {
  111. const ref = yield textModelResolverService.createModelReference(child.uri);
  112. this._previews.set(child.uri, new FilePreview(ref));
  113. }
  114. catch (err) {
  115. onUnexpectedError(err);
  116. }
  117. }
  118. return this;
  119. });
  120. }
  121. }
  122. export class ReferencesModel {
  123. constructor(links, title) {
  124. this.groups = [];
  125. this.references = [];
  126. this._onDidChangeReferenceRange = new Emitter();
  127. this.onDidChangeReferenceRange = this._onDidChangeReferenceRange.event;
  128. this._links = links;
  129. this._title = title;
  130. // grouping and sorting
  131. const [providersFirst] = links;
  132. links.sort(ReferencesModel._compareReferences);
  133. let current;
  134. for (let link of links) {
  135. if (!current || !extUri.isEqual(current.uri, link.uri, true)) {
  136. // new group
  137. current = new FileReferences(this, link.uri);
  138. this.groups.push(current);
  139. }
  140. // append, check for equality first!
  141. if (current.children.length === 0 || ReferencesModel._compareReferences(link, current.children[current.children.length - 1]) !== 0) {
  142. const oneRef = new OneReference(providersFirst === link, current, link, ref => this._onDidChangeReferenceRange.fire(ref));
  143. this.references.push(oneRef);
  144. current.children.push(oneRef);
  145. }
  146. }
  147. }
  148. dispose() {
  149. dispose(this.groups);
  150. this._onDidChangeReferenceRange.dispose();
  151. this.groups.length = 0;
  152. }
  153. clone() {
  154. return new ReferencesModel(this._links, this._title);
  155. }
  156. get title() {
  157. return this._title;
  158. }
  159. get isEmpty() {
  160. return this.groups.length === 0;
  161. }
  162. get ariaMessage() {
  163. if (this.isEmpty) {
  164. return localize('aria.result.0', "No results found");
  165. }
  166. else if (this.references.length === 1) {
  167. return localize('aria.result.1', "Found 1 symbol in {0}", this.references[0].uri.fsPath);
  168. }
  169. else if (this.groups.length === 1) {
  170. return localize('aria.result.n1', "Found {0} symbols in {1}", this.references.length, this.groups[0].uri.fsPath);
  171. }
  172. else {
  173. return localize('aria.result.nm', "Found {0} symbols in {1} files", this.references.length, this.groups.length);
  174. }
  175. }
  176. nextOrPreviousReference(reference, next) {
  177. let { parent } = reference;
  178. let idx = parent.children.indexOf(reference);
  179. let childCount = parent.children.length;
  180. let groupCount = parent.parent.groups.length;
  181. if (groupCount === 1 || next && idx + 1 < childCount || !next && idx > 0) {
  182. // cycling within one file
  183. if (next) {
  184. idx = (idx + 1) % childCount;
  185. }
  186. else {
  187. idx = (idx + childCount - 1) % childCount;
  188. }
  189. return parent.children[idx];
  190. }
  191. idx = parent.parent.groups.indexOf(parent);
  192. if (next) {
  193. idx = (idx + 1) % groupCount;
  194. return parent.parent.groups[idx].children[0];
  195. }
  196. else {
  197. idx = (idx + groupCount - 1) % groupCount;
  198. return parent.parent.groups[idx].children[parent.parent.groups[idx].children.length - 1];
  199. }
  200. }
  201. nearestReference(resource, position) {
  202. const nearest = this.references.map((ref, idx) => {
  203. return {
  204. idx,
  205. prefixLen: strings.commonPrefixLength(ref.uri.toString(), resource.toString()),
  206. offsetDist: Math.abs(ref.range.startLineNumber - position.lineNumber) * 100 + Math.abs(ref.range.startColumn - position.column)
  207. };
  208. }).sort((a, b) => {
  209. if (a.prefixLen > b.prefixLen) {
  210. return -1;
  211. }
  212. else if (a.prefixLen < b.prefixLen) {
  213. return 1;
  214. }
  215. else if (a.offsetDist < b.offsetDist) {
  216. return -1;
  217. }
  218. else if (a.offsetDist > b.offsetDist) {
  219. return 1;
  220. }
  221. else {
  222. return 0;
  223. }
  224. })[0];
  225. if (nearest) {
  226. return this.references[nearest.idx];
  227. }
  228. return undefined;
  229. }
  230. referenceAt(resource, position) {
  231. for (const ref of this.references) {
  232. if (ref.uri.toString() === resource.toString()) {
  233. if (Range.containsPosition(ref.range, position)) {
  234. return ref;
  235. }
  236. }
  237. }
  238. return undefined;
  239. }
  240. firstReference() {
  241. for (const ref of this.references) {
  242. if (ref.isProviderFirst) {
  243. return ref;
  244. }
  245. }
  246. return this.references[0];
  247. }
  248. static _compareReferences(a, b) {
  249. return extUri.compare(a.uri, b.uri) || Range.compareRangesUsingStarts(a.range, b.range);
  250. }
  251. }