@xterm/xterm 6.1.0-beta.29 → 6.1.0-beta.290

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