- Notifications
You must be signed in to change notification settings - Fork 1
✨ Execute concurrent terminal panes through a replaceable provider (#730)#738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
960e4b605cde97879bdc3a95d82016758c711ac4963a92e4577850e86a0ecb167ae30feb92602183387013bf322f4b0079b2f826aFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -68,6 +68,10 @@ import { | ||||||||||||||||||
| import type { StructuralViolation, SwitchCase, TerminalPane } from "./structural-rules.ts"; | ||||||||||||||||||
| import { terminalGridLayout } from "./terminal-grid.ts"; | ||||||||||||||||||
| import type { PlacedPane } from "./terminal-grid.ts"; | ||||||||||||||||||
| import { durableGrid, openTerminalGrid, toRequest } from "./terminal/grid.ts"; | ||||||||||||||||||
| import type { PaneWork } from "./terminal/grid.ts"; | ||||||||||||||||||
| import { recordGridLayout } from "./terminal/journal.ts"; | ||||||||||||||||||
| import { usePaneTerminal } from "./terminal/pane.ts"; | ||||||||||||||||||
| import { | ||||||||||||||||||
| asBindingViolation, | ||||||||||||||||||
| asExpressionViolation, | ||||||||||||||||||
| @@ -139,7 +143,7 @@ import { | ||||||||||||||||||
| import { remark } from "remark"; | ||||||||||||||||||
| import { select as cssSelect } from "unist-util-select"; | ||||||||||||||||||
| import { toString as mdastToString } from "mdast-util-to-string"; | ||||||||||||||||||
| import { liveEnvironment } from "./live-env.ts"; | ||||||||||||||||||
| import { derivedEnvironment, liveEnvironment } from "./live-env.ts"; | ||||||||||||||||||
| import { TestHarnessComponentDefinition } from "./test-harness.ts"; | ||||||||||||||||||
| import type { TestHarnessBinding } from "./test-harness.ts"; | ||||||||||||||||||
| @@ -1181,7 +1185,14 @@ function* expandListSegments( | ||||||||||||||||||
| if (segment.name === "Terminal.Grid") { | ||||||||||||||||||
| // No raise() here, like the branches above: expandTerminalGrid | ||||||||||||||||||
| // reports every error it creates. | ||||||||||||||||||
| yield* expandTerminalGrid(segment, result); | ||||||||||||||||||
| yield* expandTerminalGrid(segment, result, { | ||||||||||||||||||
| parentMeta, | ||||||||||||||||||
| parentProps, | ||||||||||||||||||
| hideSet, | ||||||||||||||||||
| path: elementPath, | ||||||||||||||||||
| checkedFailures, | ||||||||||||||||||
| authority, | ||||||||||||||||||
| }); | ||||||||||||||||||
| break; | ||||||||||||||||||
| } | ||||||||||||||||||
| @@ -2102,7 +2113,21 @@ function* resolveStructuralProp( | ||||||||||||||||||
| * does, which is what makes the refusal a closed one rather than a partial grid | ||||||||||||||||||
| * left behind. | ||||||||||||||||||
| */ | ||||||||||||||||||
| function* expandTerminalGrid(segment: ComponentElement, owner: Segment[]): Operation<void> { | ||||||||||||||||||
| /** Everything a pane's own content needs to expand where the grid was written. */ | ||||||||||||||||||
| interface GridSite { | ||||||||||||||||||
| readonly parentMeta: Record<string, unknown>; | ||||||||||||||||||
| readonly parentProps: Record<string, Json>; | ||||||||||||||||||
| readonly hideSet: Set<string>; | ||||||||||||||||||
| readonly path: string; | ||||||||||||||||||
| readonly checkedFailures: CheckedFailures | undefined; | ||||||||||||||||||
| readonly authority: ExpansionAuthority | undefined; | ||||||||||||||||||
| } | ||||||||||||||||||
| function* expandTerminalGrid( | ||||||||||||||||||
| segment: ComponentElement, | ||||||||||||||||||
| owner: Segment[], | ||||||||||||||||||
| site: GridSite, | ||||||||||||||||||
| ): Operation<void> { | ||||||||||||||||||
| const structure = terminalGridStructure(segment); | ||||||||||||||||||
| if (structure.violations.length > 0) { | ||||||||||||||||||
| for (const violation of structure.violations) { | ||||||||||||||||||
| @@ -2137,23 +2162,117 @@ function* expandTerminalGrid(segment: ComponentElement, owner: Segment[]): Opera | ||||||||||||||||||
| } | ||||||||||||||||||
| const layout = terminalGridLayout(columns.value, placed); | ||||||||||||||||||
| owner.push( | ||||||||||||||||||
| yield* raise({ | ||||||||||||||||||
| type: "error", | ||||||||||||||||||
| message: positioned(noTerminalProviderMessage(), segment), | ||||||||||||||||||
| source: "Terminal.Grid", | ||||||||||||||||||
| // The grid the author asked for, carried beside the sentence so an | ||||||||||||||||||
| // assertion is about the layout that was derived rather than about the | ||||||||||||||||||
| // wording of a refusal. | ||||||||||||||||||
| cause: { | ||||||||||||||||||
| layout: { | ||||||||||||||||||
| columns: layout.columns, | ||||||||||||||||||
| rows: layout.rows, | ||||||||||||||||||
| cells: layout.cells.map((cell) => ({ ...cell })), | ||||||||||||||||||
| }, | ||||||||||||||||||
| // The grid renders nothing into the document: what a pane shows belongs to | ||||||||||||||||||
| // that pane, and the sibling after `</Terminal.Grid>` renders to the root | ||||||||||||||||||
| // again only once the provider has restored it. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
| ||||||||||||||||||
| const identity = { | ||||||||||||||||||
| path: site.path, | ||||||||||||||||||
| ...(segment.position === undefined ? {} : { position: segment.position }), | ||||||||||||||||||
| }; | ||||||||||||||||||
| try { | ||||||||||||||||||
| // Recorded in this coroutine, before the lease and before any provider is | ||||||||||||||||||
| // contacted: a resumed run whose grid changed is refused while nothing has | ||||||||||||||||||
| // been opened. It cannot live inside the grid child, because a completed | ||||||||||||||||||
| // child never runs. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
| ||||||||||||||||||
| yield* recordGridLayout(identity, toRequest(layout)); | ||||||||||||||||||
| const retained = yield* durableGrid(function* (boundary) { | ||||||||||||||||||
| const work = structure.panes.map((pane, index) => | ||||||||||||||||||
| paneWork(pane, layout.cells[index]!.title, site), | ||||||||||||||||||
| ); | ||||||||||||||||||
| return yield* openTerminalGrid(layout, work, boundary); | ||||||||||||||||||
| }); | ||||||||||||||||||
| const failed = retained.panes.find((pane) => pane.status === "failed"); | ||||||||||||||||||
| if (failed !== undefined) { | ||||||||||||||||||
| owner.push(yield* raise(terminalGridError(segment, failed.reason))); | ||||||||||||||||||
| } | ||||||||||||||||||
| } catch (error) { | ||||||||||||||||||
| owner.push( | ||||||||||||||||||
| yield* raise( | ||||||||||||||||||
| terminalGridError(segment, error instanceof Error ? error.message : String(error)), | ||||||||||||||||||
| ), | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| /** | ||||||||||||||||||
| * What one authored pane does once the grid has minted its claim. | ||||||||||||||||||
| * | ||||||||||||||||||
| * A self-closing pane runs the host's default shell through its claim. A paired | ||||||||||||||||||
| * pane expands its own content in a scope of its own: it inherits the bindings, | ||||||||||||||||||
| * providers, configuration and working directory visible where the grid was | ||||||||||||||||||
| * written, and everything it creates afterwards stays inside the pane. Its | ||||||||||||||||||
| * `<Break>` cannot reach a loop outside the grid, its `<Return>` cannot claim an | ||||||||||||||||||
| * enclosing body, and a checked failure settles the pane rather than poisoning | ||||||||||||||||||
| * the root or a sibling. | ||||||||||||||||||
| */ | ||||||||||||||||||
| function paneWork(pane: TerminalPane, title: string, site: GridSite): PaneWork { | ||||||||||||||||||
| if (pane.form === "self-closing") { | ||||||||||||||||||
| return { | ||||||||||||||||||
| ordinal: pane.ordinal, | ||||||||||||||||||
| *run(claim, composite) { | ||||||||||||||||||
| const outcome = yield* claim.admit(() => | ||||||||||||||||||
| composite.shell(pane.ordinal, () => claim.ready()), | ||||||||||||||||||
| ); | ||||||||||||||||||
| if (outcome.signal !== undefined) { | ||||||||||||||||||
| throw new Error(`pane ${pane.ordinal} ("${title}") shell ended on ${outcome.signal}`); | ||||||||||||||||||
| } | ||||||||||||||||||
| if (outcome.exitCode !== undefined && outcome.exitCode !== 0) { | ||||||||||||||||||
| throw new Error( | ||||||||||||||||||
| `pane ${pane.ordinal} ("${title}") shell exited with status ${outcome.exitCode}`, | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
| }, | ||||||||||||||||||
| }), | ||||||||||||||||||
| ); | ||||||||||||||||||
| }; | ||||||||||||||||||
| } | ||||||||||||||||||
| return { | ||||||||||||||||||
| ordinal: pane.ordinal, | ||||||||||||||||||
| *run(claim, composite) { | ||||||||||||||||||
| yield* scoped(function* () { | ||||||||||||||||||
| // A pane is not inside the loop the grid was written in, so a <Break> | ||||||||||||||||||
| // in its content has no loop to exit and says so. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
| ||||||||||||||||||
| yield* ActiveLoop.set(undefined); | ||||||||||||||||||
| yield* usePaneTerminal(claim); | ||||||||||||||||||
| const siteEnv = yield* env; | ||||||||||||||||||
| // Starts from what the grid site can see and keeps its own writes: a | ||||||||||||||||||
| // binding this pane makes is visible to later work in this pane and to | ||||||||||||||||||
| // nothing else. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
| ||||||||||||||||||
| yield* provideEnv(derivedEnvironment(siteEnv, { ...(siteEnv?.values ?? {}) })); | ||||||||||||||||||
| const shown: Segment[] = []; | ||||||||||||||||||
| yield* expandSegmentsWithin( | ||||||||||||||||||
| pane.element.children, | ||||||||||||||||||
| site.parentMeta, | ||||||||||||||||||
| site.parentProps, | ||||||||||||||||||
| site.hideSet, | ||||||||||||||||||
| // A counter of its own. Panes expand concurrently, and a shared | ||||||||||||||||||
| // mutable counter would hand two of them block identities that depend | ||||||||||||||||||
| // on which happened to run first. | ||||||||||||||||||
| createBlockCounter(), | ||||||||||||||||||
| shown, | ||||||||||||||||||
| extendPath( | ||||||||||||||||||
| site.path, | ||||||||||||||||||
| elementFrame(pane.element.name, elementSite(pane.element.position, pane.index)), | ||||||||||||||||||
| ), | ||||||||||||||||||
| 0, | ||||||||||||||||||
| // The pane's own ledger: a checked failure settles this pane and | ||||||||||||||||||
| // cannot reach the root or a sibling. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
| ||||||||||||||||||
| containedLedger(site.checkedFailures), | ||||||||||||||||||
| site.authority, | ||||||||||||||||||
| // No enclosing value body: a <Return> written in a pane cannot claim | ||||||||||||||||||
| // one outside the grid. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment — restates what the code does.
Suggested change
| ||||||||||||||||||
| undefined, | ||||||||||||||||||
| ); | ||||||||||||||||||
| const text = renderSegments(shown); | ||||||||||||||||||
| if (text.length > 0) { | ||||||||||||||||||
| yield* composite.display(pane.ordinal, text); | ||||||||||||||||||
| } | ||||||||||||||||||
| }); | ||||||||||||||||||
| }, | ||||||||||||||||||
| }; | ||||||||||||||||||
| } | ||||||||||||||||||
| /** The label one pane displays, from the value its own `title` prop produced. */ | ||||||||||||||||||
| @@ -2168,15 +2287,6 @@ function* resolvePaneTitle(pane: TerminalPane): Operation<Result<string>> { | ||||||||||||||||||
| return terminalTitle(value.value); | ||||||||||||||||||
| } | ||||||||||||||||||
| /** What a complete grid says on a host where nothing can open one. */ | ||||||||||||||||||
| function noTerminalProviderMessage(): string { | ||||||||||||||||||
| return ( | ||||||||||||||||||
| "no terminal provider opened this grid. A host installs the terminal-grid capability " + | ||||||||||||||||||
| "explicitly, and this one installs none, so no pane expanded its content and no default " + | ||||||||||||||||||
| "shell started." | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
| function loopError(segment: ComponentElement, message: string): ErrorSegment { | ||||||||||||||||||
| return { type: "error", message: positioned(message, segment), source: "Loop" }; | ||||||||||||||||||
| } | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant comment — restates what the code does.