| ... | @@ -434,10 +434,11 @@ interface WidgetState { | ... | @@ -434,10 +434,11 @@ interface WidgetState { |
| 434 | export function createTerminalWidgetHost( | 434 | export function createTerminalWidgetHost( |
| 435 | env: TerminalWidgetHostOptions, | 435 | env: TerminalWidgetHostOptions, |
| 436 | ): WidgetHost { | 436 | ): WidgetHost { |
| 437 | const { lockTerminal, now, delay, writeOutputTemporaryLock } = env; | 437 | const { lockTerminal, now, delay, writeOutputTemporaryLock, color } = env; |
| 438 | | 438 | |
| 439 | let timer: async.Cancelable<void> | null = null; | 439 | let timer: async.Cancelable<void> | null = null; |
| 440 | | 440 | |
| | 441 | let rendering = false; |
| 441 | let locks = 0; | 442 | let locks = 0; |
| 442 | let redrawTime = 0; | 443 | let redrawTime = 0; |
| 443 | let lastFlush = 0; | 444 | let lastFlush = 0; |
| ... | @@ -454,6 +455,8 @@ export function createTerminalWidgetHost( | ... | @@ -454,6 +455,8 @@ export function createTerminalWidgetHost( |
| 454 | | 455 | |
| 455 | function redrawCallback() { | 456 | function redrawCallback() { |
| 456 | timer = null; | 457 | timer = null; |
| | 458 | ASSERT(!rendering); |
| | 459 | rendering = true; |
| 457 | redrawTime = (lastFlush = now()) - 0.00001; // windows time precision workaround | 460 | redrawTime = (lastFlush = now()) - 0.00001; // windows time precision workaround |
| 458 | | 461 | |
| 459 | // trivial path when not using widgets | 462 | // trivial path when not using widgets |
| ... | @@ -479,6 +482,7 @@ export function createTerminalWidgetHost( | ... | @@ -479,6 +482,7 @@ export function createTerminalWidgetHost( |
| 479 | } | 482 | } |
| 480 | partialLineIndex = partialLineLength(buffer); | 483 | partialLineIndex = partialLineLength(buffer); |
| 481 | buffer = ""; | 484 | buffer = ""; |
| | 485 | rendering = false |
| 482 | return; | 486 | return; |
| 483 | } | 487 | } |
| 484 | | 488 | |
| ... | @@ -488,11 +492,17 @@ export function createTerminalWidgetHost( | ... | @@ -488,11 +492,17 @@ export function createTerminalWidgetHost( |
| 488 | let newWidgetLines: string[] = []; | 492 | let newWidgetLines: string[] = []; |
| 489 | let next = Infinity; | 493 | let next = Infinity; |
| 490 | for (let w = 0, { length } = widgets; w < length; w += 1) { | 494 | for (let w = 0, { length } = widgets; w < length; w += 1) { |
| 491 | const out = UNWRAP(widgets[w]).format({ | 495 | const widget = UNWRAP(widgets[w]); |
| 492 | now: lastFlush, | 496 | let out: string | { text: string } | null; |
| 493 | width: columns, | 497 | try { |
| 494 | height: rows, | 498 | out = widget.format({ |
| 495 | }); | 499 | now: lastFlush, |
| | 500 | width: columns, |
| | 501 | height: rows, |
| | 502 | }); |
| | 503 | } catch (e) { |
| | 504 | out = e instanceof Error ? stack.format(e, color) : errors.message(e); |
| | 505 | } |
| 496 | if (!out) { | 506 | if (!out) { |
| 497 | widgets.splice(w, 1); | 507 | widgets.splice(w, 1); |
| 498 | UNWRAP(internals.splice(w, 1)[0]); | 508 | UNWRAP(internals.splice(w, 1)[0]); |
| ... | @@ -513,6 +523,8 @@ export function createTerminalWidgetHost( | ... | @@ -513,6 +523,8 @@ export function createTerminalWidgetHost( |
| 513 | newWidgetLines = newWidgetLines.slice(0, rows - 1); | 523 | newWidgetLines = newWidgetLines.slice(0, rows - 1); |
| 514 | if (next < Infinity) redrawSoon(next); | 524 | if (next < Infinity) redrawSoon(next); |
| 515 | | 525 | |
| | 526 | terminal ??= lockTerminal(); |
| | 527 | |
| 516 | if (!newWidgetLines[0]) { | 528 | if (!newWidgetLines[0]) { |
| 517 | ASSERT(!needsToSaveCursor); | 529 | ASSERT(!needsToSaveCursor); |
| 518 | if (lines.length > 0) { | 530 | if (lines.length > 0) { |
| ... | @@ -532,6 +544,7 @@ export function createTerminalWidgetHost( | ... | @@ -532,6 +544,7 @@ export function createTerminalWidgetHost( |
| 532 | if (buffer) terminal.writeOutput(buffer); | 544 | if (buffer) terminal.writeOutput(buffer); |
| 533 | buffer = ""; | 545 | buffer = ""; |
| 534 | if (hasSyncStart) terminal.writeInteractive(ansi.syncEnd); | 546 | if (hasSyncStart) terminal.writeInteractive(ansi.syncEnd); |
| | 547 | rendering = false; |
| 535 | return; | 548 | return; |
| 536 | } | 549 | } |
| 537 | | 550 | |
| ... | @@ -631,19 +644,19 @@ export function createTerminalWidgetHost( | ... | @@ -631,19 +644,19 @@ export function createTerminalWidgetHost( |
| 631 | needsToRestoreCursor ||= needsToSaveCursor; | 644 | needsToRestoreCursor ||= needsToSaveCursor; |
| 632 | needsToSaveCursor = false; | 645 | needsToSaveCursor = false; |
| 633 | lines = newWidgetLines; | 646 | lines = newWidgetLines; |
| 634 | | | |
| 635 | buffer = ""; | 647 | buffer = ""; |
| | 648 | rendering = false; |
| 636 | } | 649 | } |
| 637 | | 650 | |
| 638 | function redrawSoon(ms: number) { | 651 | function redrawSoon(ms: number) { |
| 639 | if (locks > 0 || (ms === 0 && timer)) return; | 652 | if (locks > 0 || (ms === 0 && rendering)) return; |
| 640 | const newRedrawTime = now() + ms; | 653 | const newRedrawTime = now() + ms; |
| 641 | if (timer) { | 654 | if (timer) { |
| 642 | if (redrawTime < newRedrawTime) return; | 655 | if (redrawTime < newRedrawTime) return; |
| 643 | timer.cancel(); // cancel previous | 656 | timer.cancel(); // cancel previous |
| 644 | timer = null; | | |
| 645 | } | 657 | } |
| 646 | redrawTime = newRedrawTime + 1; | 658 | // 1ms wiggle room, generally runtimes have much larger variance on timers |
| | 659 | redrawTime = newRedrawTime - 1; |
| 647 | timer = delay(ms); | 660 | timer = delay(ms); |
| 648 | timer.then(redrawCallback); | 661 | timer.then(redrawCallback); |
| 649 | } | 662 | } |
| ... | @@ -690,6 +703,7 @@ export function createTerminalWidgetHost( | ... | @@ -690,6 +703,7 @@ export function createTerminalWidgetHost( |
| 690 | if (chunk) buffer += chunk, redrawSoon(0); | 703 | if (chunk) buffer += chunk, redrawSoon(0); |
| 691 | }, | 704 | }, |
| 692 | getDrawLock(mode) { | 705 | getDrawLock(mode) { |
| | 706 | if (rendering) ASSERT(locks === 0); |
| 693 | if (locks === 0) { | 707 | if (locks === 0) { |
| 694 | flushAndClear(mode === "short"); | 708 | flushAndClear(mode === "short"); |
| 695 | if (widgets.length > 0 && terminal) { | 709 | if (widgets.length > 0 && terminal) { |
| ... | @@ -756,12 +770,14 @@ export function createTerminalWidgetHost( | ... | @@ -756,12 +770,14 @@ export function createTerminalWidgetHost( |
| 756 | }; | 770 | }; |
| 757 | }, | 771 | }, |
| 758 | cancel() { | 772 | cancel() { |
| | 773 | ASSERT(!rendering, "cannot call cancel() during rendering"); |
| 759 | flushAndClear(false); | 774 | flushAndClear(false); |
| 760 | widgets.splice(0, widgets.length); | 775 | widgets.splice(0, widgets.length); |
| | 776 | internals.splice(0, internals.length); |
| 761 | }, | 777 | }, |
| 762 | delay, | 778 | delay, |
| 763 | now, | 779 | now, |
| 764 | capabilities: env.color ? ["widget", "color"] : ["widget"], | 780 | capabilities: color ? ["widget", "color"] : ["widget"], |
| 765 | }; | 781 | }; |
| 766 | } | 782 | } |
| 767 | | 783 | |
| ... | @@ -1200,6 +1216,7 @@ export type MessageFormatFunction = ( | ... | @@ -1200,6 +1216,7 @@ export type MessageFormatFunction = ( |
| 1200 | ) => string; | 1216 | ) => string; |
| 1201 | | 1217 | |
| 1202 | import { ASSERT, UNWRAP } from "./assert.ts"; | 1218 | import { ASSERT, UNWRAP } from "./assert.ts"; |
| | 1219 | import * as errors from "./error.ts"; |
| 1203 | import * as async from "./async.ts"; | 1220 | import * as async from "./async.ts"; |
| 1204 | import * as stack from "./log/stack.ts"; | 1221 | import * as stack from "./log/stack.ts"; |
| 1205 | import * as node from "./node.ts"; | 1222 | import * as node from "./node.ts"; |