dee3e790cb57f928dfeaa9dcd833f71cd188796e07be624e5f82bf66a9dcd5b79bfac87efd1f2d88f5d171ccc2e0df1b133d0b1c58ae9a8e7bbe9670747f85 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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 _a;
  15. import * as dom from '../../../../base/browser/dom.js';
  16. import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
  17. import * as strings from '../../../../base/common/strings.js';
  18. import './ghostText.css';
  19. import { applyFontInfo } from '../../../browser/config/domFontInfo.js';
  20. import { EditorFontLigatures } from '../../../common/config/editorOptions.js';
  21. import { LineTokens } from '../../../common/tokens/lineTokens.js';
  22. import { Position } from '../../../common/core/position.js';
  23. import { Range } from '../../../common/core/range.js';
  24. import { createStringBuilder } from '../../../common/core/stringBuilder.js';
  25. import { InjectedTextCursorStops } from '../../../common/model.js';
  26. import { ILanguageService } from '../../../common/languages/language.js';
  27. import { ghostTextBackground, ghostTextBorder, ghostTextForeground } from '../../../common/core/editorColorRegistry.js';
  28. import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
  29. import { RenderLineInput, renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';
  30. import { GhostTextReplacement } from './ghostText.js';
  31. import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
  32. import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
  33. const ttPolicy = (_a = window.trustedTypes) === null || _a === void 0 ? void 0 : _a.createPolicy('editorGhostText', { createHTML: value => value });
  34. let GhostTextWidget = class GhostTextWidget extends Disposable {
  35. constructor(editor, model, instantiationService, languageService) {
  36. super();
  37. this.editor = editor;
  38. this.model = model;
  39. this.instantiationService = instantiationService;
  40. this.languageService = languageService;
  41. this.disposed = false;
  42. this.partsWidget = this._register(this.instantiationService.createInstance(DecorationsWidget, this.editor));
  43. this.additionalLinesWidget = this._register(new AdditionalLinesWidget(this.editor, this.languageService.languageIdCodec));
  44. this.viewMoreContentWidget = undefined;
  45. this.replacementDecoration = this._register(new DisposableDecorations(this.editor));
  46. this._register(this.editor.onDidChangeConfiguration((e) => {
  47. if (e.hasChanged(29 /* EditorOption.disableMonospaceOptimizations */)
  48. || e.hasChanged(107 /* EditorOption.stopRenderingLineAfter */)
  49. || e.hasChanged(90 /* EditorOption.renderWhitespace */)
  50. || e.hasChanged(85 /* EditorOption.renderControlCharacters */)
  51. || e.hasChanged(47 /* EditorOption.fontLigatures */)
  52. || e.hasChanged(46 /* EditorOption.fontInfo */)
  53. || e.hasChanged(61 /* EditorOption.lineHeight */)) {
  54. this.update();
  55. }
  56. }));
  57. this._register(toDisposable(() => {
  58. var _a;
  59. this.disposed = true;
  60. this.update();
  61. (_a = this.viewMoreContentWidget) === null || _a === void 0 ? void 0 : _a.dispose();
  62. this.viewMoreContentWidget = undefined;
  63. }));
  64. this._register(model.onDidChange(() => {
  65. this.update();
  66. }));
  67. this.update();
  68. }
  69. shouldShowHoverAtViewZone(viewZoneId) {
  70. return (this.additionalLinesWidget.viewZoneId === viewZoneId);
  71. }
  72. update() {
  73. var _a;
  74. const ghostText = this.model.ghostText;
  75. if (!this.editor.hasModel() || !ghostText || this.disposed) {
  76. this.partsWidget.clear();
  77. this.additionalLinesWidget.clear();
  78. this.replacementDecoration.clear();
  79. return;
  80. }
  81. const inlineTexts = new Array();
  82. const additionalLines = new Array();
  83. function addToAdditionalLines(lines, className) {
  84. if (additionalLines.length > 0) {
  85. const lastLine = additionalLines[additionalLines.length - 1];
  86. if (className) {
  87. lastLine.decorations.push(new LineDecoration(lastLine.content.length + 1, lastLine.content.length + 1 + lines[0].length, className, 0 /* InlineDecorationType.Regular */));
  88. }
  89. lastLine.content += lines[0];
  90. lines = lines.slice(1);
  91. }
  92. for (const line of lines) {
  93. additionalLines.push({
  94. content: line,
  95. decorations: className ? [new LineDecoration(1, line.length + 1, className, 0 /* InlineDecorationType.Regular */)] : []
  96. });
  97. }
  98. }
  99. if (ghostText instanceof GhostTextReplacement) {
  100. this.replacementDecoration.setDecorations([
  101. {
  102. range: new Range(ghostText.lineNumber, ghostText.columnStart, ghostText.lineNumber, ghostText.columnStart + ghostText.length),
  103. options: {
  104. inlineClassName: 'inline-completion-text-to-replace',
  105. description: 'GhostTextReplacement'
  106. }
  107. },
  108. ]);
  109. }
  110. else {
  111. this.replacementDecoration.setDecorations([]);
  112. }
  113. const textBufferLine = this.editor.getModel().getLineContent(ghostText.lineNumber);
  114. let hiddenTextStartColumn = undefined;
  115. let lastIdx = 0;
  116. for (const part of ghostText.parts) {
  117. let lines = part.lines;
  118. if (hiddenTextStartColumn === undefined) {
  119. inlineTexts.push({
  120. column: part.column,
  121. text: lines[0],
  122. preview: part.preview,
  123. });
  124. lines = lines.slice(1);
  125. }
  126. else {
  127. addToAdditionalLines([textBufferLine.substring(lastIdx, part.column - 1)], undefined);
  128. }
  129. if (lines.length > 0) {
  130. addToAdditionalLines(lines, 'ghost-text');
  131. if (hiddenTextStartColumn === undefined && part.column <= textBufferLine.length) {
  132. hiddenTextStartColumn = part.column;
  133. }
  134. }
  135. lastIdx = part.column - 1;
  136. }
  137. if (hiddenTextStartColumn !== undefined) {
  138. addToAdditionalLines([textBufferLine.substring(lastIdx)], undefined);
  139. }
  140. this.partsWidget.setParts(ghostText.lineNumber, inlineTexts, hiddenTextStartColumn !== undefined ? { column: hiddenTextStartColumn, length: textBufferLine.length + 1 - hiddenTextStartColumn } : undefined);
  141. this.additionalLinesWidget.updateLines(ghostText.lineNumber, additionalLines, ghostText.additionalReservedLineCount);
  142. if (0 < 0) {
  143. // Not supported at the moment, condition is always false.
  144. this.viewMoreContentWidget = this.renderViewMoreLines(new Position(ghostText.lineNumber, this.editor.getModel().getLineMaxColumn(ghostText.lineNumber)), '', 0);
  145. }
  146. else {
  147. (_a = this.viewMoreContentWidget) === null || _a === void 0 ? void 0 : _a.dispose();
  148. this.viewMoreContentWidget = undefined;
  149. }
  150. }
  151. renderViewMoreLines(position, firstLineText, remainingLinesLength) {
  152. const fontInfo = this.editor.getOption(46 /* EditorOption.fontInfo */);
  153. const domNode = document.createElement('div');
  154. domNode.className = 'suggest-preview-additional-widget';
  155. applyFontInfo(domNode, fontInfo);
  156. const spacer = document.createElement('span');
  157. spacer.className = 'content-spacer';
  158. spacer.append(firstLineText);
  159. domNode.append(spacer);
  160. const newline = document.createElement('span');
  161. newline.className = 'content-newline suggest-preview-text';
  162. newline.append('⏎ ');
  163. domNode.append(newline);
  164. const disposableStore = new DisposableStore();
  165. const button = document.createElement('div');
  166. button.className = 'button suggest-preview-text';
  167. button.append(`+${remainingLinesLength} lines…`);
  168. disposableStore.add(dom.addStandardDisposableListener(button, 'mousedown', (e) => {
  169. var _a;
  170. (_a = this.model) === null || _a === void 0 ? void 0 : _a.setExpanded(true);
  171. e.preventDefault();
  172. this.editor.focus();
  173. }));
  174. domNode.append(button);
  175. return new ViewMoreLinesContentWidget(this.editor, position, domNode, disposableStore);
  176. }
  177. };
  178. GhostTextWidget = __decorate([
  179. __param(2, IInstantiationService),
  180. __param(3, ILanguageService)
  181. ], GhostTextWidget);
  182. export { GhostTextWidget };
  183. class DisposableDecorations {
  184. constructor(editor) {
  185. this.editor = editor;
  186. this.decorationIds = [];
  187. }
  188. setDecorations(decorations) {
  189. // Using change decorations ensures that we update the id's before some event handler is called.
  190. this.editor.changeDecorations(accessor => {
  191. this.decorationIds = accessor.deltaDecorations(this.decorationIds, decorations);
  192. });
  193. }
  194. clear() {
  195. this.setDecorations([]);
  196. }
  197. dispose() {
  198. this.clear();
  199. }
  200. }
  201. class DecorationsWidget {
  202. constructor(editor) {
  203. this.editor = editor;
  204. this.decorationIds = [];
  205. }
  206. dispose() {
  207. this.clear();
  208. }
  209. clear() {
  210. // Using change decorations ensures that we update the id's before some event handler is called.
  211. this.editor.changeDecorations(accessor => {
  212. this.decorationIds = accessor.deltaDecorations(this.decorationIds, []);
  213. });
  214. }
  215. setParts(lineNumber, parts, hiddenText) {
  216. const textModel = this.editor.getModel();
  217. if (!textModel) {
  218. return;
  219. }
  220. const hiddenTextDecorations = new Array();
  221. if (hiddenText) {
  222. hiddenTextDecorations.push({
  223. range: Range.fromPositions(new Position(lineNumber, hiddenText.column), new Position(lineNumber, hiddenText.column + hiddenText.length)),
  224. options: {
  225. inlineClassName: 'ghost-text-hidden',
  226. description: 'ghost-text-hidden',
  227. }
  228. });
  229. }
  230. // Using change decorations ensures that we update the id's before some event handler is called.
  231. this.editor.changeDecorations(accessor => {
  232. this.decorationIds = accessor.deltaDecorations(this.decorationIds, parts.map(p => {
  233. return ({
  234. range: Range.fromPositions(new Position(lineNumber, p.column)),
  235. options: {
  236. description: 'ghost-text',
  237. after: { content: p.text, inlineClassName: p.preview ? 'ghost-text-decoration-preview' : 'ghost-text-decoration', cursorStops: InjectedTextCursorStops.Left },
  238. showIfCollapsed: true,
  239. }
  240. });
  241. }).concat(hiddenTextDecorations));
  242. });
  243. }
  244. }
  245. class AdditionalLinesWidget {
  246. constructor(editor, languageIdCodec) {
  247. this.editor = editor;
  248. this.languageIdCodec = languageIdCodec;
  249. this._viewZoneId = undefined;
  250. }
  251. get viewZoneId() { return this._viewZoneId; }
  252. dispose() {
  253. this.clear();
  254. }
  255. clear() {
  256. this.editor.changeViewZones((changeAccessor) => {
  257. if (this._viewZoneId) {
  258. changeAccessor.removeZone(this._viewZoneId);
  259. this._viewZoneId = undefined;
  260. }
  261. });
  262. }
  263. updateLines(lineNumber, additionalLines, minReservedLineCount) {
  264. const textModel = this.editor.getModel();
  265. if (!textModel) {
  266. return;
  267. }
  268. const { tabSize } = textModel.getOptions();
  269. this.editor.changeViewZones((changeAccessor) => {
  270. if (this._viewZoneId) {
  271. changeAccessor.removeZone(this._viewZoneId);
  272. this._viewZoneId = undefined;
  273. }
  274. const heightInLines = Math.max(additionalLines.length, minReservedLineCount);
  275. if (heightInLines > 0) {
  276. const domNode = document.createElement('div');
  277. renderLines(domNode, tabSize, additionalLines, this.editor.getOptions(), this.languageIdCodec);
  278. this._viewZoneId = changeAccessor.addZone({
  279. afterLineNumber: lineNumber,
  280. heightInLines: heightInLines,
  281. domNode,
  282. afterColumnAffinity: 1 /* PositionAffinity.Right */
  283. });
  284. }
  285. });
  286. }
  287. }
  288. function renderLines(domNode, tabSize, lines, opts, languageIdCodec) {
  289. const disableMonospaceOptimizations = opts.get(29 /* EditorOption.disableMonospaceOptimizations */);
  290. const stopRenderingLineAfter = opts.get(107 /* EditorOption.stopRenderingLineAfter */);
  291. // To avoid visual confusion, we don't want to render visible whitespace
  292. const renderWhitespace = 'none';
  293. const renderControlCharacters = opts.get(85 /* EditorOption.renderControlCharacters */);
  294. const fontLigatures = opts.get(47 /* EditorOption.fontLigatures */);
  295. const fontInfo = opts.get(46 /* EditorOption.fontInfo */);
  296. const lineHeight = opts.get(61 /* EditorOption.lineHeight */);
  297. const sb = createStringBuilder(10000);
  298. sb.appendASCIIString('<div class="suggest-preview-text">');
  299. for (let i = 0, len = lines.length; i < len; i++) {
  300. const lineData = lines[i];
  301. const line = lineData.content;
  302. sb.appendASCIIString('<div class="view-line');
  303. sb.appendASCIIString('" style="top:');
  304. sb.appendASCIIString(String(i * lineHeight));
  305. sb.appendASCIIString('px;width:1000000px;">');
  306. const isBasicASCII = strings.isBasicASCII(line);
  307. const containsRTL = strings.containsRTL(line);
  308. const lineTokens = LineTokens.createEmpty(line, languageIdCodec);
  309. renderViewLine(new RenderLineInput((fontInfo.isMonospace && !disableMonospaceOptimizations), fontInfo.canUseHalfwidthRightwardsArrow, line, false, isBasicASCII, containsRTL, 0, lineTokens, lineData.decorations, tabSize, 0, fontInfo.spaceWidth, fontInfo.middotWidth, fontInfo.wsmiddotWidth, stopRenderingLineAfter, renderWhitespace, renderControlCharacters, fontLigatures !== EditorFontLigatures.OFF, null), sb);
  310. sb.appendASCIIString('</div>');
  311. }
  312. sb.appendASCIIString('</div>');
  313. applyFontInfo(domNode, fontInfo);
  314. const html = sb.build();
  315. const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html;
  316. domNode.innerHTML = trustedhtml;
  317. }
  318. class ViewMoreLinesContentWidget extends Disposable {
  319. constructor(editor, position, domNode, disposableStore) {
  320. super();
  321. this.editor = editor;
  322. this.position = position;
  323. this.domNode = domNode;
  324. this.allowEditorOverflow = false;
  325. this.suppressMouseDown = false;
  326. this._register(disposableStore);
  327. this._register(toDisposable(() => {
  328. this.editor.removeContentWidget(this);
  329. }));
  330. this.editor.addContentWidget(this);
  331. }
  332. getId() {
  333. return 'editor.widget.viewMoreLinesWidget';
  334. }
  335. getDomNode() {
  336. return this.domNode;
  337. }
  338. getPosition() {
  339. return {
  340. position: this.position,
  341. preference: [0 /* ContentWidgetPositionPreference.EXACT */]
  342. };
  343. }
  344. }
  345. registerThemingParticipant((theme, collector) => {
  346. const foreground = theme.getColor(ghostTextForeground);
  347. if (foreground) {
  348. // `!important` ensures that other decorations don't cause a style conflict (#132017).
  349. collector.addRule(`.monaco-editor .ghost-text-decoration { color: ${foreground.toString()} !important; }`);
  350. collector.addRule(`.monaco-editor .ghost-text-decoration-preview { color: ${foreground.toString()} !important; }`);
  351. collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { color: ${foreground.toString()} !important; }`);
  352. }
  353. const background = theme.getColor(ghostTextBackground);
  354. if (background) {
  355. collector.addRule(`.monaco-editor .ghost-text-decoration { background-color: ${background.toString()}; }`);
  356. collector.addRule(`.monaco-editor .ghost-text-decoration-preview { background-color: ${background.toString()}; }`);
  357. collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { background-color: ${background.toString()}; }`);
  358. }
  359. const border = theme.getColor(ghostTextBorder);
  360. if (border) {
  361. collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { border: 1px solid ${border}; }`);
  362. collector.addRule(`.monaco-editor .ghost-text-decoration { border: 1px solid ${border}; }`);
  363. collector.addRule(`.monaco-editor .ghost-text-decoration-preview { border: 1px solid ${border}; }`);
  364. }
  365. });