b7cad0153335700e9c7857505cdd9104a7d3d6588da6fa1e3c34b88e975e0ceffc0bb4a466c3b42a3bcaac3d17a194ac8eea44ff65b130f6ec1af560967dab 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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. var _a;
  24. import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
  25. import { registerEditorContribution } from '../../../browser/editorExtensions.js';
  26. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  27. import { OutlineModel, OutlineElement } from '../../documentSymbols/browser/outlineModel.js';
  28. import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
  29. import * as dom from '../../../../base/browser/dom.js';
  30. import { createStringBuilder } from '../../../common/core/stringBuilder.js';
  31. import { RenderLineInput, renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';
  32. import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
  33. import { RunOnceScheduler } from '../../../../base/common/async.js';
  34. import { Position } from '../../../common/core/position.js';
  35. let StickyScrollController = class StickyScrollController extends Disposable {
  36. constructor(editor, _languageFeaturesService) {
  37. super();
  38. this._sessionStore = new DisposableStore();
  39. this._ranges = [];
  40. this._rangesVersionId = 0;
  41. this._editor = editor;
  42. this._languageFeaturesService = _languageFeaturesService;
  43. this.stickyScrollWidget = new StickyScrollWidget(this._editor);
  44. this._register(this._editor.onDidChangeConfiguration(e => {
  45. if (e.hasChanged(34 /* EditorOption.experimental */)) {
  46. this.onConfigurationChange();
  47. }
  48. }));
  49. this._updateSoon = this._register(new RunOnceScheduler(() => this._update(true), 50));
  50. this.onConfigurationChange();
  51. }
  52. onConfigurationChange() {
  53. const options = this._editor.getOption(34 /* EditorOption.experimental */);
  54. if (options.stickyScroll.enabled === false) {
  55. this.stickyScrollWidget.emptyRootNode();
  56. this._editor.removeOverlayWidget(this.stickyScrollWidget);
  57. this._sessionStore.clear();
  58. return;
  59. }
  60. else {
  61. this._editor.addOverlayWidget(this.stickyScrollWidget);
  62. this._sessionStore.add(this._editor.onDidChangeModel(() => this._update(true)));
  63. this._sessionStore.add(this._editor.onDidScrollChange(() => this._update(false)));
  64. this._sessionStore.add(this._editor.onDidChangeHiddenAreas(() => this._update(true)));
  65. this._sessionStore.add(this._editor.onDidChangeModelTokens((e) => this._onTokensChange(e)));
  66. this._sessionStore.add(this._editor.onDidChangeModelContent(() => this._updateSoon.schedule()));
  67. this._sessionStore.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => this._update(true)));
  68. this._update(true);
  69. }
  70. }
  71. _needsUpdate(event) {
  72. const stickyLineNumbers = this.stickyScrollWidget.getCurrentLines();
  73. for (const stickyLineNumber of stickyLineNumbers) {
  74. for (const range of event.ranges) {
  75. if (stickyLineNumber >= range.fromLineNumber && stickyLineNumber <= range.toLineNumber) {
  76. return true;
  77. }
  78. }
  79. }
  80. return false;
  81. }
  82. _onTokensChange(event) {
  83. if (this._needsUpdate(event)) {
  84. this._update(false);
  85. }
  86. }
  87. _update(updateOutline = false) {
  88. var _a, _b;
  89. return __awaiter(this, void 0, void 0, function* () {
  90. if (updateOutline) {
  91. (_a = this._cts) === null || _a === void 0 ? void 0 : _a.dispose(true);
  92. this._cts = new CancellationTokenSource();
  93. yield this._updateOutlineModel(this._cts.token);
  94. }
  95. const hiddenRanges = (_b = this._editor._getViewModel()) === null || _b === void 0 ? void 0 : _b.getHiddenAreas();
  96. if (hiddenRanges) {
  97. for (const hiddenRange of hiddenRanges) {
  98. this._ranges = this._ranges.filter(range => { return !(range[0] >= hiddenRange.startLineNumber && range[1] <= hiddenRange.endLineNumber + 1); });
  99. }
  100. }
  101. this._renderStickyScroll();
  102. });
  103. }
  104. _findLineRanges(outlineElement, depth) {
  105. if (outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.children.size) {
  106. let didRecursion = false;
  107. for (const outline of outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.children.values()) {
  108. const kind = outline.symbol.kind;
  109. if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */ || kind === 1 /* SymbolKind.Module */) {
  110. didRecursion = true;
  111. this._findLineRanges(outline, depth + 1);
  112. }
  113. }
  114. if (!didRecursion) {
  115. this._addOutlineRanges(outlineElement, depth);
  116. }
  117. }
  118. else {
  119. this._addOutlineRanges(outlineElement, depth);
  120. }
  121. }
  122. _addOutlineRanges(outlineElement, depth) {
  123. let currentStartLine = 0;
  124. let currentEndLine = 0;
  125. while (outlineElement) {
  126. const kind = outlineElement.symbol.kind;
  127. if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */ || kind === 1 /* SymbolKind.Module */) {
  128. currentStartLine = outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.symbol.range.startLineNumber;
  129. currentEndLine = outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.symbol.range.endLineNumber;
  130. this._ranges.push([currentStartLine, currentEndLine, depth]);
  131. depth--;
  132. }
  133. if (outlineElement.parent instanceof OutlineElement) {
  134. outlineElement = outlineElement.parent;
  135. }
  136. else {
  137. break;
  138. }
  139. }
  140. }
  141. _updateOutlineModel(token) {
  142. return __awaiter(this, void 0, void 0, function* () {
  143. if (this._editor.hasModel()) {
  144. const model = this._editor.getModel();
  145. const modelVersionId = model.getVersionId();
  146. const outlineModel = yield OutlineModel.create(this._languageFeaturesService.documentSymbolProvider, model, token);
  147. if (token.isCancellationRequested) {
  148. return;
  149. }
  150. this._ranges = [];
  151. this._rangesVersionId = modelVersionId;
  152. for (const outline of outlineModel.children.values()) {
  153. if (outline instanceof OutlineElement) {
  154. const kind = outline.symbol.kind;
  155. if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */ || kind === 1 /* SymbolKind.Module */) {
  156. this._findLineRanges(outline, 1);
  157. }
  158. else {
  159. this._findLineRanges(outline, 0);
  160. }
  161. }
  162. this._ranges = this._ranges.sort(function (a, b) {
  163. if (a[0] !== b[0]) {
  164. return a[0] - b[0];
  165. }
  166. else if (a[1] !== b[1]) {
  167. return b[1] - a[1];
  168. }
  169. else {
  170. return a[2] - b[2];
  171. }
  172. });
  173. let previous = [];
  174. for (const [index, arr] of this._ranges.entries()) {
  175. const [start, end, _depth] = arr;
  176. if (previous[0] === start && previous[1] === end) {
  177. this._ranges.splice(index, 1);
  178. }
  179. else {
  180. previous = arr;
  181. }
  182. }
  183. }
  184. }
  185. });
  186. }
  187. _renderStickyScroll() {
  188. if (!(this._editor.hasModel())) {
  189. return;
  190. }
  191. const lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */);
  192. const model = this._editor.getModel();
  193. if (this._rangesVersionId !== model.getVersionId()) {
  194. // Old _ranges not updated yet
  195. return;
  196. }
  197. const scrollTop = this._editor.getScrollTop();
  198. this.stickyScrollWidget.emptyRootNode();
  199. const beginningLinesConsidered = new Set();
  200. for (const [index, arr] of this._ranges.entries()) {
  201. const [start, end, depth] = arr;
  202. if (end - start > 0 && model.getLineContent(start) !== '') {
  203. const topOfElementAtDepth = (depth - 1) * lineHeight;
  204. const bottomOfElementAtDepth = depth * lineHeight;
  205. const bottomOfBeginningLine = this._editor.getBottomForLineNumber(start) - scrollTop;
  206. const topOfEndLine = this._editor.getTopForLineNumber(end) - scrollTop;
  207. const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop;
  208. if (!beginningLinesConsidered.has(start)) {
  209. if (topOfElementAtDepth >= topOfEndLine - 1 && topOfElementAtDepth < bottomOfEndLine - 2) {
  210. beginningLinesConsidered.add(start);
  211. this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(start, depth, this._editor, -1, bottomOfEndLine - bottomOfElementAtDepth));
  212. break;
  213. }
  214. else if (bottomOfElementAtDepth > bottomOfBeginningLine && bottomOfElementAtDepth < bottomOfEndLine - 1) {
  215. beginningLinesConsidered.add(start);
  216. this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(start, depth, this._editor, 0, 0));
  217. }
  218. }
  219. else {
  220. this._ranges.splice(index, 1);
  221. }
  222. }
  223. }
  224. this.stickyScrollWidget.updateRootNode();
  225. }
  226. dispose() {
  227. super.dispose();
  228. this._sessionStore.dispose();
  229. }
  230. };
  231. StickyScrollController.ID = 'store.contrib.stickyScrollController';
  232. StickyScrollController = __decorate([
  233. __param(1, ILanguageFeaturesService)
  234. ], StickyScrollController);
  235. const _ttPolicy = (_a = window.trustedTypes) === null || _a === void 0 ? void 0 : _a.createPolicy('stickyScrollViewLayer', { createHTML: value => value });
  236. class StickyScrollCodeLine {
  237. constructor(_lineNumber, _depth, _editor, _zIndex, _relativePosition) {
  238. this._lineNumber = _lineNumber;
  239. this._depth = _depth;
  240. this._editor = _editor;
  241. this._zIndex = _zIndex;
  242. this._relativePosition = _relativePosition;
  243. this.effectiveLineHeight = 0;
  244. this.effectiveLineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */) + this._relativePosition;
  245. }
  246. get lineNumber() {
  247. return this._lineNumber;
  248. }
  249. getDomNode() {
  250. const root = document.createElement('div');
  251. const viewModel = this._editor._getViewModel();
  252. const viewLineNumber = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(this._lineNumber, 1)).lineNumber;
  253. const lineRenderingData = viewModel.getViewLineRenderingData(viewLineNumber);
  254. let actualInlineDecorations;
  255. try {
  256. actualInlineDecorations = LineDecoration.filter(lineRenderingData.inlineDecorations, viewLineNumber, lineRenderingData.minColumn, lineRenderingData.maxColumn);
  257. }
  258. catch (err) {
  259. actualInlineDecorations = [];
  260. }
  261. const renderLineInput = new RenderLineInput(true, true, lineRenderingData.content, lineRenderingData.continuesWithWrappedLine, lineRenderingData.isBasicASCII, lineRenderingData.containsRTL, 0, lineRenderingData.tokens, actualInlineDecorations, lineRenderingData.tabSize, lineRenderingData.startVisibleColumn, 1, 1, 1, 100, 'none', true, true, null);
  262. const sb = createStringBuilder(400);
  263. renderViewLine(renderLineInput, sb);
  264. let newLine;
  265. if (_ttPolicy) {
  266. newLine = _ttPolicy.createHTML(sb.build());
  267. }
  268. else {
  269. newLine = sb.build();
  270. }
  271. const lineHTMLNode = document.createElement('span');
  272. lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  273. lineHTMLNode.style.overflow = 'hidden';
  274. lineHTMLNode.style.whiteSpace = 'nowrap';
  275. lineHTMLNode.style.display = 'inline-block';
  276. lineHTMLNode.style.lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */).toString() + 'px';
  277. lineHTMLNode.innerHTML = newLine;
  278. const lineNumberHTMLNode = document.createElement('span');
  279. lineNumberHTMLNode.style.width = this._editor.getLayoutInfo().contentLeft.toString() + 'px';
  280. lineNumberHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  281. lineNumberHTMLNode.style.color = 'var(--vscode-editorLineNumber-foreground)';
  282. lineNumberHTMLNode.style.display = 'inline-block';
  283. lineNumberHTMLNode.style.lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */).toString() + 'px';
  284. const innerLineNumberHTML = document.createElement('span');
  285. innerLineNumberHTML.innerText = this._lineNumber.toString();
  286. innerLineNumberHTML.style.paddingLeft = this._editor.getLayoutInfo().lineNumbersLeft.toString() + 'px';
  287. innerLineNumberHTML.style.width = this._editor.getLayoutInfo().lineNumbersWidth.toString() + 'px';
  288. innerLineNumberHTML.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  289. innerLineNumberHTML.style.textAlign = 'right';
  290. innerLineNumberHTML.style.float = 'left';
  291. innerLineNumberHTML.style.lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */).toString() + 'px';
  292. lineNumberHTMLNode.appendChild(innerLineNumberHTML);
  293. root.onclick = e => {
  294. e.stopPropagation();
  295. e.preventDefault();
  296. this._editor.revealPosition({ lineNumber: this._lineNumber - this._depth + 1, column: 1 });
  297. };
  298. root.onmouseover = e => {
  299. innerLineNumberHTML.style.background = `var(--vscode-editorStickyScrollHover-background)`;
  300. lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScrollHover-background)`;
  301. lineNumberHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScrollHover-background)`;
  302. root.style.backgroundColor = `var(--vscode-editorStickyScrollHover-background)`;
  303. innerLineNumberHTML.style.cursor = `pointer`;
  304. lineHTMLNode.style.cursor = `pointer`;
  305. root.style.cursor = `pointer`;
  306. lineNumberHTMLNode.style.cursor = `pointer`;
  307. };
  308. root.onmouseleave = e => {
  309. innerLineNumberHTML.style.background = `var(--vscode-editorStickyScroll-background)`;
  310. lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  311. lineNumberHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  312. root.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  313. };
  314. this._editor.applyFontInfo(lineHTMLNode);
  315. this._editor.applyFontInfo(innerLineNumberHTML);
  316. root.appendChild(lineNumberHTMLNode);
  317. root.appendChild(lineHTMLNode);
  318. root.style.zIndex = this._zIndex.toString();
  319. root.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  320. root.style.overflow = 'hidden';
  321. root.style.whiteSpace = 'nowrap';
  322. root.style.width = '100%';
  323. root.style.lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */).toString() + 'px';
  324. root.style.height = this._editor.getOption(61 /* EditorOption.lineHeight */).toString() + 'px';
  325. // Special case for last line of sticky scroll
  326. if (this._relativePosition) {
  327. root.style.position = 'relative';
  328. root.style.top = this._relativePosition + 'px';
  329. root.style.width = '100%';
  330. }
  331. return root;
  332. }
  333. }
  334. class StickyScrollWidget {
  335. constructor(_editor) {
  336. this._editor = _editor;
  337. this.arrayOfCodeLines = [];
  338. this.rootDomNode = document.createElement('div');
  339. this.rootDomNode = document.createElement('div');
  340. this.rootDomNode.style.width = '100%';
  341. this.rootDomNode.style.boxShadow = `var(--vscode-scrollbar-shadow) 0 6px 6px -6px`;
  342. }
  343. getCurrentLines() {
  344. const widgetLineRange = [];
  345. for (const codeLine of this.arrayOfCodeLines) {
  346. widgetLineRange.push(codeLine.lineNumber);
  347. }
  348. return widgetLineRange;
  349. }
  350. pushCodeLine(codeLine) {
  351. this.arrayOfCodeLines.push(codeLine);
  352. }
  353. updateRootNode() {
  354. let widgetHeight = 0;
  355. for (const line of this.arrayOfCodeLines) {
  356. widgetHeight += line.effectiveLineHeight;
  357. this.rootDomNode.appendChild(line.getDomNode());
  358. }
  359. this.rootDomNode.style.height = widgetHeight.toString() + 'px';
  360. }
  361. emptyRootNode() {
  362. this.arrayOfCodeLines.length = 0;
  363. dom.clearNode(this.rootDomNode);
  364. }
  365. getId() {
  366. return 'editor.contrib.stickyScrollWidget';
  367. }
  368. getDomNode() {
  369. this.rootDomNode.style.zIndex = '2';
  370. this.rootDomNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
  371. return this.rootDomNode;
  372. }
  373. getPosition() {
  374. return {
  375. preference: null
  376. };
  377. }
  378. }
  379. registerEditorContribution(StickyScrollController.ID, StickyScrollController);