@xterm/xterm 6.1.0-beta.28 → 6.1.0-beta.280

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +62 -38
  2. package/css/xterm.css +29 -22
  3. package/lib/xterm.js +1 -1
  4. package/lib/xterm.js.map +1 -1
  5. package/lib/xterm.mjs +8 -34
  6. package/lib/xterm.mjs.map +4 -4
  7. package/package.json +56 -45
  8. package/src/browser/AccessibilityManager.ts +18 -13
  9. package/src/browser/Clipboard.ts +8 -5
  10. package/src/browser/ColorContrastCache.ts +3 -3
  11. package/src/browser/CoreBrowserTerminal.ts +179 -348
  12. package/src/browser/Dom.ts +178 -0
  13. package/src/browser/Linkifier.ts +14 -14
  14. package/src/browser/OscLinkProvider.ts +86 -18
  15. package/src/browser/RenderDebouncer.ts +7 -9
  16. package/src/browser/TimeBasedDebouncer.ts +12 -5
  17. package/src/browser/Types.ts +16 -14
  18. package/src/browser/Viewport.ts +58 -23
  19. package/src/browser/decorations/BufferDecorationRenderer.ts +3 -3
  20. package/src/browser/decorations/ColorZoneStore.ts +1 -1
  21. package/src/browser/decorations/OverviewRulerRenderer.ts +36 -20
  22. package/src/browser/input/CompositionHelper.ts +47 -11
  23. package/src/browser/input/Mouse.ts +5 -9
  24. package/src/browser/input/MoveToCell.ts +3 -3
  25. package/src/browser/public/Terminal.ts +33 -36
  26. package/src/browser/renderer/dom/DomRenderer.ts +265 -89
  27. package/src/browser/renderer/dom/DomRendererRowFactory.ts +34 -27
  28. package/src/browser/renderer/dom/WidthCache.ts +57 -55
  29. package/src/browser/renderer/shared/Constants.ts +7 -0
  30. package/src/browser/renderer/shared/RendererUtils.ts +1 -1
  31. package/src/browser/renderer/shared/SelectionRenderModel.ts +2 -2
  32. package/src/browser/renderer/shared/TextBlinkStateManager.ts +97 -0
  33. package/src/browser/renderer/shared/Types.ts +10 -4
  34. package/src/browser/scrollable/abstractScrollbar.ts +300 -0
  35. package/src/browser/scrollable/fastDomNode.ts +126 -0
  36. package/src/browser/scrollable/globalPointerMoveMonitor.ts +90 -0
  37. package/src/browser/scrollable/horizontalScrollbar.ts +85 -0
  38. package/src/browser/scrollable/mouseEvent.ts +292 -0
  39. package/src/browser/scrollable/scrollable.ts +486 -0
  40. package/src/browser/scrollable/scrollableElement.ts +581 -0
  41. package/src/browser/scrollable/scrollableElementOptions.ts +161 -0
  42. package/src/browser/scrollable/scrollbarArrow.ts +110 -0
  43. package/src/browser/scrollable/scrollbarState.ts +246 -0
  44. package/src/browser/scrollable/scrollbarVisibilityController.ts +113 -0
  45. package/src/browser/scrollable/touch.ts +485 -0
  46. package/src/browser/scrollable/verticalScrollbar.ts +143 -0
  47. package/src/browser/scrollable/widget.ts +23 -0
  48. package/src/browser/selection/SelectionModel.ts +1 -1
  49. package/src/browser/services/CharSizeService.ts +4 -4
  50. package/src/browser/services/CharacterJoinerService.ts +13 -11
  51. package/src/browser/services/CoreBrowserService.ts +7 -5
  52. package/src/browser/services/KeyboardService.ts +67 -0
  53. package/src/browser/services/LinkProviderService.ts +3 -3
  54. package/src/browser/services/MouseCoordsService.ts +47 -0
  55. package/src/browser/services/MouseService.ts +619 -23
  56. package/src/browser/services/RenderService.ts +33 -21
  57. package/src/browser/services/SelectionService.ts +72 -54
  58. package/src/browser/services/Services.ts +44 -21
  59. package/src/browser/services/ThemeService.ts +9 -9
  60. package/src/common/Async.ts +141 -0
  61. package/src/common/CircularList.ts +24 -3
  62. package/src/common/Color.ts +13 -5
  63. package/src/common/CoreTerminal.ts +61 -35
  64. package/src/common/Event.ts +118 -0
  65. package/src/common/InputHandler.ts +286 -105
  66. package/src/common/Lifecycle.ts +113 -0
  67. package/src/common/Platform.ts +15 -5
  68. package/src/common/SortedList.ts +8 -4
  69. package/src/common/StringBuilder.ts +67 -0
  70. package/src/common/TaskQueue.ts +17 -8
  71. package/src/common/Types.ts +68 -206
  72. package/src/common/Version.ts +1 -1
  73. package/src/common/WindowsMode.ts +2 -2
  74. package/src/common/buffer/AttributeData.ts +3 -2
  75. package/src/common/buffer/Buffer.ts +47 -32
  76. package/src/common/buffer/BufferLine.ts +175 -100
  77. package/src/common/buffer/BufferLineStringCache.ts +69 -0
  78. package/src/common/buffer/BufferReflow.ts +7 -4
  79. package/src/common/buffer/BufferSet.ts +13 -9
  80. package/src/common/buffer/CellData.ts +61 -4
  81. package/src/common/buffer/Constants.ts +13 -13
  82. package/src/common/buffer/Marker.ts +4 -4
  83. package/src/common/buffer/Types.ts +153 -3
  84. package/src/common/data/Charsets.ts +4 -7
  85. package/src/common/data/EscapeSequences.ts +71 -70
  86. package/src/common/input/Keyboard.ts +17 -10
  87. package/src/common/input/KittyKeyboard.ts +526 -0
  88. package/src/common/input/TextDecoder.ts +3 -3
  89. package/src/common/input/UnicodeV6.ts +3 -2
  90. package/src/common/input/Win32InputMode.ts +297 -0
  91. package/src/common/input/WriteBuffer.ts +108 -39
  92. package/src/common/input/XParseColor.ts +2 -2
  93. package/src/common/parser/ApcParser.ts +196 -0
  94. package/src/common/parser/Constants.ts +14 -4
  95. package/src/common/parser/DcsParser.ts +15 -16
  96. package/src/common/parser/EscapeSequenceParser.ts +214 -72
  97. package/src/common/parser/OscParser.ts +14 -15
  98. package/src/common/parser/Params.ts +31 -12
  99. package/src/common/parser/Types.ts +40 -30
  100. package/src/common/public/BufferApiView.ts +3 -3
  101. package/src/common/public/BufferLineApiView.ts +4 -4
  102. package/src/common/public/BufferNamespaceApi.ts +5 -5
  103. package/src/common/public/ParserApi.ts +5 -2
  104. package/src/common/public/UnicodeApi.ts +1 -1
  105. package/src/common/services/BufferService.ts +17 -13
  106. package/src/common/services/CharsetService.ts +6 -2
  107. package/src/common/services/CoreService.ts +23 -10
  108. package/src/common/services/DecorationService.ts +260 -15
  109. package/src/common/services/InstantiationService.ts +2 -2
  110. package/src/common/services/LogService.ts +2 -32
  111. package/src/common/services/{CoreMouseService.ts → MouseStateService.ts} +22 -133
  112. package/src/common/services/OptionsService.ts +17 -7
  113. package/src/common/services/OscLinkService.ts +3 -2
  114. package/src/common/services/ServiceRegistry.ts +14 -8
  115. package/src/common/services/Services.ts +54 -50
  116. package/src/common/services/UnicodeService.ts +6 -11
  117. package/typings/xterm.d.ts +329 -34
  118. package/src/common/Clone.ts +0 -23
  119. package/src/common/TypedArrayUtils.ts +0 -17
  120. package/src/vs/base/browser/browser.ts +0 -141
  121. package/src/vs/base/browser/canIUse.ts +0 -49
  122. package/src/vs/base/browser/dom.ts +0 -2369
  123. package/src/vs/base/browser/fastDomNode.ts +0 -316
  124. package/src/vs/base/browser/globalPointerMoveMonitor.ts +0 -112
  125. package/src/vs/base/browser/iframe.ts +0 -135
  126. package/src/vs/base/browser/keyboardEvent.ts +0 -213
  127. package/src/vs/base/browser/mouseEvent.ts +0 -229
  128. package/src/vs/base/browser/touch.ts +0 -372
  129. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +0 -303
  130. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +0 -114
  131. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +0 -720
  132. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +0 -165
  133. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +0 -114
  134. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +0 -243
  135. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +0 -118
  136. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +0 -116
  137. package/src/vs/base/browser/ui/widget.ts +0 -57
  138. package/src/vs/base/browser/window.ts +0 -14
  139. package/src/vs/base/common/arrays.ts +0 -887
  140. package/src/vs/base/common/arraysFind.ts +0 -202
  141. package/src/vs/base/common/assert.ts +0 -71
  142. package/src/vs/base/common/async.ts +0 -1992
  143. package/src/vs/base/common/cancellation.ts +0 -148
  144. package/src/vs/base/common/charCode.ts +0 -450
  145. package/src/vs/base/common/collections.ts +0 -140
  146. package/src/vs/base/common/decorators.ts +0 -130
  147. package/src/vs/base/common/equals.ts +0 -146
  148. package/src/vs/base/common/errors.ts +0 -303
  149. package/src/vs/base/common/event.ts +0 -1778
  150. package/src/vs/base/common/functional.ts +0 -32
  151. package/src/vs/base/common/hash.ts +0 -316
  152. package/src/vs/base/common/iterator.ts +0 -159
  153. package/src/vs/base/common/keyCodes.ts +0 -526
  154. package/src/vs/base/common/keybindings.ts +0 -284
  155. package/src/vs/base/common/lazy.ts +0 -47
  156. package/src/vs/base/common/lifecycle.ts +0 -801
  157. package/src/vs/base/common/linkedList.ts +0 -142
  158. package/src/vs/base/common/map.ts +0 -202
  159. package/src/vs/base/common/numbers.ts +0 -98
  160. package/src/vs/base/common/observable.ts +0 -76
  161. package/src/vs/base/common/observableInternal/api.ts +0 -31
  162. package/src/vs/base/common/observableInternal/autorun.ts +0 -281
  163. package/src/vs/base/common/observableInternal/base.ts +0 -489
  164. package/src/vs/base/common/observableInternal/debugName.ts +0 -145
  165. package/src/vs/base/common/observableInternal/derived.ts +0 -428
  166. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +0 -146
  167. package/src/vs/base/common/observableInternal/logging.ts +0 -328
  168. package/src/vs/base/common/observableInternal/promise.ts +0 -209
  169. package/src/vs/base/common/observableInternal/utils.ts +0 -610
  170. package/src/vs/base/common/platform.ts +0 -281
  171. package/src/vs/base/common/scrollable.ts +0 -522
  172. package/src/vs/base/common/sequence.ts +0 -34
  173. package/src/vs/base/common/stopwatch.ts +0 -43
  174. package/src/vs/base/common/strings.ts +0 -557
  175. package/src/vs/base/common/symbols.ts +0 -9
  176. package/src/vs/base/common/uint.ts +0 -59
  177. package/src/vs/patches/nls.ts +0 -90
  178. package/src/vs/typings/base-common.d.ts +0 -20
  179. package/src/vs/typings/require.d.ts +0 -42
  180. package/src/vs/typings/vscode-globals-nls.d.ts +0 -36
  181. package/src/vs/typings/vscode-globals-product.d.ts +0 -33
