@kolisachint/hoocode-tui 0.5.67 → 0.5.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/components/editor.d.ts +16 -0
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +35 -4
- package/dist/components/editor.js.map +1 -1
- package/dist/components/spacer.d.ts +31 -0
- package/dist/components/spacer.d.ts.map +1 -1
- package/dist/components/spacer.js +44 -0
- package/dist/components/spacer.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts +9 -4
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +14 -2
- package/dist/keybindings.js.map +1 -1
- package/dist/mouse.d.ts +75 -0
- package/dist/mouse.d.ts.map +1 -0
- package/dist/mouse.js +115 -0
- package/dist/mouse.js.map +1 -0
- package/dist/terminal.d.ts +30 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +49 -0
- package/dist/terminal.js.map +1 -1
- package/dist/tui.d.ts +335 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +712 -1
- package/dist/tui.js.map +1 -1
- package/dist/undo-stack.d.ts +39 -6
- package/dist/undo-stack.d.ts.map +1 -1
- package/dist/undo-stack.js +59 -6
- package/dist/undo-stack.js.map +1 -1
- package/package.json +1 -1
package/dist/tui.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimal TUI implementation with differential rendering
|
|
3
3
|
*/
|
|
4
|
+
import type { FlexSpacer } from "./components/spacer.js";
|
|
4
5
|
import type { Terminal } from "./terminal.js";
|
|
5
6
|
/**
|
|
6
7
|
* Component interface - all components must implement this
|
|
@@ -51,6 +52,34 @@ export declare function isFocusable(component: Component | null): component is C
|
|
|
51
52
|
* TUI finds and strips this marker, then positions the hardware cursor there.
|
|
52
53
|
*/
|
|
53
54
|
export declare const CURSOR_MARKER = "\u001B_pi:c\u0007";
|
|
55
|
+
/** What the scroll indicator is told about the pinned view. */
|
|
56
|
+
export interface ScrollStatus {
|
|
57
|
+
/** 1-based transcript row at the top of the view. */
|
|
58
|
+
top: number;
|
|
59
|
+
/** 1-based transcript row at the bottom of the view. */
|
|
60
|
+
bottom: number;
|
|
61
|
+
/** Rows in the whole transcript. */
|
|
62
|
+
total: number;
|
|
63
|
+
/** Rows the view shows at once. */
|
|
64
|
+
viewHeight: number;
|
|
65
|
+
atTop: boolean;
|
|
66
|
+
/** True only when the very last row is in view — the point where the pin lets go. */
|
|
67
|
+
atBottom: boolean;
|
|
68
|
+
/** Columns the indicator may fill. */
|
|
69
|
+
width: number;
|
|
70
|
+
/** Present while a search is running; the indicator becomes its query line. */
|
|
71
|
+
search?: ScrollSearchStatus;
|
|
72
|
+
}
|
|
73
|
+
export interface ScrollSearchStatus {
|
|
74
|
+
query: string;
|
|
75
|
+
/** Rows containing a match. */
|
|
76
|
+
count: number;
|
|
77
|
+
/** 1-based position among the matches, or 0 when there are none. */
|
|
78
|
+
index: number;
|
|
79
|
+
/** True while the query is still being typed. */
|
|
80
|
+
typing: boolean;
|
|
81
|
+
}
|
|
82
|
+
export type ScrollStatusFormatter = (status: ScrollStatus) => string;
|
|
54
83
|
/**
|
|
55
84
|
* Anchor position for overlays
|
|
56
85
|
*/
|
|
@@ -125,6 +154,60 @@ export declare class Container implements Component {
|
|
|
125
154
|
removeChild(component: Component): void;
|
|
126
155
|
clear(): void;
|
|
127
156
|
invalidate(): void;
|
|
157
|
+
/**
|
|
158
|
+
* Where each direct child's output starts, in rows, from the last render.
|
|
159
|
+
*
|
|
160
|
+
* Read off the memo rather than recomputed, so asking is a walk over the
|
|
161
|
+
* children's cached line arrays and never a re-render. Undefined before the
|
|
162
|
+
* first render, or at a different width than the caller has in mind — both
|
|
163
|
+
* cases mean "no answer", not "zero".
|
|
164
|
+
*
|
|
165
|
+
* This is what lets something outside the tree point at a row inside it: a
|
|
166
|
+
* component's offset within its container, plus that container's offset at
|
|
167
|
+
* the root, is its absolute row in the buffer the viewport windows over.
|
|
168
|
+
*/
|
|
169
|
+
childRowOffsets(width: number): number[] | undefined;
|
|
170
|
+
render(width: number): string[];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A child that can be taken off screen without disturbing the diff.
|
|
174
|
+
*
|
|
175
|
+
* Hiding a component naively — returning `[]` from its render — is one of the
|
|
176
|
+
* more expensive things you can do to this renderer. `Container.render` and the
|
|
177
|
+
* root's flat cache both decide "did this subtree change" by **array identity**,
|
|
178
|
+
* so a fresh `[]` every frame reads as a change every frame: the memo is
|
|
179
|
+
* dropped, the buffer is re-flattened, and a dirty range is reported for a
|
|
180
|
+
* component that is not even drawn. One frozen array, returned every time,
|
|
181
|
+
* makes a hidden slot free instead.
|
|
182
|
+
*
|
|
183
|
+
* The child is not rendered at all while hidden, which is the other half of the
|
|
184
|
+
* saving — a hidden footer costs nothing to keep hidden. That means a child
|
|
185
|
+
* whose `render` advances an animation or maintains a cache will be paused, not
|
|
186
|
+
* merely invisible; it catches up when shown again. Chrome (footers, panels,
|
|
187
|
+
* status rows) is fine with that. A spinner is not, so do not wrap one.
|
|
188
|
+
*/
|
|
189
|
+
export declare class Slot implements Component {
|
|
190
|
+
private component;
|
|
191
|
+
/** Shared across every hidden slot: identity is all the caches compare. */
|
|
192
|
+
private static readonly EMPTY;
|
|
193
|
+
private hidden;
|
|
194
|
+
constructor(component: Component);
|
|
195
|
+
/** Whoever is in the slot right now. */
|
|
196
|
+
get child(): Component;
|
|
197
|
+
/**
|
|
198
|
+
* Swap the occupant, keeping the slot itself in place.
|
|
199
|
+
*
|
|
200
|
+
* An extension replacing the footer used to remove one root child and append
|
|
201
|
+
* another, which both moved the footer to the end of the tree — behind the
|
|
202
|
+
* widgets meant to sit below it — and handed the root's per-child cache a
|
|
203
|
+
* changed child list every time. The slot is the stable root child; only what
|
|
204
|
+
* is inside it changes.
|
|
205
|
+
*/
|
|
206
|
+
setChild(component: Component): boolean;
|
|
207
|
+
get visible(): boolean;
|
|
208
|
+
/** Returns whether this changed anything, so callers can skip a render. */
|
|
209
|
+
setVisible(visible: boolean): boolean;
|
|
210
|
+
invalidate(): void;
|
|
128
211
|
render(width: number): string[];
|
|
129
212
|
}
|
|
130
213
|
/**
|
|
@@ -169,10 +252,201 @@ export declare class TUI extends Container {
|
|
|
169
252
|
private previousViewportTop;
|
|
170
253
|
private fullRedrawCount;
|
|
171
254
|
private stopped;
|
|
255
|
+
/**
|
|
256
|
+
* The filler that keeps the app the size of the screen.
|
|
257
|
+
*
|
|
258
|
+
* ## Why the app is full-screen at all
|
|
259
|
+
*
|
|
260
|
+
* This renderer appends: a frame is the whole component tree flattened into
|
|
261
|
+
* a line buffer, written from wherever the cursor happens to be. On a fresh
|
|
262
|
+
* session that buffer is a dozen rows, so the banner sat halfway up a
|
|
263
|
+
* terminal with the prompt under it and forty rows of the user's shell
|
|
264
|
+
* history above — and the prompt walked down the screen as the conversation
|
|
265
|
+
* grew, only reaching the bottom row once the session was long enough to
|
|
266
|
+
* scroll. Two different layouts for the same app, and the one you meet first
|
|
267
|
+
* is the one that does not look like an app.
|
|
268
|
+
*
|
|
269
|
+
* ## What this does
|
|
270
|
+
*
|
|
271
|
+
* Before the frame is diffed, the root measures it and gives the leftover
|
|
272
|
+
* rows to one designated child. The buffer is therefore never shorter than
|
|
273
|
+
* the terminal, so the terminal's last row is always the buffer's last row:
|
|
274
|
+
* the header stays at the top, the prompt and the footer stay on the bottom,
|
|
275
|
+
* and everything between them is conversation. Nothing else changes — this
|
|
276
|
+
* is still the normal screen, so scrollback, selection and search all still
|
|
277
|
+
* work, and the session is still on screen after you quit.
|
|
278
|
+
*
|
|
279
|
+
* Set to `undefined` (no flex child) and the old append-only behaviour is
|
|
280
|
+
* exactly what you get back, which is what the tests that predate this and
|
|
281
|
+
* any embedder outside the app rely on.
|
|
282
|
+
*/
|
|
283
|
+
private flexSpacer?;
|
|
284
|
+
/**
|
|
285
|
+
* The pinned viewport.
|
|
286
|
+
*
|
|
287
|
+
* ## What is wrong with letting the terminal do it
|
|
288
|
+
*
|
|
289
|
+
* This renderer keeps the entire transcript in its line buffer and writes it
|
|
290
|
+
* to the normal screen, so "scrolling" has always meant the terminal's own
|
|
291
|
+
* scrollback. That works exactly as long as the app does not repaint — and
|
|
292
|
+
* this one repaints the whole buffer whenever a line *above* the viewport
|
|
293
|
+
* changes, because a positional diff cannot address a row that has scrolled
|
|
294
|
+
* out of reach. The repaint is `\x1b[2J\x1b[H\x1b[3J` followed by the
|
|
295
|
+
* transcript again, and the `\x1b[3J` throws away the scrollback the reader
|
|
296
|
+
* was sitting in. From the reader's side the screen simply jumps to the
|
|
297
|
+
* bottom, for no reason they can see, at a moment they did not choose.
|
|
298
|
+
*
|
|
299
|
+
* ## What this does instead
|
|
300
|
+
*
|
|
301
|
+
* `scrollOffset` is the transcript row drawn at the top of the screen, and
|
|
302
|
+
* `null` means "follow the tail", which is the normal live behaviour and the
|
|
303
|
+
* path everything else in this file was written for. The moment it is a
|
|
304
|
+
* number the TUI switches to the alternate screen and paints a window of the
|
|
305
|
+
* buffer itself: a fixed grid, addressed row by row, with no scrollback for
|
|
306
|
+
* anything to fight over. New output still arrives and still lands in the
|
|
307
|
+
* buffer — it just does not move the window, which is the whole point. The
|
|
308
|
+
* indicator on the last row says how far down the transcript the window is,
|
|
309
|
+
* because a view that cannot move on its own needs to say where it stopped.
|
|
310
|
+
*
|
|
311
|
+
* Going back to live leaves the alternate screen, which restores the normal
|
|
312
|
+
* screen *and its scrollback* exactly as they were, and the next frame is an
|
|
313
|
+
* ordinary differential one that writes only what arrived while the reader
|
|
314
|
+
* was away — see `scrollToLive` for what has to be true for that to be safe.
|
|
315
|
+
* Not a clear-and-replay: on a long session that is a visible flash, a burst
|
|
316
|
+
* of output, and the loss of the scrollback that had just been handed back.
|
|
317
|
+
*/
|
|
318
|
+
private scrollOffset;
|
|
319
|
+
/** Transcript length measured by the last pinned paint; what clamping uses. */
|
|
320
|
+
private scrollTotalLines;
|
|
321
|
+
private scrollStatusFormatter;
|
|
322
|
+
/** The running search: its query, the rows it matched, and where in them. */
|
|
323
|
+
private scrollSearch;
|
|
324
|
+
/**
|
|
325
|
+
* Whether the view may pin right now.
|
|
326
|
+
*
|
|
327
|
+
* The wheel is answered wherever it is turned, including with a picker on
|
|
328
|
+
* screen — and a picker that lost its arrow keys to a pinned view it did not
|
|
329
|
+
* know about would be far worse than a wheel that did nothing. The app sets
|
|
330
|
+
* this because only the app knows which of its surfaces is asking a question.
|
|
331
|
+
* Unset means always.
|
|
332
|
+
*/
|
|
333
|
+
canPinScroll?: () => boolean;
|
|
172
334
|
private focusOrderCounter;
|
|
173
335
|
private overlayStack;
|
|
174
336
|
constructor(terminal: Terminal, showHardwareCursor?: boolean);
|
|
175
337
|
get fullRedraws(): number;
|
|
338
|
+
/** True while the view is pinned rather than following the tail. */
|
|
339
|
+
get scrollPinned(): boolean;
|
|
340
|
+
/** Where the pinned window sits, or null while live. */
|
|
341
|
+
getScrollPosition(): {
|
|
342
|
+
top: number;
|
|
343
|
+
total: number;
|
|
344
|
+
viewHeight: number;
|
|
345
|
+
} | null;
|
|
346
|
+
/** Let the app paint the indicator in its own theme. */
|
|
347
|
+
setScrollStatusFormatter(formatter: ScrollStatusFormatter): void;
|
|
348
|
+
/**
|
|
349
|
+
* Nominate the child that absorbs the leftover rows (see `flexSpacer`).
|
|
350
|
+
*
|
|
351
|
+
* It must already be a child of the root, and it should sit between the part
|
|
352
|
+
* of the tree that flows from the top and the chrome that hangs off the
|
|
353
|
+
* bottom — everything after it is what gets pinned to the foot of the screen.
|
|
354
|
+
*/
|
|
355
|
+
setFlexSpacer(spacer: FlexSpacer | undefined): void;
|
|
356
|
+
/**
|
|
357
|
+
* Give the flex child whatever the frame did not use, at `height` rows.
|
|
358
|
+
*
|
|
359
|
+
* Returns true when the height moved, meaning the caller has to flatten
|
|
360
|
+
* again — the measurement can only be made from a finished frame, so the
|
|
361
|
+
* frame that answers it is always the second one. Both passes are cheap
|
|
362
|
+
* after the first: every other child returns its memoized array untouched.
|
|
363
|
+
*/
|
|
364
|
+
private fitFlexSpacer;
|
|
365
|
+
/**
|
|
366
|
+
* The screen rows a pinned window shows, the last one being the indicator.
|
|
367
|
+
*
|
|
368
|
+
* The indicator is not optional: a pinned view looks exactly like a live one
|
|
369
|
+
* that has gone quiet, and a reader who cannot tell the two apart will wait
|
|
370
|
+
* for output that is arriving perfectly well just out of sight.
|
|
371
|
+
*/
|
|
372
|
+
private scrollViewHeight;
|
|
373
|
+
/** Rows available to scroll through — the live buffer while live, the
|
|
374
|
+
* measured one while pinned. The filler is not transcript: counting it would
|
|
375
|
+
* let a session with nothing above the fold pin itself one row off the
|
|
376
|
+
* bottom and paint a screen of blanks. */
|
|
377
|
+
private transcriptLength;
|
|
378
|
+
/**
|
|
379
|
+
* Move the view by `delta` rows; negative is towards the start.
|
|
380
|
+
*
|
|
381
|
+
* Returns whether anything moved, so a caller can let the key fall through
|
|
382
|
+
* to whatever else wants it when there is nothing to scroll.
|
|
383
|
+
*/
|
|
384
|
+
scrollByLines(delta: number): boolean;
|
|
385
|
+
/**
|
|
386
|
+
* Move by pages, keeping two rows of overlap.
|
|
387
|
+
*
|
|
388
|
+
* A page that moves a full screen leaves nothing in common between before
|
|
389
|
+
* and after, and the reader has to find their place again on every press.
|
|
390
|
+
* The two kept rows are what makes the jump readable.
|
|
391
|
+
*/
|
|
392
|
+
scrollByPages(delta: number): boolean;
|
|
393
|
+
/** Pin the view to the very start of the transcript. */
|
|
394
|
+
scrollToTop(): boolean;
|
|
395
|
+
/** Release the pin and follow the tail again. */
|
|
396
|
+
scrollToLive(): boolean;
|
|
397
|
+
/**
|
|
398
|
+
* Find rows containing `query`, and pin the view to the nearest one above.
|
|
399
|
+
*
|
|
400
|
+
* Searching *backwards* first is the `ctrl+r` convention and it is the right
|
|
401
|
+
* one here: what you are looking for in a session is nearly always behind
|
|
402
|
+
* you, and the most recent occurrence is nearly always the one you meant.
|
|
403
|
+
*
|
|
404
|
+
* Matching is over the visible text, so it finds what the screen shows
|
|
405
|
+
* rather than the escape sequences underneath it — a query containing a
|
|
406
|
+
* colour code is not something anyone ever means.
|
|
407
|
+
*
|
|
408
|
+
* Returns how many rows matched.
|
|
409
|
+
*/
|
|
410
|
+
setScrollSearch(query: string, options?: {
|
|
411
|
+
typing?: boolean;
|
|
412
|
+
}): number;
|
|
413
|
+
/**
|
|
414
|
+
* Step to the next match, `-1` being further back through the session.
|
|
415
|
+
*
|
|
416
|
+
* Wraps, because a search that stops dead at the last match makes you
|
|
417
|
+
* retype it to get back to the first.
|
|
418
|
+
*/
|
|
419
|
+
scrollSearchStep(direction: 1 | -1): boolean;
|
|
420
|
+
/** Stop typing the query but keep the matches, so n/N can step them. */
|
|
421
|
+
commitScrollSearch(): void;
|
|
422
|
+
/** Drop the search, leaving the view where it is. */
|
|
423
|
+
clearScrollSearch(): void;
|
|
424
|
+
get scrollSearchActive(): boolean;
|
|
425
|
+
/** The query being searched for, or "" when there is no search. */
|
|
426
|
+
get scrollSearchQuery(): string;
|
|
427
|
+
private scrollSearchStatus;
|
|
428
|
+
/**
|
|
429
|
+
* Rows whose visible text contains `query`, case-insensitively.
|
|
430
|
+
*
|
|
431
|
+
* One pass over the buffer, run when the query changes rather than per
|
|
432
|
+
* frame. The results are re-measured if the transcript has grown since —
|
|
433
|
+
* rare while someone is reading, and wrong in a way people notice if skipped.
|
|
434
|
+
*/
|
|
435
|
+
private findScrollMatches;
|
|
436
|
+
/** Re-run the search if the buffer grew under it. */
|
|
437
|
+
private refreshScrollSearch;
|
|
438
|
+
/**
|
|
439
|
+
* Mark the query where it appears in a row about to be painted.
|
|
440
|
+
*
|
|
441
|
+
* Done at paint time, over the handful of rows on screen, rather than stored
|
|
442
|
+
* per match — highlighting the whole buffer to show a screenful would be the
|
|
443
|
+
* same work multiplied by the session's length.
|
|
444
|
+
*
|
|
445
|
+
* The row is sliced by display column so the styling already in it survives:
|
|
446
|
+
* a match inside a coloured span keeps its colour and gains the marker.
|
|
447
|
+
*/
|
|
448
|
+
private highlightScrollMatches;
|
|
449
|
+
private setScrollOffset;
|
|
176
450
|
getShowHardwareCursor(): boolean;
|
|
177
451
|
setShowHardwareCursor(enabled: boolean): void;
|
|
178
452
|
getClearOnShrink(): boolean;
|
|
@@ -182,6 +456,28 @@ export declare class TUI extends Container {
|
|
|
182
456
|
* When false, empty rows remain (reduces redraws on slower terminals).
|
|
183
457
|
*/
|
|
184
458
|
setClearOnShrink(enabled: boolean): void;
|
|
459
|
+
/** The component keystrokes are currently going to. */
|
|
460
|
+
get focused(): Component | null;
|
|
461
|
+
/**
|
|
462
|
+
* The root's own child offsets, which the flat cache already tracks.
|
|
463
|
+
*
|
|
464
|
+
* The base implementation reads `Container.renderMemo`, which the root does
|
|
465
|
+
* not keep — it has `flatCache` instead, holding exactly this. Falling
|
|
466
|
+
* through to the base would quietly return undefined forever.
|
|
467
|
+
*/
|
|
468
|
+
childRowOffsets(width: number): number[] | undefined;
|
|
469
|
+
/**
|
|
470
|
+
* Pin the view so `row` is on screen, without demanding it be at the top.
|
|
471
|
+
*
|
|
472
|
+
* A jump that always parks its target on the first row throws away whatever
|
|
473
|
+
* led up to it, and what led up to a message is most of what makes it
|
|
474
|
+
* readable. A row already comfortably in view is left where it is, so
|
|
475
|
+
* stepping through nearby landmarks does not make the screen lurch for each
|
|
476
|
+
* one.
|
|
477
|
+
*/
|
|
478
|
+
scrollToRow(row: number, options?: {
|
|
479
|
+
context?: number;
|
|
480
|
+
}): boolean;
|
|
185
481
|
setFocus(component: Component | null): void;
|
|
186
482
|
/**
|
|
187
483
|
* Show an overlay component with configurable positioning and sizing.
|
|
@@ -205,6 +501,24 @@ export declare class TUI extends Container {
|
|
|
205
501
|
requestRender(force?: boolean): void;
|
|
206
502
|
private scheduleRender;
|
|
207
503
|
private handleInput;
|
|
504
|
+
/**
|
|
505
|
+
* Act on every mouse report in `data` and return what is left of it.
|
|
506
|
+
*
|
|
507
|
+
* Returns null when the chunk was nothing but reports. Reports arrive
|
|
508
|
+
* coalesced — a flick of the wheel delivers a run of them in one read, and a
|
|
509
|
+
* keystroke pressed during the flick rides along behind — so they are peeled
|
|
510
|
+
* off one at a time instead of the chunk being classified as a whole.
|
|
511
|
+
*/
|
|
512
|
+
private consumeMouseReports;
|
|
513
|
+
/**
|
|
514
|
+
* What the mouse does.
|
|
515
|
+
*
|
|
516
|
+
* Only the wheel is acted on. Clicks are swallowed rather than handled:
|
|
517
|
+
* reporting is on for the wheel's sake, and a click that fell through to the
|
|
518
|
+
* focused component would arrive as the raw report text in whatever field
|
|
519
|
+
* has focus.
|
|
520
|
+
*/
|
|
521
|
+
private handleMouseEvent;
|
|
208
522
|
/** Run a requested render now, bypassing the coalescing delay. Used for
|
|
209
523
|
* input-driven frames where echo latency matters more than batching. */
|
|
210
524
|
private expediteRender;
|
|
@@ -256,6 +570,27 @@ export declare class TUI extends Container {
|
|
|
256
570
|
* or the child list changed.
|
|
257
571
|
*/
|
|
258
572
|
render(width: number): string[];
|
|
573
|
+
/**
|
|
574
|
+
* Paint the pinned window onto the alternate screen.
|
|
575
|
+
*
|
|
576
|
+
* Deliberately not differential. The alternate screen is `rows` tall and
|
|
577
|
+
* nothing else writes to it, so a whole frame is at most a screenful of
|
|
578
|
+
* cells inside one synchronized-output pair — cheaper to emit than the
|
|
579
|
+
* bookkeeping a diff would need, and with nothing to get out of step with.
|
|
580
|
+
* The differential renderer's state is left exactly as the last live frame
|
|
581
|
+
* left it, because `scrollToLive` throws it away rather than resuming from it.
|
|
582
|
+
*/
|
|
583
|
+
private renderScrollView;
|
|
584
|
+
/**
|
|
585
|
+
* One transcript row, ready for the pinned window.
|
|
586
|
+
*
|
|
587
|
+
* Images are named rather than drawn. A kitty or iTerm image is placed by
|
|
588
|
+
* the cursor and sized in pixels, so the same escape replayed at a different
|
|
589
|
+
* screen row lands somewhere the window did not ask for and survives the
|
|
590
|
+
* frame that was supposed to replace it — a smear across the view that no
|
|
591
|
+
* later repaint can clear.
|
|
592
|
+
*/
|
|
593
|
+
private emitScrollLine;
|
|
259
594
|
private doRender;
|
|
260
595
|
/**
|
|
261
596
|
* Build the escape sequence that parks the hardware cursor for this frame.
|