Repository navigation
Conversation
Using `code-server --wait --reuse-window` as GIT_EDITOR fails with "Unknown option --wait" whenever code-server's own script is the one on PATH rather than the remote-cli script VS Code adds to the integrated terminal (coder#8089). It also cannot be used as an editor from a shell outside the integrated terminal. With --wait, code-server now creates a marker file, passes it to Code with the open request, and returns once Code deletes it, which Code does when the opened files are closed. This is what VS Code's remote-cli does. Like --reuse-window, --wait always opens in an existing instance.
| ) | ||
| vscode.on("error", (error: unknown) => { | ||
| logger.error("got error from Code", field("error", error)) | ||
| failed = true |
There was a problem hiding this comment.
I think in the error case we should actually throw the error instead of logging it, so the wait command does not exit with 0 despite the request having failed.
With other commands it would not matter as much if we exit with 0 despite failing to make the request, but I think it will be more important with --wait otherwise git might think the edit was done even though it failed.
I think it could look something like this (untested):
try {
await new Promise((resolve, reject) => {
const vscode = http.request(
{
path: "/",
method: "POST",
socketPath,
},
(response) => {
response.on("data", (message) => {
logger.debug("got message from Code", field("message", message.toString()))
})
response.on("end", resolve)
},
)
vscode.on("error", reject)
vscode.write(JSON.stringify(pipeArgs))
vscode.end()
})
} catch (error) {
// Try to clean up the marker, but do not let removal errors override the original error.
if (marker) {
try {
await fs.rm(marker)
} catch (error) {
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
logger.error("failed to clean up wait marker", error)
}
}
}
throw error
}
// Code deletes the marker file once the opened files are closed.
while (marker && await isFile(marker)) {
await newPromise((resolve) => setTimeout(resolve, 1000))
}There was a problem hiding this comment.
Done, it now rejects on request errors and rethrows after trying to remove the marker, so --wait exits non-zero. I followed your sketch, so the other commands now wait for the response and fail on errors too.
| logger.error("--wait requires at least one file") | ||
| process.exit(1) | ||
| } | ||
| pipeArgs.waitMarkerFilePath = path.join(os.tmpdir(), `code-server-wait-${crypto.randomBytes(8).toString("hex")}`) |
There was a problem hiding this comment.
We should use tmpdir from constants instead of os.tmpdir() (it is basically the same but uses a code-server sub-directory).
Also maybe we could use generateUuid() from common/util.
So something like marker = path.join(tmpdir, generateUuid(16))
There was a problem hiding this comment.
Done: path.join(tmpdir, generateUuid(16)). I also mkdir tmpdir first, since nothing else creates it in the CLI process before this runs.
| expect((await fs.stat(marker!)).isFile()).toBe(true) | ||
|
|
||
| // Still waiting while the file is open. | ||
| await new Promise((resolve) => setTimeout(resolve, 1500)) |
There was a problem hiding this comment.
This would add an extra constant 1.5 seconds to the test run time, instead could we perhaps use Jest's timer mocks? jest.useFakeTimers() and jest.runAllTimers() should do the trick I think.
There was a problem hiding this comment.
Switched to fake timers. The test runs the poll timer as soon as it's scheduled, so it takes about 25 ms now.
|
|
||
| // Code deletes the marker once the file is closed. | ||
| await fs.rm(marker!) | ||
| await opening |
There was a problem hiding this comment.
Similarly here, after removing we would do jest.runAllTimers() again to make it resolve instantly.
There was a problem hiding this comment.
Done, the removal is followed by running the pending timer.
| await fs.rm(pipeArgs.waitMarkerFilePath, { force: true }) | ||
| } | ||
| server.close() | ||
| } |
There was a problem hiding this comment.
There is no need to clean up the file because everything in the tmpdir gets deleted as part of the test cleanup afterward.
Reject on request errors instead of logging them, so --wait does not exit 0 when Code never got the request, and clean up the marker in that case. Put the marker in code-server's tmpdir with generateUuid(16), and use fake timers in the test.
Fixes #8089
As suggested in the issue, this adds
--waitto code-server's own script, socode-server --wait --reuse-windowworks asGIT_EDITOReven when that script is the one onPATHinstead ofremote-cli. It also works from a shell outside the integrated terminal, through the session socket.It does what VS Code's remote-cli does: create a marker file, send it as
waitMarkerFilePathwith the open request, and return once Code deletes it after the files are closed. Like--reuse-window,--waitonly opens in an existing instance and errors if there is none.-wis already--welcome-text, so there's no short flag.Added unit tests for the wait loop (with a fake socket standing in for Code's) and for
--waitnot falling back to starting a server. I haven't run it against a full build.