@tui-sandbox/library 11.11.2 → 12.0.0
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/dist/browser/assets/index-CYUPHpRk.css +1 -0
- package/dist/browser/assets/index-DPQpUaDL.js +9 -0
- package/dist/browser/index.html +2 -2
- package/dist/src/browser/neovim-client.d.ts +2 -0
- package/dist/src/browser/neovim-client.js +2 -0
- package/dist/src/browser/neovim-client.js.map +1 -1
- package/dist/src/client/index.d.ts +1 -0
- package/dist/src/client/neovim-terminal-client.d.ts +0 -2
- package/dist/src/client/neovim-terminal-client.js +0 -2
- package/dist/src/client/neovim-terminal-client.js.map +1 -1
- package/dist/src/client/startTerminal.d.ts +0 -2
- package/dist/src/client/startTerminal.js +0 -2
- package/dist/src/client/startTerminal.js.map +1 -1
- package/dist/src/client/terminal-terminal-client.d.ts +0 -1
- package/dist/src/client/terminal-terminal-client.js +0 -1
- package/dist/src/client/terminal-terminal-client.js.map +1 -1
- package/dist/src/server/cypress-support/contents.js +9 -11
- package/dist/src/server/cypress-support/contents.js.map +1 -1
- package/dist/src/server/index.d.ts +4 -0
- package/dist/src/server/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +34 -8
- package/CHANGELOG.md +0 -941
- package/dist/browser/assets/index-BQzArJW3.js +0 -9
- package/dist/browser/assets/index-hkmOP7rU.css +0 -1
- package/index.html +0 -11
- package/src/browser/neovim-client.ts +0 -126
- package/src/client/MyNeovimConfigModification.test.ts +0 -25
- package/src/client/MyNeovimConfigModification.ts +0 -8
- package/src/client/color-utilities.test.ts +0 -10
- package/src/client/color-utilities.ts +0 -9
- package/src/client/cypress-assertions.ts +0 -35
- package/src/client/index.ts +0 -4
- package/src/client/neovim-terminal-client.ts +0 -130
- package/src/client/public/DejaVuSansMNerdFontMono-Regular.ttf +0 -0
- package/src/client/startTerminal.ts +0 -97
- package/src/client/style.css +0 -32
- package/src/client/terminal-config.ts +0 -25
- package/src/client/terminal-terminal-client.ts +0 -106
- package/src/client/validateMouseEvent.ts +0 -22
- package/src/scripts/commands/commandRun.ts +0 -68
- package/src/scripts/commands/commandTuiNeovimExec.ts +0 -36
- package/src/scripts/commands/commandTuiNeovimPrepare.ts +0 -12
- package/src/scripts/commands/commandTuiStart.ts +0 -36
- package/src/scripts/parseArguments.test.ts +0 -28
- package/src/scripts/parseArguments.ts +0 -66
- package/src/scripts/resolveConfig.test.ts +0 -78
- package/src/scripts/resolveTuiConfig.ts +0 -65
- package/src/scripts/tui.ts +0 -77
- package/src/server/TestServer.ts +0 -73
- package/src/server/applications/neovim/NeovimApplication.ts +0 -236
- package/src/server/applications/neovim/NeovimJavascriptApiClient.test.ts +0 -48
- package/src/server/applications/neovim/NeovimJavascriptApiClient.ts +0 -68
- package/src/server/applications/neovim/api.ts +0 -257
- package/src/server/applications/neovim/environment/TempDirectory.ts +0 -18
- package/src/server/applications/neovim/environment/createTempDir.test.ts +0 -29
- package/src/server/applications/neovim/environment/createTempDir.ts +0 -91
- package/src/server/applications/neovim/neovimRouter.ts +0 -100
- package/src/server/applications/neovim/prepareNewTestDirectory.test.ts +0 -32
- package/src/server/applications/neovim/prepareNewTestDirectory.ts +0 -21
- package/src/server/applications/terminal/TerminalTestApplication.ts +0 -101
- package/src/server/applications/terminal/api.ts +0 -89
- package/src/server/applications/terminal/runBlockingShellCommand.test.ts +0 -41
- package/src/server/applications/terminal/runBlockingShellCommand.ts +0 -64
- package/src/server/applications/terminal/terminalRouter.ts +0 -47
- package/src/server/blockingCommandInputSchema.test.ts +0 -24
- package/src/server/blockingCommandInputSchema.ts +0 -27
- package/src/server/config.test.ts +0 -7
- package/src/server/config.ts +0 -18
- package/src/server/connection/trpc.ts +0 -3
- package/src/server/cypress-support/contents.ts +0 -245
- package/src/server/cypress-support/createCypressSupportFile.test.ts +0 -69
- package/src/server/cypress-support/createCypressSupportFile.ts +0 -56
- package/src/server/dirtree/index.test.ts +0 -352
- package/src/server/dirtree/index.ts +0 -144
- package/src/server/dirtree/json-to-zod.ts +0 -60
- package/src/server/index.ts +0 -6
- package/src/server/server.ts +0 -34
- package/src/server/types.ts +0 -98
- package/src/server/updateTestdirectorySchemaFile.test.ts +0 -49
- package/src/server/updateTestdirectorySchemaFile.ts +0 -71
- package/src/server/utilities/DisposableSingleApplication.test.ts +0 -92
- package/src/server/utilities/DisposableSingleApplication.ts +0 -49
- package/src/server/utilities/Lazy.ts +0 -16
- package/src/server/utilities/TerminalApplication.ts +0 -100
- package/src/server/utilities/generator.test.ts +0 -50
- package/src/server/utilities/generator.ts +0 -12
- package/src/server/utilities/tabId.ts +0 -4
- package/src/server/utilities/timeout.ts +0 -3
- package/src/server/utilities/timeoutable.ts +0 -19
- package/tsconfig.json +0 -31
- package/vite.config.js +0 -27
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import assert from "assert"
|
|
2
|
-
import { exec } from "child_process"
|
|
3
|
-
import EventEmitter from "events"
|
|
4
|
-
import { access } from "fs/promises"
|
|
5
|
-
import type { NeovimClient as NeovimApiClient } from "neovim"
|
|
6
|
-
import { tmpdir } from "os"
|
|
7
|
-
import path, { join } from "path"
|
|
8
|
-
import { debuglog } from "util"
|
|
9
|
-
import type { TestDirectory, TestEnvironmentCommonEnvironmentVariables } from "../../types.js"
|
|
10
|
-
import { DisposableSingleApplication } from "../../utilities/DisposableSingleApplication.js"
|
|
11
|
-
import type { Lazy } from "../../utilities/Lazy.js"
|
|
12
|
-
import { TerminalApplication } from "../../utilities/TerminalApplication.js"
|
|
13
|
-
import { connectNeovimApi } from "./NeovimJavascriptApiClient.js"
|
|
14
|
-
|
|
15
|
-
const log = debuglog("tui-sandbox-neovim-application")
|
|
16
|
-
|
|
17
|
-
/*
|
|
18
|
-
|
|
19
|
-
Usage:
|
|
20
|
-
nvim [options] [file ...]
|
|
21
|
-
|
|
22
|
-
Options:
|
|
23
|
-
--cmd <cmd> Execute <cmd> before any config
|
|
24
|
-
+<cmd>, -c <cmd> Execute <cmd> after config and first file
|
|
25
|
-
-l <script> [args...] Execute Lua <script> (with optional args)
|
|
26
|
-
-S <session> Source <session> after loading the first file
|
|
27
|
-
-s <scriptin> Read Normal mode commands from <scriptin>
|
|
28
|
-
-u <config> Use this config file
|
|
29
|
-
|
|
30
|
-
-d Diff mode
|
|
31
|
-
-es, -Es Silent (batch) mode
|
|
32
|
-
-h, --help Print this help message
|
|
33
|
-
-i <shada> Use this shada file
|
|
34
|
-
-n No swap file, use memory only
|
|
35
|
-
-o[N] Open N windows (default: one per file)
|
|
36
|
-
-O[N] Open N vertical windows (default: one per file)
|
|
37
|
-
-p[N] Open N tab pages (default: one per file)
|
|
38
|
-
-R Read-only (view) mode
|
|
39
|
-
-v, --version Print version information
|
|
40
|
-
-V[N][file] Verbose [level][file]
|
|
41
|
-
|
|
42
|
-
-- Only file names after this
|
|
43
|
-
--api-info Write msgpack-encoded API metadata to stdout
|
|
44
|
-
--clean "Factory defaults" (skip user config and plugins, shada)
|
|
45
|
-
--embed Use stdin/stdout as a msgpack-rpc channel
|
|
46
|
-
--headless Don't start a user interface
|
|
47
|
-
--listen <address> Serve RPC API from this address
|
|
48
|
-
--remote[-subcommand] Execute commands remotely on a server
|
|
49
|
-
--server <address> Connect to this Nvim server
|
|
50
|
-
--startuptime <file> Write startup timing messages to <file>
|
|
51
|
-
|
|
52
|
-
See ":help startup-options" for all options.
|
|
53
|
-
|
|
54
|
-
NVIM v0.11.2
|
|
55
|
-
Build type: Release
|
|
56
|
-
LuaJIT 2.1.1741730670
|
|
57
|
-
Run "nvim -V1 -v" for more info
|
|
58
|
-
|
|
59
|
-
*/
|
|
60
|
-
|
|
61
|
-
export type StdoutOrStderrMessage = "stdout"
|
|
62
|
-
|
|
63
|
-
export type StartNeovimGenericArguments = {
|
|
64
|
-
filename: string | { openInVerticalSplits: string[] }
|
|
65
|
-
startupScriptModifications?: string[]
|
|
66
|
-
|
|
67
|
-
/** Executes the given command with --headless -c <command> -c qa */
|
|
68
|
-
headlessCmd?: string
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* This variable controls the sub-directory that Nvim will read from (and
|
|
72
|
-
* auto-create) in each of the base directories.
|
|
73
|
-
*
|
|
74
|
-
* For example, setting $NVIM_APPNAME to "foo" before starting will cause
|
|
75
|
-
* Nvim to look for configuration files in $XDG_CONFIG_HOME/foo instead of
|
|
76
|
-
* $XDG_CONFIG_HOME/nvim. $NVIM_APPNAME must be a name, such as "foo", or a
|
|
77
|
-
* relative path, such as "foo/bar".
|
|
78
|
-
*
|
|
79
|
-
* https://neovim.io/doc/user/starting.html#_nvim_appname
|
|
80
|
-
*/
|
|
81
|
-
NVIM_APPNAME?: string
|
|
82
|
-
|
|
83
|
-
/** Additions to the environment variables for the Neovim process. These
|
|
84
|
-
* override any already existing environment variables. */
|
|
85
|
-
additionalEnvironmentVariables?: Record<string, string> | undefined
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type TerminalDimensions = { cols: number; rows: number }
|
|
89
|
-
|
|
90
|
-
type ResettableState = {
|
|
91
|
-
testDirectory: TestDirectory
|
|
92
|
-
NVIM_APPNAME: string | undefined
|
|
93
|
-
socketPath: string
|
|
94
|
-
client: Lazy<Promise<NeovimApiClient>>
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export class NeovimApplication implements AsyncDisposable {
|
|
98
|
-
public state: ResettableState | undefined
|
|
99
|
-
public readonly events: EventEmitter
|
|
100
|
-
|
|
101
|
-
public constructor(
|
|
102
|
-
private readonly testEnvironmentPath: string,
|
|
103
|
-
public readonly application: DisposableSingleApplication = new DisposableSingleApplication()
|
|
104
|
-
) {
|
|
105
|
-
this.events = new EventEmitter()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Kill the current application and start a new one with the given arguments.
|
|
110
|
-
*/
|
|
111
|
-
public async startNextAndKillCurrent(
|
|
112
|
-
testDirectory: TestDirectory,
|
|
113
|
-
startArgs: StartNeovimGenericArguments,
|
|
114
|
-
terminalDimensions: TerminalDimensions
|
|
115
|
-
): Promise<void> {
|
|
116
|
-
await this[Symbol.asyncDispose]()
|
|
117
|
-
assert(
|
|
118
|
-
this.state === undefined,
|
|
119
|
-
"NeovimApplication state should be undefined after disposing so that no previous state is reused."
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
const neovimArguments: string[] = []
|
|
123
|
-
|
|
124
|
-
if (startArgs.startupScriptModifications) {
|
|
125
|
-
for (const modification of startArgs.startupScriptModifications) {
|
|
126
|
-
let file = path.join(testDirectory.rootPathAbsolute, "config-modifications", modification)
|
|
127
|
-
try {
|
|
128
|
-
await access(file)
|
|
129
|
-
} catch (e) {
|
|
130
|
-
throw new Error(`startupScriptModifications file does not exist: ${file}. Error: ${String(e)}`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
file = file.replaceAll("'", "\\'")
|
|
134
|
-
neovimArguments.push("-c", `lua dofile('${file}')`)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (typeof startArgs.filename === "string") {
|
|
139
|
-
const file = path.join(testDirectory.rootPathAbsolute, startArgs.filename)
|
|
140
|
-
neovimArguments.push(file)
|
|
141
|
-
} else if (startArgs.filename.openInVerticalSplits.length > 0) {
|
|
142
|
-
// `-O[N]` Open N vertical windows (default: one per file)
|
|
143
|
-
neovimArguments.push("-O")
|
|
144
|
-
|
|
145
|
-
for (const file of startArgs.filename.openInVerticalSplits) {
|
|
146
|
-
const filePath = path.join(testDirectory.rootPathAbsolute, file)
|
|
147
|
-
neovimArguments.push(filePath)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (startArgs.headlessCmd) {
|
|
152
|
-
// NOTE: update the doc comment above if this changes
|
|
153
|
-
neovimArguments.push("--headless")
|
|
154
|
-
neovimArguments.push("-c", startArgs.headlessCmd)
|
|
155
|
-
neovimArguments.push("-c", "qa")
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const id = Math.random().toString().slice(2, 8)
|
|
159
|
-
const socketPath = `${tmpdir()}/tui-sandbox-nvim-socket-${id}`
|
|
160
|
-
neovimArguments.push("--listen", socketPath)
|
|
161
|
-
|
|
162
|
-
const stdout = this.events
|
|
163
|
-
|
|
164
|
-
await this.application.startNextAndKillCurrent(async () => {
|
|
165
|
-
const env = NeovimApplication.getEnvironmentVariables(
|
|
166
|
-
testDirectory,
|
|
167
|
-
startArgs.NVIM_APPNAME,
|
|
168
|
-
startArgs.additionalEnvironmentVariables
|
|
169
|
-
)
|
|
170
|
-
return TerminalApplication.start({
|
|
171
|
-
command: "nvim",
|
|
172
|
-
args: neovimArguments,
|
|
173
|
-
|
|
174
|
-
cwd: this.testEnvironmentPath,
|
|
175
|
-
env,
|
|
176
|
-
dimensions: terminalDimensions,
|
|
177
|
-
|
|
178
|
-
onStdoutOrStderr(data) {
|
|
179
|
-
data satisfies string
|
|
180
|
-
stdout.emit("stdout" satisfies StdoutOrStderrMessage, data)
|
|
181
|
-
},
|
|
182
|
-
})
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
const processId = this.application.processId()
|
|
186
|
-
assert(processId !== undefined, "Neovim was started without a process ID. This is a bug - please open an issue.")
|
|
187
|
-
|
|
188
|
-
this.state = {
|
|
189
|
-
testDirectory,
|
|
190
|
-
socketPath,
|
|
191
|
-
NVIM_APPNAME: startArgs.NVIM_APPNAME,
|
|
192
|
-
client: connectNeovimApi(socketPath),
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
log(`🚀 Started Neovim instance ${processId} (NVIM_APPNAME: ${startArgs.NVIM_APPNAME})`)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
public static getEnvironmentVariables(
|
|
199
|
-
testDirectory: TestDirectory,
|
|
200
|
-
NVIM_APPNAME: string | undefined,
|
|
201
|
-
additionalEnvironmentVariables?: Record<string, string>
|
|
202
|
-
): NodeJS.ProcessEnv {
|
|
203
|
-
return {
|
|
204
|
-
...process.env,
|
|
205
|
-
...({
|
|
206
|
-
HOME: testDirectory.rootPathAbsolute,
|
|
207
|
-
XDG_CONFIG_HOME: join(testDirectory.rootPathAbsolute, ".config"),
|
|
208
|
-
XDG_DATA_HOME: join(testDirectory.testEnvironmentPath, ".repro", "data"),
|
|
209
|
-
TUI_SANDBOX_TEST_ENVIRONMENT_PATH: testDirectory.testEnvironmentPath,
|
|
210
|
-
} satisfies TestEnvironmentCommonEnvironmentVariables),
|
|
211
|
-
NVIM_APPNAME: NVIM_APPNAME ?? "nvim",
|
|
212
|
-
|
|
213
|
-
...additionalEnvironmentVariables,
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async [Symbol.asyncDispose](): Promise<void> {
|
|
218
|
-
await this.application[Symbol.asyncDispose]()
|
|
219
|
-
|
|
220
|
-
if (!this.state) return
|
|
221
|
-
|
|
222
|
-
exec(`rm -rf ${this.state.testDirectory.rootPathAbsolute}`)
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
await access(this.state.socketPath)
|
|
226
|
-
// this is probably not dangerous, but I'm not sure why it sometimes
|
|
227
|
-
// happens. It's better to report it than to hide it.
|
|
228
|
-
log(`Socket file ${this.state.socketPath} should have been removed by neovim when it exited.`)
|
|
229
|
-
return
|
|
230
|
-
} catch {
|
|
231
|
-
// all good
|
|
232
|
-
} finally {
|
|
233
|
-
this.state = undefined
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { access } from "fs/promises"
|
|
2
|
-
import { attach } from "neovim"
|
|
3
|
-
import { afterEach, beforeEach, expect, it, vi } from "vitest"
|
|
4
|
-
import type { PollingInterval } from "./NeovimJavascriptApiClient.js"
|
|
5
|
-
import { connectNeovimApi } from "./NeovimJavascriptApiClient.js"
|
|
6
|
-
|
|
7
|
-
vi.mock("neovim")
|
|
8
|
-
vi.mock("fs/promises")
|
|
9
|
-
|
|
10
|
-
const mocked = {
|
|
11
|
-
attach: vi.mocked(attach),
|
|
12
|
-
access: vi.mocked(access),
|
|
13
|
-
log: vi.spyOn(console, "log").mockImplementation(() => {
|
|
14
|
-
//
|
|
15
|
-
}),
|
|
16
|
-
}
|
|
17
|
-
const pollingInterval = 100 satisfies PollingInterval
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
vi.useFakeTimers()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
vi.useRealTimers()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it("is lazy - does not connect right away", async () => {
|
|
28
|
-
mocked.access.mockRejectedValue(new Error("no such file or directory"))
|
|
29
|
-
connectNeovimApi("foosocket")
|
|
30
|
-
|
|
31
|
-
vi.advanceTimersByTime(pollingInterval)
|
|
32
|
-
expect(mocked.attach).not.toHaveBeenCalled()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it("connects right away if the socket file is already there", async () => {
|
|
36
|
-
mocked.access.mockResolvedValue(undefined)
|
|
37
|
-
const lazyClient = connectNeovimApi("foosocket")
|
|
38
|
-
await lazyClient.get()
|
|
39
|
-
|
|
40
|
-
vi.advanceTimersByTime(pollingInterval)
|
|
41
|
-
expect(mocked.attach).toHaveBeenCalledWith({
|
|
42
|
-
socket: "foosocket",
|
|
43
|
-
options: {
|
|
44
|
-
logger: expect.any(Object) as never,
|
|
45
|
-
},
|
|
46
|
-
} satisfies Partial<Parameters<typeof attach>[0]>)
|
|
47
|
-
expect(mocked.attach).toHaveBeenCalledTimes(1)
|
|
48
|
-
})
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { access } from "fs/promises"
|
|
2
|
-
import type { NeovimClient as NeovimApiClient } from "neovim"
|
|
3
|
-
import { attach } from "neovim"
|
|
4
|
-
import { createLogger, format, transports } from "winston"
|
|
5
|
-
import { Lazy } from "../../utilities/Lazy.js"
|
|
6
|
-
|
|
7
|
-
// const log = debuglog("tui-sandbox.neovim.NeovimJavascriptApiClient")
|
|
8
|
-
|
|
9
|
-
export type NeovimJavascriptApiClient = NeovimApiClient
|
|
10
|
-
|
|
11
|
-
export type PollingInterval = 100
|
|
12
|
-
|
|
13
|
-
export function connectNeovimApi(socketPath: string): Lazy<Promise<NeovimJavascriptApiClient>> {
|
|
14
|
-
// When a custom logger is provided to attach(), we can hide debug level log
|
|
15
|
-
// messages
|
|
16
|
-
//
|
|
17
|
-
// https://github.com/neovim/node-client/blob/e0568e32e0fc8837ad900146bfd5ca27b9416235/README.md#logging
|
|
18
|
-
const logger = createLogger({
|
|
19
|
-
level: "warn",
|
|
20
|
-
|
|
21
|
-
transports: [
|
|
22
|
-
new transports.Console({
|
|
23
|
-
format: format.combine(
|
|
24
|
-
format(info => {
|
|
25
|
-
if ((info.message as string).startsWith("failed request to")) {
|
|
26
|
-
// This is logged when neovim is started with --embed, which we don't use.
|
|
27
|
-
// It's not a problem, so hide it.
|
|
28
|
-
//
|
|
29
|
-
// Returning false removes this log entry
|
|
30
|
-
return false
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return info
|
|
34
|
-
})(),
|
|
35
|
-
format.colorize(),
|
|
36
|
-
format.simple()
|
|
37
|
-
),
|
|
38
|
-
}),
|
|
39
|
-
],
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
// it takes about 100ms for the socket file to be created - best make this
|
|
43
|
-
// Lazy so that we don't wait for it unnecessarily.
|
|
44
|
-
return new Lazy(async () => {
|
|
45
|
-
for (let i = 0; i < 100; i++) {
|
|
46
|
-
try {
|
|
47
|
-
await access(socketPath)
|
|
48
|
-
// log(`socket file ${socketPath} created after at attempt ${i + 1}`)
|
|
49
|
-
break
|
|
50
|
-
} catch {
|
|
51
|
-
// log(`polling for socket file ${socketPath} to be created (attempt ${i + 1})`)
|
|
52
|
-
await new Promise(resolve => setTimeout(resolve, 100 satisfies PollingInterval))
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Prevent neovim node client from monkey patching console.log. It runs in
|
|
57
|
-
// a separate process entirely, so there doesn't seem to be any benefit to
|
|
58
|
-
// this.
|
|
59
|
-
process.env["ALLOW_CONSOLE"] = "1"
|
|
60
|
-
|
|
61
|
-
return attach({
|
|
62
|
-
socket: socketPath,
|
|
63
|
-
options: {
|
|
64
|
-
logger,
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import assert from "assert"
|
|
2
|
-
import { access } from "fs/promises"
|
|
3
|
-
import path from "path"
|
|
4
|
-
import { debuglog } from "util"
|
|
5
|
-
import type { BlockingCommandInput } from "../../blockingCommandInputSchema.js"
|
|
6
|
-
import type {
|
|
7
|
-
BlockingShellCommandOutput,
|
|
8
|
-
RunExCommandOutput,
|
|
9
|
-
RunLuaCodeOutput,
|
|
10
|
-
StartNeovimGenericArguments,
|
|
11
|
-
TestDirectory,
|
|
12
|
-
} from "../../types.js"
|
|
13
|
-
import type { TestServerConfig } from "../../updateTestdirectorySchemaFile.js"
|
|
14
|
-
import { convertEventEmitterToAsyncGenerator } from "../../utilities/generator.js"
|
|
15
|
-
import { Lazy } from "../../utilities/Lazy.js"
|
|
16
|
-
import type { TabId } from "../../utilities/tabId.js"
|
|
17
|
-
import { timeout } from "../../utilities/timeout.js"
|
|
18
|
-
import { executeBlockingShellCommand } from "../terminal/runBlockingShellCommand.js"
|
|
19
|
-
import type { StdoutOrStderrMessage, TerminalDimensions } from "./NeovimApplication.js"
|
|
20
|
-
import { NeovimApplication } from "./NeovimApplication.js"
|
|
21
|
-
import type { ExCommandInput, LuaCodeInput } from "./neovimRouter.js"
|
|
22
|
-
import { prepareNewTestDirectory } from "./prepareNewTestDirectory.js"
|
|
23
|
-
|
|
24
|
-
const neovims = new Map<TabId["tabId"], NeovimApplication>()
|
|
25
|
-
const resources: Lazy<AsyncDisposableStack> = new Lazy(() => {
|
|
26
|
-
return new AsyncDisposableStack()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const log = debuglog("tui-sandbox.neovim.api")
|
|
30
|
-
|
|
31
|
-
export async function installDependencies(NVIM_APPNAME: string | undefined, config: TestServerConfig): Promise<void> {
|
|
32
|
-
await using app = new NeovimApplication(config.directories.testEnvironmentPath)
|
|
33
|
-
const testDirectory = await prepareNewTestDirectory(config)
|
|
34
|
-
const prepareFilePath = path.join(testDirectory.rootPathAbsolute, ".config", NVIM_APPNAME ?? "nvim", "prepare.lua")
|
|
35
|
-
try {
|
|
36
|
-
await access(prepareFilePath)
|
|
37
|
-
} catch {
|
|
38
|
-
// show the output here because it's typically shown in the console before
|
|
39
|
-
// the tests start. It's also sensitive to outside changes.
|
|
40
|
-
//
|
|
41
|
-
// eslint-disable-next-line no-restricted-properties
|
|
42
|
-
console.log(
|
|
43
|
-
`Neovim prepareFilePath does not exist: ${prepareFilePath}. If you want to run a prepare script before starting the tests, create it.`
|
|
44
|
-
)
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// eslint-disable-next-line no-restricted-properties
|
|
49
|
-
console.log(`🚀 Running Neovim prepareFilePath ${prepareFilePath}...`)
|
|
50
|
-
|
|
51
|
-
let output = ""
|
|
52
|
-
app.events.on("stdout" satisfies StdoutOrStderrMessage, data => {
|
|
53
|
-
assert(data)
|
|
54
|
-
assert(typeof data === "string")
|
|
55
|
-
output += data
|
|
56
|
-
})
|
|
57
|
-
await app.startNextAndKillCurrent(
|
|
58
|
-
testDirectory,
|
|
59
|
-
{
|
|
60
|
-
filename: "empty.txt",
|
|
61
|
-
headlessCmd: `lua dofile("${prepareFilePath}")`,
|
|
62
|
-
NVIM_APPNAME,
|
|
63
|
-
},
|
|
64
|
-
{ cols: 80, rows: 24 }
|
|
65
|
-
)
|
|
66
|
-
await app.application.untilExit()
|
|
67
|
-
// eslint-disable-next-line no-restricted-properties
|
|
68
|
-
console.log(`🚀 Neovim installDependencies output:`)
|
|
69
|
-
// eslint-disable-next-line no-restricted-properties
|
|
70
|
-
console.log(output)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function initializeStdout(
|
|
74
|
-
options: { client: TabId },
|
|
75
|
-
signal: AbortSignal | undefined,
|
|
76
|
-
testEnvironmentPath: string
|
|
77
|
-
): Promise<AsyncGenerator<string, void, unknown>> {
|
|
78
|
-
const tabId = options.client.tabId
|
|
79
|
-
const neovim = neovims.get(tabId) ?? new NeovimApplication(testEnvironmentPath)
|
|
80
|
-
if (neovims.get(tabId) === undefined) {
|
|
81
|
-
neovims.set(tabId, neovim)
|
|
82
|
-
resources.get().use(neovim)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const stdout = convertEventEmitterToAsyncGenerator(neovim.events, "stdout" satisfies StdoutOrStderrMessage)
|
|
86
|
-
if (signal) {
|
|
87
|
-
signal.addEventListener("abort", () => {
|
|
88
|
-
void neovim[Symbol.asyncDispose]().finally(() => {
|
|
89
|
-
neovims.delete(tabId)
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return stdout
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export async function start(
|
|
98
|
-
options: StartNeovimGenericArguments,
|
|
99
|
-
terminalDimensions: TerminalDimensions,
|
|
100
|
-
tabId: TabId,
|
|
101
|
-
config: TestServerConfig
|
|
102
|
-
): Promise<TestDirectory> {
|
|
103
|
-
const neovim = neovims.get(tabId.tabId)
|
|
104
|
-
assert(neovim, `Neovim instance not found for client id ${tabId.tabId}`)
|
|
105
|
-
|
|
106
|
-
const testDirectory = await prepareNewTestDirectory(config)
|
|
107
|
-
await neovim.startNextAndKillCurrent(testDirectory, options, terminalDimensions)
|
|
108
|
-
|
|
109
|
-
return testDirectory
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export async function sendStdin(options: { tabId: TabId; data: string }): Promise<void> {
|
|
113
|
-
const neovim = neovims.get(options.tabId.tabId)
|
|
114
|
-
assert(
|
|
115
|
-
neovim !== undefined,
|
|
116
|
-
`Neovim instance for clientId not found - cannot send stdin. Maybe it's not started yet?`
|
|
117
|
-
)
|
|
118
|
-
assert(
|
|
119
|
-
neovim.application,
|
|
120
|
-
`Neovim application not found for client id ${options.tabId.tabId}. Maybe it's not started yet?`
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
await neovim.application.write(options.data)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export async function runBlockingShellCommand(
|
|
127
|
-
signal: AbortSignal | undefined,
|
|
128
|
-
input: BlockingCommandInput,
|
|
129
|
-
allowFailure: boolean
|
|
130
|
-
): Promise<BlockingShellCommandOutput> {
|
|
131
|
-
const neovim = neovims.get(input.tabId.tabId)
|
|
132
|
-
assert(
|
|
133
|
-
neovim !== undefined,
|
|
134
|
-
`Neovim instance for clientId not found - cannot run blocking shell command. Maybe neovim's not started yet?`
|
|
135
|
-
)
|
|
136
|
-
const testDirectory = neovim.state?.testDirectory
|
|
137
|
-
assert(testDirectory, `Test directory not found for client id ${input.tabId.tabId}. Maybe neovim's not started yet?`)
|
|
138
|
-
|
|
139
|
-
const env = NeovimApplication.getEnvironmentVariables(testDirectory, neovim.state?.NVIM_APPNAME, input.envOverrides)
|
|
140
|
-
return executeBlockingShellCommand(testDirectory, input, signal, allowFailure, env)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export async function runLuaCode(options: LuaCodeInput): Promise<RunLuaCodeOutput> {
|
|
144
|
-
const neovim = neovims.get(options.tabId.tabId)
|
|
145
|
-
assert(
|
|
146
|
-
neovim !== undefined,
|
|
147
|
-
`Neovim instance for clientId not found - cannot run Lua code. Maybe neovim's not started yet?`
|
|
148
|
-
)
|
|
149
|
-
assert(
|
|
150
|
-
neovim.application,
|
|
151
|
-
`Neovim application not found for client id ${options.tabId.tabId}. Maybe it's not started yet?`
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
const api = await neovim.state?.client.get()
|
|
155
|
-
if (!api) {
|
|
156
|
-
throw new Error(`Neovim API not available for client id ${options.tabId.tabId}. Maybe it's not started yet?`)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
log(`Neovim ${neovim.application.processId()} running Lua code: ${options.luaCode}`)
|
|
160
|
-
try {
|
|
161
|
-
const value = await api.lua(options.luaCode)
|
|
162
|
-
return { value }
|
|
163
|
-
} catch (e) {
|
|
164
|
-
console.warn(`Error running Lua code: ${options.luaCode}`, e)
|
|
165
|
-
throw new Error(`Error running Lua code: ${options.luaCode}`, { cause: e })
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export type PollLuaCodeInput = {
|
|
170
|
-
luaAssertion: string
|
|
171
|
-
tabId: TabId
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export async function waitForLuaCode(
|
|
175
|
-
options: PollLuaCodeInput,
|
|
176
|
-
signal: AbortSignal | undefined
|
|
177
|
-
): Promise<RunLuaCodeOutput> {
|
|
178
|
-
const neovim = neovims.get(options.tabId.tabId)
|
|
179
|
-
assert(
|
|
180
|
-
neovim !== undefined,
|
|
181
|
-
`Neovim instance for clientId not found - cannot pollLuaCode. Maybe neovim's not started yet?`
|
|
182
|
-
)
|
|
183
|
-
assert(
|
|
184
|
-
neovim.application,
|
|
185
|
-
`Neovim application not found for client id ${options.tabId.tabId}. Maybe it's not started yet?`
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
const api = await neovim.state?.client.get()
|
|
189
|
-
if (!api) {
|
|
190
|
-
throw new Error(`Neovim API not available for client id ${options.tabId.tabId}. Maybe it's not started yet?`)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
log(`Neovim ${neovim.application.processId()} polling Lua code: ${options.luaAssertion}`)
|
|
194
|
-
|
|
195
|
-
let running: boolean = true
|
|
196
|
-
signal?.addEventListener("abort", () => {
|
|
197
|
-
log(`Polling Lua code: '${options.luaAssertion}' was aborted via signal`)
|
|
198
|
-
running = false
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
const failureMessages = new Set<string>()
|
|
202
|
-
const reportFailure = () => {
|
|
203
|
-
console.warn(`Polling Lua code: '${options.luaAssertion}' failed. Failure messages:`, failureMessages)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const maxIterations = 100
|
|
207
|
-
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
208
|
-
if (!running) {
|
|
209
|
-
reportFailure()
|
|
210
|
-
throw new Error(`Polling Lua code: '${options.luaAssertion}' was aborted after ${iteration} iterations`)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
const value = await api.lua(options.luaAssertion)
|
|
215
|
-
log(`Lua code assertion passed: ${options.luaAssertion} (iteration ${iteration})`)
|
|
216
|
-
|
|
217
|
-
return { value }
|
|
218
|
-
} catch (e) {
|
|
219
|
-
failureMessages.add(String(e))
|
|
220
|
-
await timeout(100)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
reportFailure()
|
|
225
|
-
throw new Error(
|
|
226
|
-
`Polling Lua code: '${options.luaAssertion}' always raised an error after ${maxIterations} iterations`
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export async function runExCommand(options: ExCommandInput): Promise<RunExCommandOutput> {
|
|
231
|
-
const neovim = neovims.get(options.tabId.tabId)
|
|
232
|
-
assert(
|
|
233
|
-
neovim !== undefined,
|
|
234
|
-
`Neovim instance for clientId not found - cannot runExCommand. Maybe neovim's not started yet?`
|
|
235
|
-
)
|
|
236
|
-
assert(
|
|
237
|
-
neovim.application,
|
|
238
|
-
`Neovim application not found for client id ${options.tabId.tabId}. Maybe it's not started yet?`
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
const api = await neovim.state?.client.get()
|
|
242
|
-
if (!api) {
|
|
243
|
-
throw new Error(`Neovim API not available for client id ${options.tabId.tabId}. Maybe it's not started yet?`)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
log(`Neovim ${neovim.application.processId()} running Ex command: ${options.command}`)
|
|
247
|
-
try {
|
|
248
|
-
const output = await api.commandOutput(options.command)
|
|
249
|
-
if (options.log) {
|
|
250
|
-
console.info(`:${options.command} output: ${output}`)
|
|
251
|
-
}
|
|
252
|
-
return { value: output }
|
|
253
|
-
} catch (e) {
|
|
254
|
-
console.warn(`Error running Ex command: ${options.command}`, e)
|
|
255
|
-
throw new Error(`Error running Ex command: ${options.command}`, { cause: e })
|
|
256
|
-
}
|
|
257
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import fs from "fs"
|
|
2
|
-
import nodePath from "path"
|
|
3
|
-
|
|
4
|
-
export type TestTempDirPrefix = "test-temp-dir-"
|
|
5
|
-
|
|
6
|
-
export class TempDirectory implements Disposable {
|
|
7
|
-
private constructor(public readonly path: string) {}
|
|
8
|
-
|
|
9
|
-
public static create(): TempDirectory {
|
|
10
|
-
const tmp = fs.mkdtempSync("test-temp-dir-" satisfies TestTempDirPrefix)
|
|
11
|
-
const absolutePath = nodePath.resolve(tmp)
|
|
12
|
-
return new TempDirectory(absolutePath)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
[Symbol.dispose](): void {
|
|
16
|
-
fs.rmdirSync(this.path, { recursive: true, maxRetries: 5 })
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import nodePath from "path"
|
|
2
|
-
import type { PartialDeep } from "type-fest"
|
|
3
|
-
import { expect, it, vi } from "vitest"
|
|
4
|
-
import { createDefaultConfig } from "../../../config.js"
|
|
5
|
-
import type { TestServerConfig } from "../../../updateTestdirectorySchemaFile.js"
|
|
6
|
-
import type { TestDirsPath } from "./createTempDir.js"
|
|
7
|
-
import { createTempDir } from "./createTempDir.js"
|
|
8
|
-
import type { TestTempDirPrefix } from "./TempDirectory.js"
|
|
9
|
-
import { TempDirectory } from "./TempDirectory.js"
|
|
10
|
-
|
|
11
|
-
vi.spyOn(console, "log").mockImplementation(vi.fn())
|
|
12
|
-
|
|
13
|
-
it("should create a temp dir with no contents", async () => {
|
|
14
|
-
// typically the user will want to have contents, but this should not be an error
|
|
15
|
-
using dir = TempDirectory.create()
|
|
16
|
-
const result = await createTempDir({
|
|
17
|
-
...createDefaultConfig("cwd", {}),
|
|
18
|
-
directories: {
|
|
19
|
-
testEnvironmentPath: dir.path,
|
|
20
|
-
outputFilePath: nodePath.join(dir.path, "MyTestDirectory.ts"),
|
|
21
|
-
latestSymlinkName: "latest",
|
|
22
|
-
},
|
|
23
|
-
} satisfies PartialDeep<TestServerConfig>)
|
|
24
|
-
|
|
25
|
-
expect(result.contents).toEqual({})
|
|
26
|
-
expect(result.testEnvironmentPath).toEqual(dir.path)
|
|
27
|
-
expect(result.testEnvironmentPath.includes("test-temp-dir-" satisfies TestTempDirPrefix)).toBeTruthy()
|
|
28
|
-
expect(result.testEnvironmentPathRelative.includes("testdirs" satisfies TestDirsPath)).toBeTruthy()
|
|
29
|
-
})
|