@ebowwa/coder 0.7.66 → 0.7.69
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.
- package/dist/index.js +209 -23
- package/dist/index.js.map +156 -0
- package/dist/interfaces/ui/terminal/cli/bootstrap.js.map +141 -0
- package/dist/interfaces/ui/terminal/cli/index.js +202 -16
- package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
- package/dist/native/index.darwin-arm64.node +0 -0
- package/native/index.darwin-arm64.node +0 -0
- package/package.json +2 -2
- package/packages/src/core/api-client-impl.ts +166 -25
- package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +31 -2
- package/packages/src/interfaces/ui/terminal/cli/interactive/scroll-handler.ts +284 -0
- package/packages/src/native/index.ts +1 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scroll Handler - Chat History Scrolling
|
|
3
|
+
*
|
|
4
|
+
* Manages scroll state for the chat message history.
|
|
5
|
+
* Scroll offset represents how many messages to "hide" from the bottom (newer messages).
|
|
6
|
+
*
|
|
7
|
+
* scrollOffset = 0 -> Show latest messages (bottom)
|
|
8
|
+
* scrollOffset > 0 -> Show older messages (scrolled up)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { NativeKeyEvent } from "./types.js";
|
|
12
|
+
import { KeyEvents } from "./input-handler.js";
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// TYPES
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
export interface ScrollState {
|
|
19
|
+
/** Number of messages hidden from bottom (0 = show latest) */
|
|
20
|
+
offset: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ScrollConfig {
|
|
24
|
+
/** Messages to scroll per PageUp/PageDown */
|
|
25
|
+
pageScrollAmount: number;
|
|
26
|
+
/** Messages to scroll per Shift+Up/Down */
|
|
27
|
+
lineScrollAmount: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================
|
|
31
|
+
// DEFAULTS
|
|
32
|
+
// ============================================
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_SCROLL_CONFIG: ScrollConfig = {
|
|
35
|
+
pageScrollAmount: 3,
|
|
36
|
+
lineScrollAmount: 1,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ============================================
|
|
40
|
+
// SCROLL HANDLER CLASS
|
|
41
|
+
// ============================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handles chat history scrolling with keyboard input
|
|
45
|
+
*
|
|
46
|
+
* Usage:
|
|
47
|
+
* ```ts
|
|
48
|
+
* const scrollHandler = new ScrollHandler();
|
|
49
|
+
*
|
|
50
|
+
* // In input handler
|
|
51
|
+
* const result = scrollHandler.handleKeyEvent(event, totalMessages);
|
|
52
|
+
* if (result.handled) {
|
|
53
|
+
* state.scrollOffset = result.newOffset;
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export class ScrollHandler {
|
|
58
|
+
private config: ScrollConfig;
|
|
59
|
+
private _offset: number = 0;
|
|
60
|
+
|
|
61
|
+
constructor(config: Partial<ScrollConfig> = {}) {
|
|
62
|
+
this.config = { ...DEFAULT_SCROLL_CONFIG, ...config };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Current scroll offset */
|
|
66
|
+
get offset(): number {
|
|
67
|
+
return this._offset;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Set scroll offset directly */
|
|
71
|
+
set offset(value: number) {
|
|
72
|
+
this._offset = Math.max(0, value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handle a keyboard event for scrolling
|
|
77
|
+
* Returns the new offset and whether it event was handled
|
|
78
|
+
*/
|
|
79
|
+
handleKeyEvent(
|
|
80
|
+
event: NativeKeyEvent,
|
|
81
|
+
totalMessages: number
|
|
82
|
+
): { handled: boolean; newOffset: number } {
|
|
83
|
+
// Shift+Up = scroll up (show older messages, increase offset)
|
|
84
|
+
if (KeyEvents.isUp(event) && event.shift) {
|
|
85
|
+
const maxScroll = this.calculateMaxScroll(totalMessages);
|
|
86
|
+
const newOffset = Math.min(this._offset + this.config.lineScrollAmount, maxScroll);
|
|
87
|
+
this._offset = newOffset;
|
|
88
|
+
return { handled: true, newOffset };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Shift+Down = scroll down (show newer messages, decrease offset)
|
|
92
|
+
if (KeyEvents.isDown(event) && event.shift) {
|
|
93
|
+
const newOffset = Math.max(0, this._offset - this.config.lineScrollAmount);
|
|
94
|
+
this._offset = newOffset;
|
|
95
|
+
return { handled: true, newOffset };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// PageUp = scroll up by page amount
|
|
99
|
+
if (KeyEvents.isPageUp(event)) {
|
|
100
|
+
const maxScroll = this.calculateMaxScroll(totalMessages);
|
|
101
|
+
const newOffset = Math.min(this._offset + this.config.pageScrollAmount, maxScroll);
|
|
102
|
+
this._offset = newOffset;
|
|
103
|
+
return { handled: true, newOffset };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// PageDown = scroll down by page amount
|
|
107
|
+
if (KeyEvents.isPageDown(event)) {
|
|
108
|
+
const newOffset = Math.max(0, this._offset - this.config.pageScrollAmount);
|
|
109
|
+
this._offset = newOffset;
|
|
110
|
+
return { handled: true, newOffset };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Not a scroll event
|
|
114
|
+
return { handled: false, newOffset: this._offset };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Calculate maximum scroll offset
|
|
119
|
+
* Returns the maximum number of lines you can scroll up
|
|
120
|
+
* This allows scrolling even with a single message
|
|
121
|
+
*/
|
|
122
|
+
private calculateMaxScroll(totalMessages: number, estimatedLines: number = 50): number {
|
|
123
|
+
// If we have messages, allow scrolling through all their estimated lines
|
|
124
|
+
// Even 1 message with many lines should be scrollable
|
|
125
|
+
if (totalMessages === 0) return 0;
|
|
126
|
+
// Estimate: each message has ~5 lines on average, minimum 10 lines scrollable
|
|
127
|
+
const estimatedTotalLines = Math.max(totalMessages * 5, 10);
|
|
128
|
+
return Math.max(0, estimatedTotalLines);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Reset scroll to bottom (show latest)
|
|
133
|
+
*/
|
|
134
|
+
reset(): void {
|
|
135
|
+
this._offset = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if currently scrolled (not at bottom)
|
|
140
|
+
*/
|
|
141
|
+
get isScrolled(): boolean {
|
|
142
|
+
return this._offset > 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get scroll info for display
|
|
147
|
+
*/
|
|
148
|
+
getScrollInfo(totalMessages: number, visibleCount: number): {
|
|
149
|
+
isScrollable: boolean;
|
|
150
|
+
olderCount: number;
|
|
151
|
+
newerCount: number;
|
|
152
|
+
visibleRange: string;
|
|
153
|
+
} {
|
|
154
|
+
const maxScroll = this.calculateMaxScroll(totalMessages);
|
|
155
|
+
const isScrollable = totalMessages > 1;
|
|
156
|
+
|
|
157
|
+
// How many older messages are hidden above the view
|
|
158
|
+
const olderCount = this._offset;
|
|
159
|
+
|
|
160
|
+
// How many newer messages are hidden below the view
|
|
161
|
+
const newerCount = Math.max(0, totalMessages - visibleCount - olderCount);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
isScrollable,
|
|
165
|
+
olderCount,
|
|
166
|
+
newerCount,
|
|
167
|
+
visibleRange: `${visibleCount}/${totalMessages}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================
|
|
173
|
+
// STANDALONE FUNCTIONS
|
|
174
|
+
// ============================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Handle scroll key events (stateless version)
|
|
178
|
+
* Use this if you prefer functional style over class-based
|
|
179
|
+
*
|
|
180
|
+
* Keybindings:
|
|
181
|
+
* - PageUp: scroll up by page (always works)
|
|
182
|
+
* - PageDown: scroll down by page (always works)
|
|
183
|
+
* - Home: reset to bottom (show latest)
|
|
184
|
+
* - Shift+Up/Down: line scroll (may not work in all terminals)
|
|
185
|
+
* - Alt+Up/Down: line scroll (alternative)
|
|
186
|
+
* - Ctrl+Up/Down: line scroll (alternative)
|
|
187
|
+
*/
|
|
188
|
+
export function handleScrollEvent(
|
|
189
|
+
event: NativeKeyEvent,
|
|
190
|
+
currentOffset: number,
|
|
191
|
+
totalMessages: number,
|
|
192
|
+
config: Partial<ScrollConfig> = {}
|
|
193
|
+
): { handled: boolean; newOffset: number } {
|
|
194
|
+
const { pageScrollAmount, lineScrollAmount } = { ...DEFAULT_SCROLL_CONFIG, ...config };
|
|
195
|
+
|
|
196
|
+
// Calculate max scroll - allow scrolling even with few messages
|
|
197
|
+
// Minimum scroll range of 10 to handle small terminals
|
|
198
|
+
const maxScroll = Math.max(0, Math.max(totalMessages - 1, 10));
|
|
199
|
+
|
|
200
|
+
// Debug: log what we're receiving
|
|
201
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
202
|
+
console.error("[ScrollHandler] Event:", {
|
|
203
|
+
code: event.code,
|
|
204
|
+
shift: event.shift,
|
|
205
|
+
ctrl: event.ctrl,
|
|
206
|
+
alt: event.alt,
|
|
207
|
+
is_special: event.is_special,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// PageUp = scroll up by page (always works)
|
|
212
|
+
if (KeyEvents.isPageUp(event)) {
|
|
213
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
214
|
+
console.error("[ScrollHandler] PageUp detected, scrolling up");
|
|
215
|
+
}
|
|
216
|
+
return { handled: true, newOffset: Math.min(currentOffset + pageScrollAmount, maxScroll) };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// PageDown = scroll down by page
|
|
220
|
+
if (KeyEvents.isPageDown(event)) {
|
|
221
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
222
|
+
console.error("[ScrollHandler] PageDown detected, scrolling down");
|
|
223
|
+
}
|
|
224
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - pageScrollAmount) };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Home = reset to bottom (show latest)
|
|
228
|
+
if (KeyEvents.isHome(event)) {
|
|
229
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
230
|
+
console.error("[ScrollHandler] Home detected, resetting to bottom");
|
|
231
|
+
}
|
|
232
|
+
return { handled: true, newOffset: 0 };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Alt+Up = scroll up (works in most terminals)
|
|
236
|
+
if (KeyEvents.isUp(event) && event.alt) {
|
|
237
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
238
|
+
console.error("[ScrollHandler] Alt+Up detected, scrolling up");
|
|
239
|
+
}
|
|
240
|
+
return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Alt+Down = scroll down
|
|
244
|
+
if (KeyEvents.isDown(event) && event.alt) {
|
|
245
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
246
|
+
console.error("[ScrollHandler] Alt+Down detected, scrolling down");
|
|
247
|
+
}
|
|
248
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Ctrl+Up = scroll up (alternative)
|
|
252
|
+
if (KeyEvents.isUp(event) && event.ctrl) {
|
|
253
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
254
|
+
console.error("[ScrollHandler] Ctrl+Up detected, scrolling up");
|
|
255
|
+
}
|
|
256
|
+
return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Ctrl+Down = scroll down (alternative)
|
|
260
|
+
if (KeyEvents.isDown(event) && event.ctrl) {
|
|
261
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
262
|
+
console.error("[ScrollHandler] Ctrl+Down detected, scrolling down");
|
|
263
|
+
}
|
|
264
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Shift+Up = scroll up (fallback, may not work in all terminals)
|
|
268
|
+
if (KeyEvents.isUp(event) && event.shift) {
|
|
269
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
270
|
+
console.error("[ScrollHandler] Shift+Up detected, scrolling up");
|
|
271
|
+
}
|
|
272
|
+
return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Shift+Down = scroll down (fallback)
|
|
276
|
+
if (KeyEvents.isDown(event) && event.shift) {
|
|
277
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
278
|
+
console.error("[ScrollHandler] Shift+Down detected, scrolling down");
|
|
279
|
+
}
|
|
280
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return { handled: false, newOffset: currentOffset };
|
|
284
|
+
}
|