@@ -3,44 +3,640 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { ICharSizeService, IRenderService, IMouseService } from './Services';
7
- import { getCoords, getCoordsRelativeToElement } from 'browser/input/Mouse';
6
+ import { addDisposableListener } from '../Dom';
7
+ import { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from '../../common/services/Services';
8
+ import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from '../../common/Types';
9
+ import { C0 } from '../../common/data/EscapeSequences';
10
+ import { DisposableStore, MutableDisposable, toDisposable } from '../../common/Lifecycle';
11
+ import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
12
+ import { Gesture, EventType as GestureEventType, IGestureEvent } from '../scrollable/touch';
13
+
14
+ type RequestedMouseEvents = Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener | null>;
15
+
16
+ export const enum MouseEventCssClasses {
17
+ ENABLE_MOUSE_EVENTS = 'enable-mouse-events'
18
+ }
19
+
20
+ interface IMouseBindContext {
21
+ readonly target: IMouseServiceTarget;
22
+ readonly focus: () => void;
23
+ readonly requestedEvents: RequestedMouseEvents;
24
+ }
8
25
 
9
26
  export class MouseService implements IMouseService {
10
27
  public serviceBrand: undefined;
11
28
 
29
+ private _lastEvent: ICoreMouseEvent | null = null;
30
+ private _wheelPartialScroll: number = 0;
31
+ private _touchScrollAccumulator: number = 0;
32
+ private _altMouseCursor: AltMouseCursorController | undefined;
33
+
12
34
  constructor(
13
35
  @IRenderService private readonly _renderService: IRenderService,
14
- @ICharSizeService private readonly _charSizeService: ICharSizeService
36
+ @IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,
37
+ @IMouseStateService private readonly _mouseStateService: IMouseStateService,
38
+ @ICoreService private readonly _coreService: ICoreService,
39
+ @IBufferService private readonly _bufferService: IBufferService,
40
+ @IOptionsService private readonly _optionsService: IOptionsService,
41
+ @ISelectionService private readonly _selectionService: ISelectionService,
42
+ @ILogService private readonly _logService: ILogService,
43
+ @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
15
44
  ) {
16
45
  }
17
46
 
18
- public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
19
- return getCoords(
20
- window,
21
- event,
47
+ public bindMouse(target: IMouseServiceTarget, register: (disposable: IDisposable) => void, focus: () => void): void {
48
+ const { element, document } = target;
49
+
50
+ /**
51
+ * Event listener state handling.
52
+ * We listen to the onProtocolChange event of MouseStateService and put
53
+ * requested listeners in `requestedEvents`. With this the listeners
54
+ * have all bits to do the event listener juggling.
55
+ * Note: 'mousedown' currently is "always on" and not managed
56
+ * by onProtocolChange.
57
+ */
58
+ const requestedEvents: RequestedMouseEvents = {
59
+ mouseup: null,
60
+ wheel: null,
61
+ mousedrag: null,
62
+ mousemove: null
63
+ };
64
+ const ctx: IMouseBindContext = { target, focus, requestedEvents };
65
+ const eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener> = {
66
+ mouseup: (ev: Event) => this._handleMouseUp(ctx, ev as MouseEvent),
67
+ wheel: (ev: Event) => this._handleWheel(ctx, ev as WheelEvent),
68
+ mousedrag: (ev: Event) => this._handleMouseDrag(ctx, ev as MouseEvent),
69
+ mousemove: (ev: Event) => this._handleMouseMove(ctx, ev as MouseEvent)
70
+ };
71
+ this._altMouseCursor = new AltMouseCursorController(
22
72
  element,
23
- colCount,
24
- rowCount,
25
- this._charSizeService.hasValidSize,
26
- this._renderService.dimensions.css.cell.width,
27
- this._renderService.dimensions.css.cell.height,
28
- isSelection
73
+ document,
74
+ () => this._mouseStateService.areMouseEventsActive
75
+ && !!this._optionsService.rawOptions.mouseEventsRequireAlt
29
76
  );
77
+ register(this._altMouseCursor);
78
+ register(this._mouseStateService.onProtocolChange(events => {
79
+ this._handleProtocolChange(ctx, eventListeners, events);
80
+ }));
81
+ register(this._optionsService.onSpecificOptionChange('mouseEventsRequireAlt', () => {
82
+ this._syncMouseModeState(element);
83
+ this._altMouseCursor?.sync();
84
+ }));
85
+ // force initial onProtocolChange so we dont miss early mouse requests
86
+ this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol;
87
+
88
+ // Ensure document-level listeners are removed on dispose
89
+ register(toDisposable(() => {
90
+ if (requestedEvents.mouseup) {
91
+ document.removeEventListener('mouseup', requestedEvents.mouseup);
92
+ }
93
+ if (requestedEvents.mousedrag) {
94
+ document.removeEventListener('mousemove', requestedEvents.mousedrag);
95
+ }
96
+ }));
97
+
98
+ /**
99
+ * "Always on" event listeners.
100
+ */
101
+ register(addDisposableListener(element, 'mousedown', (ev: MouseEvent) => this._handleMouseDown(ctx, ev)));
102
+ register(addDisposableListener(element, 'wheel', (ev: WheelEvent) => this._handlePassiveWheel(ctx, ev), { passive: false }));
103
+ register(Gesture.addTarget(target.screenElement));
104
+ register(addDisposableListener(target.screenElement, GestureEventType.START, () => this._handleTouchStart()));
105
+ register(addDisposableListener(target.screenElement, GestureEventType.CHANGE, (e: IGestureEvent) => this._handleTouchChange(ctx, e)));
106
+ }
107
+
108
+ private _sendEvent(ctx: IMouseBindContext, ev: MouseEvent | WheelEvent): boolean {
109
+ // Get mouse coordinates
110
+ const pos = this._mouseCoordsService.getMouseReportCoords(ev as MouseEvent, ctx.target.screenElement);
111
+ if (!pos) {
112
+ return false;
113
+ }
114
+
115
+ let but: CoreMouseButton;
116
+ let action: CoreMouseAction | undefined;
117
+ switch ((ev as MouseEvent & { overrideType?: string }).overrideType || ev.type) {
118
+ case 'mousemove':
119
+ action = CoreMouseAction.MOVE;
120
+ if (ev.buttons === undefined) {
121
+ // buttons is not supported on macOS, try to get a value from button instead
122
+ but = CoreMouseButton.NONE;
123
+ if (ev.button !== undefined) {
124
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
125
+ }
126
+ } else {
127
+ // according to MDN buttons only reports up to button 5 (AUX2)
128
+ but = ev.buttons & 1 ? CoreMouseButton.LEFT :
129
+ ev.buttons & 4 ? CoreMouseButton.MIDDLE :
130
+ ev.buttons & 2 ? CoreMouseButton.RIGHT :
131
+ CoreMouseButton.NONE; // fallback to NONE
132
+ }
133
+ break;
134
+ case 'mouseup':
135
+ action = CoreMouseAction.UP;
136
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
137
+ break;
138
+ case 'mousedown':
139
+ action = CoreMouseAction.DOWN;
140
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
141
+ break;
142
+ case 'wheel':
143
+ if (!this._mouseStateService.allowCustomWheelEvent(ev as WheelEvent)) {
144
+ return false;
145
+ }
146
+ const deltaY = (ev as WheelEvent).deltaY;
147
+ if (deltaY === 0) {
148
+ return false;
149
+ }
150
+ const lines = this._consumeWheelEvent(
151
+ ev as WheelEvent,
152
+ this._renderService?.dimensions?.device?.cell?.height,
153
+ this._coreBrowserService?.dpr
154
+ );
155
+ if (lines === 0) {
156
+ return false;
157
+ }
158
+ action = deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
159
+ but = CoreMouseButton.WHEEL;
160
+ break;
161
+ default:
162
+ // dont handle other event types by accident
163
+ return false;
164
+ }
165
+
166
+ // exit if we cannot determine valid button/action values
167
+ // do nothing for higher buttons than wheel
168
+ if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {
169
+ return false;
170
+ }
171
+
172
+ if (but !== CoreMouseButton.WHEEL
173
+ && this._optionsService.rawOptions.mouseEventsRequireAlt
174
+ && this._mouseStateService.areMouseEventsActive
175
+ && !ev.altKey) {
176
+ return false;
177
+ }
178
+
179
+ // Alt is only used locally to gate mouse passthrough; do not forward it to the
180
+ // application (e.g. tmux ignores alt-modified mouse reports).
181
+ const stripAltFromReport = but !== CoreMouseButton.WHEEL
182
+ && this._optionsService.rawOptions.mouseEventsRequireAlt
183
+ && this._mouseStateService.areMouseEventsActive;
184
+
185
+ return this._triggerMouseEvent({
186
+ col: pos.col,
187
+ row: pos.row,
188
+ x: pos.x,
189
+ y: pos.y,
190
+ button: but,
191
+ action,
192
+ ctrl: ev.ctrlKey,
193
+ alt: stripAltFromReport ? false : ev.altKey,
194
+ shift: ev.shiftKey
195
+ });
196
+ }
197
+
198
+ private _handleMouseUp(ctx: IMouseBindContext, ev: MouseEvent): void {
199
+ this._sendEvent(ctx, ev);
200
+ if (!ev.buttons) {
201
+ // if no other button is held remove global handlers
202
+ if (ctx.requestedEvents.mouseup) {
203
+ ctx.target.document.removeEventListener('mouseup', ctx.requestedEvents.mouseup);
204
+ }
205
+ if (ctx.requestedEvents.mousedrag) {
206
+ ctx.target.document.removeEventListener('mousemove', ctx.requestedEvents.mousedrag);
207
+ }
208
+ }
209
+ }
210
+
211
+ private _handleWheel(ctx: IMouseBindContext, ev: WheelEvent): false {
212
+ this._sendEvent(ctx, ev);
213
+ ev.preventDefault();
214
+ ev.stopPropagation();
215
+ return false;
216
+ }
217
+
218
+ private _handleMouseDrag(ctx: IMouseBindContext, ev: MouseEvent): void {
219
+ // deal only with move while a button is held
220
+ if (ev.buttons) {
221
+ this._sendEvent(ctx, ev);
222
+ }
223
+ }
224
+
225
+ private _handleMouseMove(ctx: IMouseBindContext, ev: MouseEvent): void {
226
+ // deal only with move without any button
227
+ if (!ev.buttons) {
228
+ this._sendEvent(ctx, ev);
229
+ }
230
+ }
231
+
232
+ private _handleMouseDown(ctx: IMouseBindContext, ev: MouseEvent): void {
233
+ ev.preventDefault();
234
+ ctx.focus();
235
+
236
+ // Don't send the mouse button to the pty if mouse events are disabled or
237
+ // if the selection manager is having selection forced (ie. a modifier is
238
+ // held).
239
+ if (!this._mouseStateService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) {
240
+ return;
241
+ }
242
+
243
+ this._sendEvent(ctx, ev);
244
+
245
+ // Register additional global handlers which should keep reporting outside
246
+ // of the terminal element.
247
+ // Note: Other emulators also do this for 'mousedown' while a button
248
+ // is held, we currently limit 'mousedown' to the terminal only.
249
+ if (ctx.requestedEvents.mouseup) {
250
+ ctx.target.document.addEventListener('mouseup', ctx.requestedEvents.mouseup);
251
+ }
252
+ if (ctx.requestedEvents.mousedrag) {
253
+ ctx.target.document.addEventListener('mousemove', ctx.requestedEvents.mousedrag);
254
+ }
30
255
  }
31
256
 
32
- public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
33
- const coords = getCoordsRelativeToElement(window, event, element);
34
- if (!this._charSizeService.hasValidSize) {
35
- return undefined;
257
+ private _handlePassiveWheel(ctx: IMouseBindContext, ev: WheelEvent): false | void {
258
+ // do nothing, if app side handles wheel itself
259
+ if (ctx.requestedEvents.wheel) {
260
+ return;
261
+ }
262
+
263
+ if (!this._mouseStateService.allowCustomWheelEvent(ev)) {
264
+ return false;
265
+ }
266
+
267
+ if (!this._bufferService.buffer.hasScrollback) {
268
+ // Convert wheel events into up/down events when the buffer does not have scrollback, this
269
+ // enables scrolling in apps hosted in the alt buffer such as vim or tmux even when mouse
270
+ // events are not enabled.
271
+ // This used implementation used get the actual lines/partial lines scrolled from the
272
+ // viewport but since moving to the new viewport implementation has been simplified to
273
+ // simply send a single up or down sequence.
274
+
275
+ // Do nothing if there's no vertical scroll
276
+ const deltaY = ev.deltaY;
277
+ if (deltaY === 0) {
278
+ return false;
279
+ }
280
+
281
+ const lines = this._consumeWheelEvent(
282
+ ev,
283
+ this._renderService?.dimensions?.device?.cell?.height,
284
+ this._coreBrowserService?.dpr
285
+ );
286
+ if (lines === 0) {
287
+ ev.preventDefault();
288
+ ev.stopPropagation();
289
+ return false;
290
+ }
291
+
292
+ // Construct and send sequences
293
+ const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
294
+ this._coreService.triggerDataEvent(sequence, true);
295
+ ev.preventDefault();
296
+ ev.stopPropagation();
297
+ return false;
298
+ }
299
+ }
300
+
301
+ private _handleTouchStart(): void {
302
+ this._touchScrollAccumulator = 0;
303
+ }
304
+
305
+ private _handleTouchChange(ctx: IMouseBindContext, e: IGestureEvent): void {
306
+ e.preventDefault();
307
+ e.stopPropagation();
308
+
309
+ // When mouse protocol has wheel events active, send as mouse wheel events.
310
+ if (ctx.requestedEvents.wheel) {
311
+ this._handleTouchScrollAsWheel(ctx, e);
312
+ return;
313
+ }
314
+
315
+ // When in alt buffer (no scrollback), send up/down key sequences.
316
+ if (!this._bufferService.buffer.hasScrollback) {
317
+ this._handleTouchScrollAsKeys(e);
318
+ return;
36
319
  }
37
- coords[0] = Math.min(Math.max(coords[0], 0), this._renderService.dimensions.css.canvas.width - 1);
38
- coords[1] = Math.min(Math.max(coords[1], 0), this._renderService.dimensions.css.canvas.height - 1);
320
+
321
+ // Normal scrollback: delegate to viewport scrolling when available.
322
+ ctx.target.handleTouchScroll?.(e.translationY);
323
+ }
324
+
325
+ private _handleTouchScrollAsKeys(e: IGestureEvent): void {
326
+ const cellHeight = this._renderService?.dimensions.css.cell.height;
327
+ if (!cellHeight) {
328
+ return;
329
+ }
330
+
331
+ this._touchScrollAccumulator -= e.translationY;
332
+ const lines = Math.trunc(this._touchScrollAccumulator / cellHeight);
333
+ if (lines === 0) {
334
+ return;
335
+ }
336
+
337
+ this._touchScrollAccumulator -= lines * cellHeight;
338
+ const sequence = C0.ESC
339
+ + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[')
340
+ + (lines < 0 ? 'A' : 'B');
341
+ for (let i = 0; i < Math.abs(lines); i++) {
342
+ this._coreService.triggerDataEvent(sequence, true);
343
+ }
344
+ }
345
+
346
+ private _handleTouchScrollAsWheel(ctx: IMouseBindContext, e: IGestureEvent): void {
347
+ const cellHeight = this._renderService?.dimensions.css.cell.height;
348
+ if (!cellHeight) {
349
+ return;
350
+ }
351
+
352
+ this._touchScrollAccumulator -= e.translationY;
353
+ const lines = Math.trunc(this._touchScrollAccumulator / cellHeight);
354
+ if (lines === 0) {
355
+ return;
356
+ }
357
+
358
+ this._touchScrollAccumulator -= lines * cellHeight;
359
+ const pos = this._mouseCoordsService.getMouseReportCoords(e, ctx.target.screenElement);
360
+ if (!pos) {
361
+ return;
362
+ }
363
+
364
+ for (let i = 0; i < Math.abs(lines); i++) {
365
+ this._triggerMouseEvent({
366
+ col: pos.col,
367
+ row: pos.row,
368
+ x: pos.x,
369
+ y: pos.y,
370
+ button: CoreMouseButton.WHEEL,
371
+ action: lines < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN,
372
+ ctrl: false,
373
+ alt: false,
374
+ shift: false
375
+ });
376
+ }
377
+ }
378
+
379
+ public reset(): void {
380
+ this._lastEvent = null;
381
+ this._wheelPartialScroll = 0;
382
+ this._touchScrollAccumulator = 0;
383
+ }
384
+
385
+ private _syncMouseModeState(element: HTMLElement): void {
386
+ if (this._mouseStateService.areMouseEventsActive) {
387
+ if (this._optionsService.rawOptions.mouseEventsRequireAlt) {
388
+ this._altMouseCursor?.resetClass();
389
+ this._selectionService.enable();
390
+ } else {
391
+ element.classList.add(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
392
+ this._selectionService.disable();
393
+ }
394
+ } else {
395
+ element.classList.remove(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
396
+ this._selectionService.enable();
397
+ }
398
+ }
399
+
400
+ private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void {
401
+ const { element, document } = ctx.target;
402
+ const { requestedEvents } = ctx;
403
+ // apply global changes on events
404
+ if (events) {
405
+ if (this._optionsService.rawOptions.logLevel === 'debug') {
406
+ this._logService.debug('Binding to mouse events:', this._explainEvents(events));
407
+ }
408
+ } else {
409
+ this._logService.debug('Unbinding from mouse events.');
410
+ }
411
+ this._syncMouseModeState(element);
412
+ this._altMouseCursor?.sync();
413
+
414
+ // add/remove handlers from requestedEvents
415
+ if (!(events & CoreMouseEventType.MOVE)) {
416
+ if (requestedEvents.mousemove) {
417
+ element.removeEventListener('mousemove', requestedEvents.mousemove);
418
+ }
419
+ requestedEvents.mousemove = null;
420
+ } else if (!requestedEvents.mousemove) {
421
+ element.addEventListener('mousemove', eventListeners.mousemove);
422
+ requestedEvents.mousemove = eventListeners.mousemove;
423
+ }
424
+
425
+ if (!(events & CoreMouseEventType.WHEEL)) {
426
+ if (requestedEvents.wheel) {
427
+ element.removeEventListener('wheel', requestedEvents.wheel);
428
+ }
429
+ requestedEvents.wheel = null;
430
+ } else if (!requestedEvents.wheel) {
431
+ element.addEventListener('wheel', eventListeners.wheel, { passive: false });
432
+ requestedEvents.wheel = eventListeners.wheel;
433
+ }
434
+
435
+ if (!(events & CoreMouseEventType.UP)) {
436
+ if (requestedEvents.mouseup) {
437
+ document.removeEventListener('mouseup', requestedEvents.mouseup);
438
+ }
439
+ requestedEvents.mouseup = null;
440
+ } else {
441
+ requestedEvents.mouseup ??= eventListeners.mouseup;
442
+ }
443
+
444
+ if (!(events & CoreMouseEventType.DRAG)) {
445
+ if (requestedEvents.mousedrag) {
446
+ document.removeEventListener('mousemove', requestedEvents.mousedrag);
447
+ }
448
+ requestedEvents.mousedrag = null;
449
+ } else {
450
+ requestedEvents.mousedrag ??= eventListeners.mousedrag;
451
+ }
452
+ }
453
+
454
+ private _applyScrollModifier(amount: number, ev: WheelEvent): number {
455
+ // Multiply the scroll speed when the modifier key is pressed
456
+ if (ev.altKey || ev.ctrlKey || ev.shiftKey) {
457
+ return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
458
+ }
459
+ return amount * this._optionsService.rawOptions.scrollSensitivity;
460
+ }
461
+
462
+ /**
463
+ * Processes a wheel event, accounting for partial scrolls for trackpad, mouse scrolls.
464
+ * This prevents hyper-sensitive scrolling in alt buffer.
465
+ */
466
+ private _consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number {
467
+ // Do nothing if it's not a vertical scroll event
468
+ if (ev.deltaY === 0 || ev.shiftKey) {
469
+ return 0;
470
+ }
471
+
472
+ if (cellHeight === undefined || dpr === undefined) {
473
+ return 0;
474
+ }
475
+
476
+ const targetWheelEventPixels = cellHeight / dpr;
477
+ let amount = this._applyScrollModifier(ev.deltaY, ev);
478
+
479
+ if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
480
+ amount /= (targetWheelEventPixels + 0.0); // Prevent integer division
481
+
482
+ const isLikelyTrackpad = Math.abs(ev.deltaY) < 50;
483
+ if (isLikelyTrackpad) {
484
+ amount *= 0.3;
485
+ }
486
+
487
+ this._wheelPartialScroll += amount;
488
+ amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
489
+ this._wheelPartialScroll %= 1;
490
+ } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
491
+ amount *= this._bufferService.rows;
492
+ }
493
+ return amount;
494
+ }
495
+
496
+ /**
497
+ * Triggers a mouse event to be sent.
498
+ *
499
+ * Returns true if the event passed all protocol restrictions and a report
500
+ * was sent, otherwise false. The return value may be used to decide whether
501
+ * the default event action in the browser component should be omitted.
502
+ *
503
+ * Note: The method will change values of the given event object
504
+ * to fulfill protocol and encoding restrictions.
505
+ */
506
+ private _triggerMouseEvent(e: ICoreMouseEvent): boolean {
507
+ // range check for col/row
508
+ if (e.col < 0 || e.col >= this._bufferService.cols
509
+ || e.row < 0 || e.row >= this._bufferService.rows) {
510
+ return false;
511
+ }
512
+
513
+ // filter nonsense combinations of button + action
514
+ if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) {
515
+ return false;
516
+ }
517
+ if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) {
518
+ return false;
519
+ }
520
+ if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) {
521
+ return false;
522
+ }
523
+
524
+ // report 1-based coords
525
+ e.col++;
526
+ e.row++;
527
+
528
+ // debounce move events at grid or pixel level
529
+ if (e.action === CoreMouseAction.MOVE
530
+ && this._lastEvent
531
+ && this._equalEvents(this._lastEvent, e, this._mouseStateService.isPixelEncoding)
532
+ ) {
533
+ return false;
534
+ }
535
+
536
+ // apply protocol restrictions
537
+ if (!this._mouseStateService.restrictMouseEvent(e)) {
538
+ return false;
539
+ }
540
+
541
+ // encode report and send
542
+ const report = this._mouseStateService.encodeMouseEvent(e);
543
+ if (report) {
544
+ if (this._mouseStateService.isDefaultEncoding) {
545
+ this._coreService.triggerBinaryEvent(report);
546
+ } else {
547
+ this._coreService.triggerDataEvent(report, true);
548
+ }
549
+ }
550
+
551
+ this._lastEvent = e;
552
+ return true;
553
+ }
554
+
555
+ private _explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {
39
556
  return {
40
- col: Math.floor(coords[0] / this._renderService.dimensions.css.cell.width),
41
- row: Math.floor(coords[1] / this._renderService.dimensions.css.cell.height),
42
- x: Math.floor(coords[0]),
43
- y: Math.floor(coords[1])
557
+ down: !!(events & CoreMouseEventType.DOWN),
558
+ up: !!(events & CoreMouseEventType.UP),
559
+ drag: !!(events & CoreMouseEventType.DRAG),
560
+ move: !!(events & CoreMouseEventType.MOVE),
561
+ wheel: !!(events & CoreMouseEventType.WHEEL)
44
562
  };
45
563
  }
564
+
565
+ private _equalEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent, pixels: boolean): boolean {
566
+ if (pixels) {
567
+ if (e1.x !== e2.x) return false;
568
+ if (e1.y !== e2.y) return false;
569
+ } else {
570
+ if (e1.col !== e2.col) return false;
571
+ if (e1.row !== e2.row) return false;
572
+ }
573
+ if (e1.button !== e2.button) return false;
574
+ if (e1.action !== e2.action) return false;
575
+ if (e1.ctrl !== e2.ctrl) return false;
576
+ if (e1.alt !== e2.alt) return false;
577
+ if (e1.shift !== e2.shift) return false;
578
+ return true;
579
+ }
580
+
581
+ }
582
+
583
+ /**
584
+ * Toggles MouseEventCssClasses.ENABLE_MOUSE_EVENTS on the terminal element while alt is held when
585
+ * `mouseEventsRequireAlt` is active. DOM listeners are only registered while active.
586
+ */
587
+ export class AltMouseCursorController implements IDisposable {
588
+ private readonly _listeners = new MutableDisposable<IDisposable>();
589
+
590
+ constructor(
591
+ private readonly _element: HTMLElement,
592
+ private readonly _document: Document,
593
+ private readonly _isActive: () => boolean
594
+ ) {
595
+ }
596
+
597
+ public dispose(): void {
598
+ this._listeners.dispose();
599
+ }
600
+
601
+ public sync(): void {
602
+ this._listeners.clear();
603
+
604
+ if (!this._isActive()) {
605
+ return;
606
+ }
607
+
608
+ const store = new DisposableStore();
609
+ const syncFromModifier = (ev: KeyboardEvent | MouseEvent): void => this.syncFromModifier(ev);
610
+ store.add(addDisposableListener(this._document, 'keydown', syncFromModifier));
611
+ store.add(addDisposableListener(this._document, 'keyup', syncFromModifier));
612
+ store.add(addDisposableListener(this._element, 'mousemove', syncFromModifier));
613
+ const targetWindow = this._element.ownerDocument?.defaultView;
614
+ if (targetWindow) {
615
+ store.add(addDisposableListener(targetWindow, 'blur', () => {
616
+ if (this._isActive()) {
617
+ this.resetClass();
618
+ }
619
+ }));
620
+ }
621
+ this._listeners.value = store;
622
+ }
623
+
624
+ public resetClass(): void {
625
+ this._updateClass(false);
626
+ }
627
+
628
+ public syncFromModifier(ev: KeyboardEvent | MouseEvent): void {
629
+ if (!this._isActive()) {
630
+ return;
631
+ }
632
+ this._updateClass(ev.getModifierState('Alt'));
633
+ }
634
+
635
+ private _updateClass(altHeld: boolean): void {
636
+ if (altHeld) {
637
+ this._element.classList.add(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
638
+ } else {
639
+ this._element.classList.remove(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
640
+ }
641
+ }
46
642
  }