@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.
Files changed (31) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/browser/assets/index-BYvynUT_.css +32 -0
  3. package/dist/browser/assets/index-NiH4zpUV.js +9 -0
  4. package/dist/browser/index.html +2 -2
  5. package/dist/src/browser/neovim-client.d.ts +16 -1
  6. package/dist/src/browser/neovim-client.js.map +1 -1
  7. package/dist/src/client/terminal-config.d.ts +13 -0
  8. package/dist/src/client/terminal-config.js +21 -0
  9. package/dist/src/client/terminal-config.js.map +1 -0
  10. package/dist/src/client/terminal-terminal-client.d.ts +4 -2
  11. package/dist/src/client/terminal-terminal-client.js +18 -6
  12. package/dist/src/client/terminal-terminal-client.js.map +1 -1
  13. package/dist/src/client/websocket-client.d.ts +2 -2
  14. package/dist/src/client/websocket-client.js +3 -3
  15. package/dist/src/client/websocket-client.js.map +1 -1
  16. package/dist/src/server/applications/neovim/NeovimApplication.js +5 -2
  17. package/dist/src/server/applications/neovim/NeovimApplication.js.map +1 -1
  18. package/dist/src/server/cypress-support/contents.js +15 -3
  19. package/dist/src/server/cypress-support/contents.js.map +1 -1
  20. package/dist/src/server/types.d.ts +11 -0
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +7 -7
  23. package/src/browser/neovim-client.ts +19 -2
  24. package/src/client/terminal-config.ts +25 -0
  25. package/src/client/terminal-terminal-client.ts +23 -8
  26. package/src/client/websocket-client.ts +4 -4
  27. package/src/server/applications/neovim/NeovimApplication.ts +4 -3
  28. package/src/server/cypress-support/contents.ts +15 -3
  29. package/src/server/types.ts +12 -0
  30. package/dist/browser/assets/index-CO9qJUD3.js +0 -9
  31. 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.5.1",
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.1",
16
- "@trpc/server": "11.4.1",
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.5.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.1",
36
+ "@types/node": "24.0.4",
37
37
  "nodemon": "3.1.10",
38
- "vite": "6.3.5",
39
- "vitest": "3.2.3",
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: StartTerminalGenericArguments): Promise<GenericTerminalBrowserApi>
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: StartTerminalGenericArguments
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 { StartTerminalGenericArguments } from "../server/applications/terminal/TerminalTestApplication.js"
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
- const terminal = startTerminal(app, {
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 Neovim stdout - this will take some (short) amount of
51
- // time to complete
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: StartTerminalGenericArguments): Promise<TestDirectory> {
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 StartTerminalOptions = {
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, options: StartTerminalOptions): Terminal {
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
- options.onMouseEvent(mouseEvent)
65
+ api.onMouseEvent(mouseEvent)
66
66
  }
67
67
  })
68
68
 
69
69
  terminal.onKey(event => {
70
- options.onKeyPress(event)
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
- throw new Error(`Socket file ${this.state.socketPath} should have been removed by neovim when it exited.`)
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(args)
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(args: StartTerminalGenericArguments): Chainable<TerminalTestApplicationContext>
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. */
@@ -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>