@tui-sandbox/library 10.5.1 → 10.6.1
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/CHANGELOG.md +14 -0
- package/dist/browser/assets/index-BYvynUT_.css +32 -0
- package/dist/browser/assets/index-NiH4zpUV.js +9 -0
- package/dist/browser/index.html +2 -2
- package/dist/src/browser/neovim-client.d.ts +16 -1
- package/dist/src/browser/neovim-client.js.map +1 -1
- package/dist/src/client/terminal-config.d.ts +13 -0
- package/dist/src/client/terminal-config.js +21 -0
- package/dist/src/client/terminal-config.js.map +1 -0
- package/dist/src/client/terminal-terminal-client.d.ts +4 -2
- package/dist/src/client/terminal-terminal-client.js +18 -6
- package/dist/src/client/terminal-terminal-client.js.map +1 -1
- package/dist/src/client/websocket-client.d.ts +2 -2
- package/dist/src/client/websocket-client.js +3 -3
- package/dist/src/client/websocket-client.js.map +1 -1
- package/dist/src/server/applications/neovim/NeovimApplication.js +5 -2
- package/dist/src/server/applications/neovim/NeovimApplication.js.map +1 -1
- package/dist/src/server/cypress-support/contents.js +15 -3
- package/dist/src/server/cypress-support/contents.js.map +1 -1
- package/dist/src/server/types.d.ts +11 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/browser/neovim-client.ts +19 -2
- package/src/client/terminal-config.ts +25 -0
- package/src/client/terminal-terminal-client.ts +23 -8
- package/src/client/websocket-client.ts +4 -4
- package/src/server/applications/neovim/NeovimApplication.ts +4 -3
- package/src/server/cypress-support/contents.ts +15 -3
- package/src/server/types.ts +12 -0
- package/dist/browser/assets/index-CO9qJUD3.js +0 -9
- package/dist/browser/assets/index-D6fBrqAi.css +0 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tui-sandbox/library",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.6.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/mikavilpas/tui-sandbox"
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@catppuccin/palette": "1.7.1",
|
|
15
|
-
"@trpc/client": "11.4.
|
|
16
|
-
"@trpc/server": "11.4.
|
|
15
|
+
"@trpc/client": "11.4.2",
|
|
16
|
+
"@trpc/server": "11.4.2",
|
|
17
17
|
"@xterm/addon-attach": "0.11.0",
|
|
18
18
|
"@xterm/addon-fit": "0.10.0",
|
|
19
19
|
"@xterm/xterm": "5.5.0",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"express": "5.1.0",
|
|
25
25
|
"neovim": "5.3.0",
|
|
26
26
|
"node-pty": "1.0.0",
|
|
27
|
-
"prettier": "3.
|
|
27
|
+
"prettier": "3.6.1",
|
|
28
28
|
"tsx": "4.20.3",
|
|
29
29
|
"type-fest": "4.41.0",
|
|
30
30
|
"winston": "3.17.0"
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"@types/command-exists": "1.2.3",
|
|
34
34
|
"@types/cors": "2.8.19",
|
|
35
35
|
"@types/express": "5.0.3",
|
|
36
|
-
"@types/node": "24.0.
|
|
36
|
+
"@types/node": "24.0.4",
|
|
37
37
|
"nodemon": "3.1.10",
|
|
38
|
-
"vite": "
|
|
39
|
-
"vitest": "3.2.
|
|
38
|
+
"vite": "7.0.0",
|
|
39
|
+
"vitest": "3.2.4",
|
|
40
40
|
"zod": "4.0.0-beta.20250505T195954"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { Terminal } from "@xterm/xterm"
|
|
1
2
|
import { TerminalClient as NeovimTerminalClient } from "../client/index.js"
|
|
2
3
|
import { TerminalTerminalClient } from "../client/terminal-terminal-client.js"
|
|
4
|
+
import type { TuiTerminalApi } from "../client/websocket-client.js"
|
|
3
5
|
import type {
|
|
4
6
|
ExCommandClientInput,
|
|
5
7
|
LuaCodeClientInput,
|
|
@@ -64,7 +66,7 @@ window.startNeovim = async function (startArgs?: StartNeovimGenericArguments): P
|
|
|
64
66
|
declare global {
|
|
65
67
|
interface Window {
|
|
66
68
|
startNeovim(startArguments?: StartNeovimGenericArguments): Promise<GenericNeovimBrowserApi>
|
|
67
|
-
startTerminalApplication(args:
|
|
69
|
+
startTerminalApplication(args: StartTerminalBrowserArguments): Promise<GenericTerminalBrowserApi>
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -73,9 +75,24 @@ export type GenericTerminalBrowserApi = {
|
|
|
73
75
|
runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
export type BrowserTerminalSettings = {
|
|
79
|
+
configureTerminal?: (term: {
|
|
80
|
+
terminal: Terminal
|
|
81
|
+
api: TuiTerminalApi
|
|
82
|
+
recipes: {
|
|
83
|
+
supportDA1: () => void
|
|
84
|
+
}
|
|
85
|
+
}) => void
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type StartTerminalBrowserArguments = {
|
|
89
|
+
serverSettings: StartTerminalGenericArguments
|
|
90
|
+
browserSettings: BrowserTerminalSettings
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
/** Entrypoint for the test runner (cypress) */
|
|
77
94
|
window.startTerminalApplication = async function (
|
|
78
|
-
args:
|
|
95
|
+
args: StartTerminalBrowserArguments
|
|
79
96
|
): Promise<GenericTerminalBrowserApi> {
|
|
80
97
|
const terminal = terminalClient.get()
|
|
81
98
|
const testDirectory = await terminal.startTerminalApplication(args)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Terminal } from "@xterm/xterm"
|
|
2
|
+
import type { TuiTerminalApi } from "./websocket-client.js"
|
|
3
|
+
|
|
4
|
+
/** DA1—Primary Device Attributes
|
|
5
|
+
* In this DA exchange, the host asks for the terminal's architectural class and basic attributes.
|
|
6
|
+
* https://vt100.net/docs/vt510-rm/DA1.html
|
|
7
|
+
*
|
|
8
|
+
* Terminal Response
|
|
9
|
+
* The terminal responds by sending its architectural class and basic
|
|
10
|
+
* attributes to the host. This response depends on the terminal's current
|
|
11
|
+
* operating VT level.
|
|
12
|
+
*/
|
|
13
|
+
export function supportDA1(terminal: Terminal, api: TuiTerminalApi): void {
|
|
14
|
+
// Register a CSI handler for the 'c' command (ESC [ c)
|
|
15
|
+
terminal.parser.registerCsiHandler({ final: "c" }, () => {
|
|
16
|
+
// Emit a fake DA1 response: ESC [ ? 1 ; 2 c
|
|
17
|
+
api.onKeyPress({
|
|
18
|
+
key: "\x1b" + ("[?1;2c" satisfies FakeDA1Response),
|
|
19
|
+
domEvent: new KeyboardEvent("keydown", { key: "Escape" }),
|
|
20
|
+
})
|
|
21
|
+
return true // prevent default handling
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type FakeDA1Response = "[?1;2c"
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createTRPCClient, httpBatchLink, httpSubscriptionLink, splitLink } from "@trpc/client"
|
|
2
2
|
import type { Terminal } from "@xterm/xterm"
|
|
3
3
|
import "@xterm/xterm/css/xterm.css"
|
|
4
|
-
import type {
|
|
4
|
+
import type { StartTerminalBrowserArguments } from "../browser/neovim-client.js"
|
|
5
5
|
import type { BlockingCommandClientInput } from "../server/blockingCommandInputSchema.js"
|
|
6
6
|
import type { AppRouter } from "../server/server.js"
|
|
7
7
|
import type { BlockingShellCommandOutput, TestDirectory } from "../server/types.js"
|
|
8
8
|
import "./style.css"
|
|
9
|
+
import { supportDA1 } from "./terminal-config.js"
|
|
10
|
+
import type { TuiTerminalApi } from "./websocket-client.js"
|
|
9
11
|
import { getTabId, startTerminal } from "./websocket-client.js"
|
|
10
12
|
|
|
11
13
|
/** Manages the terminal state in the browser as well as the (browser's)
|
|
@@ -15,6 +17,7 @@ export class TerminalTerminalClient {
|
|
|
15
17
|
private readonly tabId: { tabId: string }
|
|
16
18
|
private readonly terminal: Terminal
|
|
17
19
|
private readonly trpc: ReturnType<typeof createTRPCClient<AppRouter>>
|
|
20
|
+
terminalApi: TuiTerminalApi
|
|
18
21
|
|
|
19
22
|
constructor(app: HTMLElement) {
|
|
20
23
|
const trpc = createTRPCClient<AppRouter>({
|
|
@@ -35,7 +38,7 @@ export class TerminalTerminalClient {
|
|
|
35
38
|
this.tabId = getTabId()
|
|
36
39
|
const tabId = this.tabId
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
this.terminalApi = {
|
|
39
42
|
onMouseEvent(data: string) {
|
|
40
43
|
void trpc.terminal.sendStdin.mutate({ tabId, data }).catch((error: unknown) => {
|
|
41
44
|
console.error(`Error sending mouse event`, error)
|
|
@@ -44,11 +47,12 @@ export class TerminalTerminalClient {
|
|
|
44
47
|
onKeyPress(event) {
|
|
45
48
|
void trpc.terminal.sendStdin.mutate({ tabId, data: event.key })
|
|
46
49
|
},
|
|
47
|
-
}
|
|
50
|
+
}
|
|
51
|
+
const terminal = startTerminal(app, this.terminalApi)
|
|
48
52
|
this.terminal = terminal
|
|
49
53
|
|
|
50
|
-
// start listening to
|
|
51
|
-
//
|
|
54
|
+
// start listening to stdout - this will take some (short) amount of time
|
|
55
|
+
// to complete
|
|
52
56
|
this.ready = new Promise<void>(resolve => {
|
|
53
57
|
console.log("Subscribing to stdout")
|
|
54
58
|
trpc.terminal.onStdout.subscribe(
|
|
@@ -68,13 +72,24 @@ export class TerminalTerminalClient {
|
|
|
68
72
|
})
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
public async startTerminalApplication(args:
|
|
75
|
+
public async startTerminalApplication(args: StartTerminalBrowserArguments): Promise<TestDirectory> {
|
|
72
76
|
await this.ready
|
|
77
|
+
|
|
78
|
+
args.browserSettings.configureTerminal?.({
|
|
79
|
+
terminal: this.terminal,
|
|
80
|
+
api: this.terminalApi,
|
|
81
|
+
recipes: {
|
|
82
|
+
supportDA1: () => {
|
|
83
|
+
supportDA1(this.terminal, this.terminalApi)
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
73
88
|
const testDirectory = await this.trpc.terminal.start.mutate({
|
|
74
89
|
tabId: this.tabId,
|
|
75
90
|
startTerminalArguments: {
|
|
76
|
-
additionalEnvironmentVariables: args.additionalEnvironmentVariables,
|
|
77
|
-
commandToRun: args.commandToRun,
|
|
91
|
+
additionalEnvironmentVariables: args.serverSettings.additionalEnvironmentVariables,
|
|
92
|
+
commandToRun: args.serverSettings.commandToRun,
|
|
78
93
|
terminalDimensions: {
|
|
79
94
|
cols: this.terminal.cols,
|
|
80
95
|
rows: this.terminal.rows,
|
|
@@ -7,11 +7,11 @@ import type { TabId } from "../server/utilities/tabId.ts"
|
|
|
7
7
|
import "./style.css"
|
|
8
8
|
import { validateMouseEvent } from "./validateMouseEvent.js"
|
|
9
9
|
|
|
10
|
-
export type
|
|
10
|
+
export type TuiTerminalApi = {
|
|
11
11
|
onMouseEvent: (data: string) => void
|
|
12
12
|
onKeyPress: (event: { key: string; domEvent: KeyboardEvent }) => void
|
|
13
13
|
}
|
|
14
|
-
export function startTerminal(app: HTMLElement,
|
|
14
|
+
export function startTerminal(app: HTMLElement, api: TuiTerminalApi): Terminal {
|
|
15
15
|
const terminal = new Terminal({
|
|
16
16
|
cursorBlink: false,
|
|
17
17
|
convertEol: true,
|
|
@@ -62,12 +62,12 @@ export function startTerminal(app: HTMLElement, options: StartTerminalOptions):
|
|
|
62
62
|
|
|
63
63
|
const mouseEvent = validateMouseEvent(data)
|
|
64
64
|
if (mouseEvent) {
|
|
65
|
-
|
|
65
|
+
api.onMouseEvent(mouseEvent)
|
|
66
66
|
}
|
|
67
67
|
})
|
|
68
68
|
|
|
69
69
|
terminal.onKey(event => {
|
|
70
|
-
|
|
70
|
+
api.onKeyPress(event)
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
return terminal
|
|
@@ -202,11 +202,12 @@ export class NeovimApplication implements AsyncDisposable {
|
|
|
202
202
|
|
|
203
203
|
try {
|
|
204
204
|
await access(this.state.socketPath)
|
|
205
|
-
|
|
205
|
+
console.warn(`Socket file ${this.state.socketPath} should have been removed by neovim when it exited.`)
|
|
206
|
+
return
|
|
206
207
|
} catch (e) {
|
|
207
208
|
// all good
|
|
209
|
+
} finally {
|
|
210
|
+
this.state = undefined
|
|
208
211
|
}
|
|
209
|
-
|
|
210
|
-
this.state = undefined
|
|
211
212
|
}
|
|
212
213
|
}
|
|
@@ -11,10 +11,12 @@ export async function createCypressSupportFileContents(): Promise<string> {
|
|
|
11
11
|
// This file is autogenerated by tui-sandbox. Do not edit it directly.
|
|
12
12
|
//
|
|
13
13
|
import type {
|
|
14
|
+
BrowserTerminalSettings,
|
|
14
15
|
GenericNeovimBrowserApi,
|
|
15
16
|
GenericTerminalBrowserApi,
|
|
16
17
|
} from "@tui-sandbox/library/dist/src/browser/neovim-client"
|
|
17
18
|
import type {
|
|
19
|
+
AllKeys,
|
|
18
20
|
BlockingShellCommandOutput,
|
|
19
21
|
RunExCommandOutput,
|
|
20
22
|
RunLuaCodeOutput,
|
|
@@ -131,9 +133,17 @@ Cypress.Commands.add("nvim_isRunning", () => {
|
|
|
131
133
|
})
|
|
132
134
|
})
|
|
133
135
|
|
|
134
|
-
Cypress.Commands.add("startTerminalApplication", (args: StartTerminalGenericArguments) => {
|
|
136
|
+
Cypress.Commands.add("startTerminalApplication", (args: StartTerminalGenericArguments & BrowserTerminalSettings) => {
|
|
135
137
|
cy.window().then(async win => {
|
|
136
|
-
const terminal: GenericTerminalBrowserApi = await win.startTerminalApplication(
|
|
138
|
+
const terminal: GenericTerminalBrowserApi = await win.startTerminalApplication({
|
|
139
|
+
serverSettings: {
|
|
140
|
+
commandToRun: args.commandToRun,
|
|
141
|
+
additionalEnvironmentVariables: args.additionalEnvironmentVariables,
|
|
142
|
+
} satisfies AllKeys<StartTerminalGenericArguments>,
|
|
143
|
+
browserSettings: {
|
|
144
|
+
configureTerminal: args.configureTerminal,
|
|
145
|
+
} satisfies AllKeys<BrowserTerminalSettings>,
|
|
146
|
+
})
|
|
137
147
|
|
|
138
148
|
Cypress.Commands.addAll({
|
|
139
149
|
terminal_runBlockingShellCommand: terminal.runBlockingShellCommand,
|
|
@@ -177,7 +187,9 @@ declare global {
|
|
|
177
187
|
namespace Cypress {
|
|
178
188
|
interface Chainable {
|
|
179
189
|
startNeovim(args?: MyStartNeovimServerArguments): Chainable<NeovimContext>
|
|
180
|
-
startTerminalApplication(
|
|
190
|
+
startTerminalApplication(
|
|
191
|
+
args: StartTerminalGenericArguments & BrowserTerminalSettings
|
|
192
|
+
): Chainable<TerminalTestApplicationContext>
|
|
181
193
|
|
|
182
194
|
/** Types text into the terminal, making the terminal application receive
|
|
183
195
|
* the keystrokes as input. Requires neovim to be running. */
|
package/src/server/types.ts
CHANGED
|
@@ -64,3 +64,15 @@ export type RunLuaCodeOutput = {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export type RunExCommandOutput = { value?: string }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Require all of an object's keys to be set explicitly. This is useful for
|
|
70
|
+
* readability: writing all the keys makes their presence explicit. They could
|
|
71
|
+
* also be added via a spread operator (...obj), but in that case the following
|
|
72
|
+
* cases are unclear:
|
|
73
|
+
*
|
|
74
|
+
* - extra keys might be included because TypeScript does not check for them
|
|
75
|
+
* - if the target object type has optional keys, they might be missing. In
|
|
76
|
+
* this case, they will never get set
|
|
77
|
+
**/
|
|
78
|
+
export type AllKeys<T extends Record<never, never>> = Record<keyof T, unknown>
|