-
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
Changes from all commits
6f088a2
259520e
9bfbf18
75bdb27
08c275f
14ae876
271c627
de092e7
871c9bd
ce6a37a
64292a4
4cb91ed
4f22ff4
824d2f0
5b51988
1018291
4b1bf53
bb5c125
0828774
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| 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, | ||||||||||||||||||||||
|
|
@@ -143,7 +147,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"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -1185,7 +1189,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; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -2106,7 +2117,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) { | ||||||||||||||||||||||
|
|
@@ -2141,23 +2166,119 @@ 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
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
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 created its terminal. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * A self-closing pane runs the host's default shell as a terminal activity, | ||||||||||||||||||||||
| * exactly as a paired pane's content does. 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(terminal, grid) { | ||||||||||||||||||||||
| // The shell is this pane's one terminal activity, and acquiring it is | ||||||||||||||||||||||
| // what makes the pane ready — the same boundary a paired pane's content | ||||||||||||||||||||||
| // crosses, rather than a second way in. | ||||||||||||||||||||||
| const outcome = yield* terminal.use(grid.shell(pane.ordinal)); | ||||||||||||||||||||||
| 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(terminal, grid) { | ||||||||||||||||||||||
| 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
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(terminal); | ||||||||||||||||||||||
| 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
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. | ||||||||||||||||||||||
|
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
|
||||||||||||||||||||||
| 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
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
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* grid.display(pane.ordinal, text); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** The label one pane displays, from the value its own `title` prop produced. */ | ||||||||||||||||||||||
|
|
@@ -2172,15 +2293,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" }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
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.