97ba5c60357d9824beca810358f9425239042c21caddad03e5a1985daf34d03e56f88f27fafc036a0db8d934d4caea505e7277b565505763961f395c96d458 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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 { Range } from '../../../common/core/range.js';
  6. import { MinimapPosition, OverviewRulerLane } from '../../../common/model.js';
  7. import { ModelDecorationOptions } from '../../../common/model/textModel.js';
  8. import { minimapFindMatch, overviewRulerFindMatchForeground } from '../../../../platform/theme/common/colorRegistry.js';
  9. import { themeColorFromId } from '../../../../platform/theme/common/themeService.js';
  10. export class FindDecorations {
  11. constructor(editor) {
  12. this._editor = editor;
  13. this._decorations = [];
  14. this._overviewRulerApproximateDecorations = [];
  15. this._findScopeDecorationIds = [];
  16. this._rangeHighlightDecorationId = null;
  17. this._highlightedDecorationId = null;
  18. this._startPosition = this._editor.getPosition();
  19. }
  20. dispose() {
  21. this._editor.removeDecorations(this._allDecorations());
  22. this._decorations = [];
  23. this._overviewRulerApproximateDecorations = [];
  24. this._findScopeDecorationIds = [];
  25. this._rangeHighlightDecorationId = null;
  26. this._highlightedDecorationId = null;
  27. }
  28. reset() {
  29. this._decorations = [];
  30. this._overviewRulerApproximateDecorations = [];
  31. this._findScopeDecorationIds = [];
  32. this._rangeHighlightDecorationId = null;
  33. this._highlightedDecorationId = null;
  34. }
  35. getCount() {
  36. return this._decorations.length;
  37. }
  38. /** @deprecated use getFindScopes to support multiple selections */
  39. getFindScope() {
  40. if (this._findScopeDecorationIds[0]) {
  41. return this._editor.getModel().getDecorationRange(this._findScopeDecorationIds[0]);
  42. }
  43. return null;
  44. }
  45. getFindScopes() {
  46. if (this._findScopeDecorationIds.length) {
  47. const scopes = this._findScopeDecorationIds.map(findScopeDecorationId => this._editor.getModel().getDecorationRange(findScopeDecorationId)).filter(element => !!element);
  48. if (scopes.length) {
  49. return scopes;
  50. }
  51. }
  52. return null;
  53. }
  54. getStartPosition() {
  55. return this._startPosition;
  56. }
  57. setStartPosition(newStartPosition) {
  58. this._startPosition = newStartPosition;
  59. this.setCurrentFindMatch(null);
  60. }
  61. _getDecorationIndex(decorationId) {
  62. const index = this._decorations.indexOf(decorationId);
  63. if (index >= 0) {
  64. return index + 1;
  65. }
  66. return 1;
  67. }
  68. getCurrentMatchesPosition(desiredRange) {
  69. const candidates = this._editor.getModel().getDecorationsInRange(desiredRange);
  70. for (const candidate of candidates) {
  71. const candidateOpts = candidate.options;
  72. if (candidateOpts === FindDecorations._FIND_MATCH_DECORATION || candidateOpts === FindDecorations._CURRENT_FIND_MATCH_DECORATION) {
  73. return this._getDecorationIndex(candidate.id);
  74. }
  75. }
  76. // We don't know the current match position, so returns zero to show '?' in find widget
  77. return 0;
  78. }
  79. setCurrentFindMatch(nextMatch) {
  80. let newCurrentDecorationId = null;
  81. let matchPosition = 0;
  82. if (nextMatch) {
  83. for (let i = 0, len = this._decorations.length; i < len; i++) {
  84. const range = this._editor.getModel().getDecorationRange(this._decorations[i]);
  85. if (nextMatch.equalsRange(range)) {
  86. newCurrentDecorationId = this._decorations[i];
  87. matchPosition = (i + 1);
  88. break;
  89. }
  90. }
  91. }
  92. if (this._highlightedDecorationId !== null || newCurrentDecorationId !== null) {
  93. this._editor.changeDecorations((changeAccessor) => {
  94. if (this._highlightedDecorationId !== null) {
  95. changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations._FIND_MATCH_DECORATION);
  96. this._highlightedDecorationId = null;
  97. }
  98. if (newCurrentDecorationId !== null) {
  99. this._highlightedDecorationId = newCurrentDecorationId;
  100. changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations._CURRENT_FIND_MATCH_DECORATION);
  101. }
  102. if (this._rangeHighlightDecorationId !== null) {
  103. changeAccessor.removeDecoration(this._rangeHighlightDecorationId);
  104. this._rangeHighlightDecorationId = null;
  105. }
  106. if (newCurrentDecorationId !== null) {
  107. let rng = this._editor.getModel().getDecorationRange(newCurrentDecorationId);
  108. if (rng.startLineNumber !== rng.endLineNumber && rng.endColumn === 1) {
  109. const lineBeforeEnd = rng.endLineNumber - 1;
  110. const lineBeforeEndMaxColumn = this._editor.getModel().getLineMaxColumn(lineBeforeEnd);
  111. rng = new Range(rng.startLineNumber, rng.startColumn, lineBeforeEnd, lineBeforeEndMaxColumn);
  112. }
  113. this._rangeHighlightDecorationId = changeAccessor.addDecoration(rng, FindDecorations._RANGE_HIGHLIGHT_DECORATION);
  114. }
  115. });
  116. }
  117. return matchPosition;
  118. }
  119. set(findMatches, findScopes) {
  120. this._editor.changeDecorations((accessor) => {
  121. let findMatchesOptions = FindDecorations._FIND_MATCH_DECORATION;
  122. const newOverviewRulerApproximateDecorations = [];
  123. if (findMatches.length > 1000) {
  124. // we go into a mode where the overview ruler gets "approximate" decorations
  125. // the reason is that the overview ruler paints all the decorations in the file and we don't want to cause freezes
  126. findMatchesOptions = FindDecorations._FIND_MATCH_NO_OVERVIEW_DECORATION;
  127. // approximate a distance in lines where matches should be merged
  128. const lineCount = this._editor.getModel().getLineCount();
  129. const height = this._editor.getLayoutInfo().height;
  130. const approxPixelsPerLine = height / lineCount;
  131. const mergeLinesDelta = Math.max(2, Math.ceil(3 / approxPixelsPerLine));
  132. // merge decorations as much as possible
  133. let prevStartLineNumber = findMatches[0].range.startLineNumber;
  134. let prevEndLineNumber = findMatches[0].range.endLineNumber;
  135. for (let i = 1, len = findMatches.length; i < len; i++) {
  136. const range = findMatches[i].range;
  137. if (prevEndLineNumber + mergeLinesDelta >= range.startLineNumber) {
  138. if (range.endLineNumber > prevEndLineNumber) {
  139. prevEndLineNumber = range.endLineNumber;
  140. }
  141. }
  142. else {
  143. newOverviewRulerApproximateDecorations.push({
  144. range: new Range(prevStartLineNumber, 1, prevEndLineNumber, 1),
  145. options: FindDecorations._FIND_MATCH_ONLY_OVERVIEW_DECORATION
  146. });
  147. prevStartLineNumber = range.startLineNumber;
  148. prevEndLineNumber = range.endLineNumber;
  149. }
  150. }
  151. newOverviewRulerApproximateDecorations.push({
  152. range: new Range(prevStartLineNumber, 1, prevEndLineNumber, 1),
  153. options: FindDecorations._FIND_MATCH_ONLY_OVERVIEW_DECORATION
  154. });
  155. }
  156. // Find matches
  157. const newFindMatchesDecorations = new Array(findMatches.length);
  158. for (let i = 0, len = findMatches.length; i < len; i++) {
  159. newFindMatchesDecorations[i] = {
  160. range: findMatches[i].range,
  161. options: findMatchesOptions
  162. };
  163. }
  164. this._decorations = accessor.deltaDecorations(this._decorations, newFindMatchesDecorations);
  165. // Overview ruler approximate decorations
  166. this._overviewRulerApproximateDecorations = accessor.deltaDecorations(this._overviewRulerApproximateDecorations, newOverviewRulerApproximateDecorations);
  167. // Range highlight
  168. if (this._rangeHighlightDecorationId) {
  169. accessor.removeDecoration(this._rangeHighlightDecorationId);
  170. this._rangeHighlightDecorationId = null;
  171. }
  172. // Find scope
  173. if (this._findScopeDecorationIds.length) {
  174. this._findScopeDecorationIds.forEach(findScopeDecorationId => accessor.removeDecoration(findScopeDecorationId));
  175. this._findScopeDecorationIds = [];
  176. }
  177. if (findScopes === null || findScopes === void 0 ? void 0 : findScopes.length) {
  178. this._findScopeDecorationIds = findScopes.map(findScope => accessor.addDecoration(findScope, FindDecorations._FIND_SCOPE_DECORATION));
  179. }
  180. });
  181. }
  182. matchBeforePosition(position) {
  183. if (this._decorations.length === 0) {
  184. return null;
  185. }
  186. for (let i = this._decorations.length - 1; i >= 0; i--) {
  187. const decorationId = this._decorations[i];
  188. const r = this._editor.getModel().getDecorationRange(decorationId);
  189. if (!r || r.endLineNumber > position.lineNumber) {
  190. continue;
  191. }
  192. if (r.endLineNumber < position.lineNumber) {
  193. return r;
  194. }
  195. if (r.endColumn > position.column) {
  196. continue;
  197. }
  198. return r;
  199. }
  200. return this._editor.getModel().getDecorationRange(this._decorations[this._decorations.length - 1]);
  201. }
  202. matchAfterPosition(position) {
  203. if (this._decorations.length === 0) {
  204. return null;
  205. }
  206. for (let i = 0, len = this._decorations.length; i < len; i++) {
  207. const decorationId = this._decorations[i];
  208. const r = this._editor.getModel().getDecorationRange(decorationId);
  209. if (!r || r.startLineNumber < position.lineNumber) {
  210. continue;
  211. }
  212. if (r.startLineNumber > position.lineNumber) {
  213. return r;
  214. }
  215. if (r.startColumn < position.column) {
  216. continue;
  217. }
  218. return r;
  219. }
  220. return this._editor.getModel().getDecorationRange(this._decorations[0]);
  221. }
  222. _allDecorations() {
  223. let result = [];
  224. result = result.concat(this._decorations);
  225. result = result.concat(this._overviewRulerApproximateDecorations);
  226. if (this._findScopeDecorationIds.length) {
  227. result.push(...this._findScopeDecorationIds);
  228. }
  229. if (this._rangeHighlightDecorationId) {
  230. result.push(this._rangeHighlightDecorationId);
  231. }
  232. return result;
  233. }
  234. }
  235. FindDecorations._CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
  236. description: 'current-find-match',
  237. stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */,
  238. zIndex: 13,
  239. className: 'currentFindMatch',
  240. showIfCollapsed: true,
  241. overviewRuler: {
  242. color: themeColorFromId(overviewRulerFindMatchForeground),
  243. position: OverviewRulerLane.Center
  244. },
  245. minimap: {
  246. color: themeColorFromId(minimapFindMatch),
  247. position: MinimapPosition.Inline
  248. }
  249. });
  250. FindDecorations._FIND_MATCH_DECORATION = ModelDecorationOptions.register({
  251. description: 'find-match',
  252. stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */,
  253. zIndex: 10,
  254. className: 'findMatch',
  255. showIfCollapsed: true,
  256. overviewRuler: {
  257. color: themeColorFromId(overviewRulerFindMatchForeground),
  258. position: OverviewRulerLane.Center
  259. },
  260. minimap: {
  261. color: themeColorFromId(minimapFindMatch),
  262. position: MinimapPosition.Inline
  263. }
  264. });
  265. FindDecorations._FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({
  266. description: 'find-match-no-overview',
  267. stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */,
  268. className: 'findMatch',
  269. showIfCollapsed: true
  270. });
  271. FindDecorations._FIND_MATCH_ONLY_OVERVIEW_DECORATION = ModelDecorationOptions.register({
  272. description: 'find-match-only-overview',
  273. stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */,
  274. overviewRuler: {
  275. color: themeColorFromId(overviewRulerFindMatchForeground),
  276. position: OverviewRulerLane.Center
  277. }
  278. });
  279. FindDecorations._RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({
  280. description: 'find-range-highlight',
  281. stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */,
  282. className: 'rangeHighlight',
  283. isWholeLine: true
  284. });
  285. FindDecorations._FIND_SCOPE_DECORATION = ModelDecorationOptions.register({
  286. description: 'find-scope',
  287. className: 'findScope',
  288. isWholeLine: true
  289. });