ghostText.js 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  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 { Disposable } from '../../../base/common/lifecycle.js';
  7. import { Range } from '../../common/core/range.js';
  8. export class GhostText {
  9. constructor(lineNumber, parts, additionalReservedLineCount = 0) {
  10. this.lineNumber = lineNumber;
  11. this.parts = parts;
  12. this.additionalReservedLineCount = additionalReservedLineCount;
  13. }
  14. renderForScreenReader(lineText) {
  15. if (this.parts.length === 0) {
  16. return '';
  17. }
  18. const lastPart = this.parts[this.parts.length - 1];
  19. const cappedLineText = lineText.substr(0, lastPart.column - 1);
  20. const text = applyEdits(cappedLineText, this.parts.map(p => ({
  21. range: { startLineNumber: 1, endLineNumber: 1, startColumn: p.column, endColumn: p.column },
  22. text: p.lines.join('\n')
  23. })));
  24. return text.substring(this.parts[0].column - 1);
  25. }
  26. }
  27. class PositionOffsetTransformer {
  28. constructor(text) {
  29. this.lineStartOffsetByLineIdx = [];
  30. this.lineStartOffsetByLineIdx.push(0);
  31. for (let i = 0; i < text.length; i++) {
  32. if (text.charAt(i) === '\n') {
  33. this.lineStartOffsetByLineIdx.push(i + 1);
  34. }
  35. }
  36. }
  37. getOffset(position) {
  38. return this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1;
  39. }
  40. }
  41. function applyEdits(text, edits) {
  42. const transformer = new PositionOffsetTransformer(text);
  43. const offsetEdits = edits.map(e => {
  44. const range = Range.lift(e.range);
  45. return ({
  46. startOffset: transformer.getOffset(range.getStartPosition()),
  47. endOffset: transformer.getOffset(range.getEndPosition()),
  48. text: e.text
  49. });
  50. });
  51. offsetEdits.sort((a, b) => b.startOffset - a.startOffset);
  52. for (const edit of offsetEdits) {
  53. text = text.substring(0, edit.startOffset) + edit.text + text.substring(edit.endOffset);
  54. }
  55. return text;
  56. }
  57. export class GhostTextPart {
  58. constructor(column, lines,
  59. /**
  60. * Indicates if this part is a preview of an inline suggestion when a suggestion is previewed.
  61. */
  62. preview) {
  63. this.column = column;
  64. this.lines = lines;
  65. this.preview = preview;
  66. }
  67. }
  68. export class BaseGhostTextWidgetModel extends Disposable {
  69. constructor(editor) {
  70. super();
  71. this.editor = editor;
  72. this._expanded = undefined;
  73. this.onDidChangeEmitter = new Emitter();
  74. this.onDidChange = this.onDidChangeEmitter.event;
  75. this._register(editor.onDidChangeConfiguration((e) => {
  76. if (e.hasChanged(105 /* suggest */) && this._expanded === undefined) {
  77. this.onDidChangeEmitter.fire();
  78. }
  79. }));
  80. }
  81. setExpanded(expanded) {
  82. this._expanded = true;
  83. this.onDidChangeEmitter.fire();
  84. }
  85. }