Repository navigation
Conversation
|
@fdncred Here is the PR for the debugger in vscode |
|
wow, that's a lot of changes. LOL |
There was a problem hiding this comment.
Notes from my clanker.
Lint, both typechecks, compile and the 33 unit tests pass on d3b563b. The comments below come from reading through the debugger code. The examples for script-signatures.ts and split-args.ts come from running those helpers under node with the inputs shown.
The resolve.ts change is probably the one to settle first, since it affects language server and terminal users who never open the debugger. Most of the rest is the launch-time signature parsing. With some common signatures it skips the argument prompt when the script needs arguments, and with others it shows the prompt when the script needs none.
9be2a1b to
2b2d722
Compare
|
Also reverted much of the unrelated changes to keep it as small as possible |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2b2d722 to
61f7edb
Compare
|
let's do one more review to make sure things look in order and look at the ci issue as well. |
fdncred
left a comment
There was a problem hiding this comment.
Notes from my clanker, second pass at 61f7edb.
Earlier review threads
All nine are addressed. Three of them have a follow-up, posted inline.
resolve.tsbad setting: the PATH fallback and the warning are back. Follow-up: the warning now fires from every caller, includingquietNushell, so one F5 can show it twice.script-signatures.ts(three threads: space-separated params,[^\]]*and nested modules, dashed optionals and comments): moving toast --jsonfixes all of them, and only top-level defs are read now. Follow-up: adefwith an attribute in front (@example,@category,@search-terms) comes back wrapped inAttributeBlockand gets skipped. That brings back the original bug, no prompt andmainruns without its argument.entry-point.tsrelativeprogram:absoluteProgramresolves it. Follow-up: a relativecwdis used as the base forprogram, but it goes to the adapter unresolved.split-args.ts:--name="hello world" xnow gives['--name=hello world', 'x'].restart-on-save.ts: only manual saves of the session'sprogramrestart now.pinned-variable.ts:pinVariable/unpinVariablebumpgeneration. Follow-up (low): the first fetch invisualizeVariableisn't guarded.descriptor-factory.ts: throwsCancellationError, so only the resolver's message shows.
CI failure
The build failure on 61f7edb has nothing to do with the PR. GitHub builds the Docker image for lannonbr/vsce-action@master before the job's steps run, and its FROM node:20-slim pull got 504 Gateway Timeout from auth.docker.io three times in a row. Checkout, npm install, typecheck:webview, test:unit and vsce package were all skipped, so this head has never had a real CI run. Re-running the job should fix it. Since this comes from a fork, the re-run may need maintainer approval like the earlier action_required run did.
On 61f7edb locally: npm run lint, tsc -p client --noEmit, npm run typecheck:webview, npm run test:unit (35 pass), npm run compile and vsce package all succeed, and the .vsix has out/debug/ir-panel.html, visualize-panel.html and visualize-panel.webview.js.
Optional, and it could be its own PR: the workflow now runs actions/setup-node, so - run: npx @vscode/vsce package could replace lannonbr/vsce-action@master. That drops the Docker Hub pull from every run and stops depending on an unpinned @master.
Minor, no inline comment
shared/html.tsmakeNonceusesMath.random().crypto.randomBytes(16).toString('base64')is the usual choice for a CSP nonce.test:unitpasses a glob tonode --test, which needs Node 21 or newer. CI pins 22, but CONTRIBUTING doesn't mention it, and on Node 20 the command fails.- The Show IR button sits in the title bar of every Nushell editor, even with no debug session or with a
nuolder than 0.116. AddinginDebugModeto itswhenclause would hide it outside debugging, if that's what you want. - The comment on
LOCALS_REFERENCEinvisualize-request.tspoints at "the fork's crates/nu-dap/src/state.rs".nu-dapis upstream now, so the comment could point there. - In
restart-on-save.ts, theisNuDocumentcheck is redundant onceisSessionProgrammatches the path. const nushell = async () => ensureNushell();inextension.tswraps a sync call in a promise. Meanwhileconfiguration-provider.tsimportsresolveNushelldirectly instead of using the injected resolver, so the debugger getsnutwo different ways.- On main, the not-found error told the user to run "Nushell: Start Language Server" after installing. The new message dropped that. A setting change restarts the server, but installing
nuonto PATH doesn't.
| for (const pipeline of pipelines) { | ||
| const elements = field(pipeline, 'elements'); | ||
| const first: unknown = Array.isArray(elements) ? elements[0] : undefined; | ||
| const call = field(field(field(first, 'expr'), 'expr'), 'Call'); |
There was a problem hiding this comment.
A def with an attribute in front of it doesn't come back as a Call. Its expr.expr is {"AttributeBlock": {"attributes": [...], "item": {"expr": {"Call": ...}}}}, so field(..., 'Call') is undefined and the def is skipped.
With nu 0.116.2:
@example "greet" { main 1 }
def main [x: int] {}readDefinitions returns []. No main is found and there are no defs to pick from, so askForEntryPoint returns the config unchanged. main runs without x, which is the failure the earlier signature threads were about. @example, @category and @search-terms are common on def main in newer scripts.
Unwrapping first would cover it:
const expr = field(field(first, 'expr'), 'expr');
const block = field(expr, 'AttributeBlock');
const call = field(block !== undefined ? field(field(block, 'item'), 'expr') : expr, 'Call');A fixture case for it would be good too.
|
|
||
| return { | ||
| name, | ||
| signature: typeof written === 'string' ? written.slice(1, -1) : '', |
There was a problem hiding this comment.
slice(1, -1) expects the Signature span to end at ]. When the def declares input/output types, the span covers those as well. With nu 0.116.2:
def main [x: int]: nothing -> int { $x }gives the signaturex: int]: nothing -> in- a multi-line signature ending
]: nothing -> nothing {gives\n name: string # the name\n]: nothing -> nothin
The prompt then reads Requires arguments: main [x: int]: nothing -> in]. Cutting at the last ] before the : (or rebuilding the label from required_positional names) would fix it.
| return { kind: 'found', path: found, source: 'setting' }; | ||
| } | ||
|
|
||
| void window.showWarningMessage( |
There was a problem hiding this comment.
The fallback is back, thanks. But the warning now lives inside resolveNushell, so every caller shows it, including quietNushell in configuration-provider.ts, whose comment says it doesn't report. With a stale setting, one F5 calls resolveNushell from quietNushell and again from ensureNushell in the descriptor factory, and the same warning comes up twice. Opening a terminal and every change to the setting each show it again.
source on the found result (and the NushellSource type) is never read. That looks like the place this was meant to go. Have resolveNushell return something like { kind: 'found', path, fellBackFrom?: configured } without side effects, and let ensureNushell decide whether to warn. Then quietNushell really is quiet.
| if (e.affectsConfiguration(`${CONFIG_SECTION}.trace.server`)) { | ||
| applyTraceFromConfig(); | ||
| } | ||
| if (e.affectsConfiguration(`${CONFIG_SECTION}.nushellExecutablePath`)) { |
There was a problem hiding this comment.
This runs on every change to nushellExecutablePath. The Settings UI writes the value as you type, so typing /usr/local/bin/nu produces several changes with partial paths. Each one runs which, shows the "not found ... Falling back" warning, and stops and restarts the language server on PATH nu.
It also starts the server when the user had stopped it with "Nushell: Stop Language Server", since restartLanguageServer calls startLanguageServer either way.
Two overlapping events can also race. The second handler's stopLanguageServer returns right away because client is already cleared, so it starts a client. The first handler then calls startLanguageServer and shows "Nushell Language Server is already running."
Restarting only when client was running, debouncing a bit, and skipping when the resolved path hasn't changed would handle all three.
| return program; // nothing to anchor it to: let the adapter report it | ||
| } | ||
|
|
||
| return path.resolve(...bases, program); |
There was a problem hiding this comment.
A relative cwd gets resolved against the workspace folder here, but only to build program. config.cwd itself goes to the adapter unchanged, and DebugAdapterExecutable is created without a cwd option, so nu --dap resolves a relative cwd against the extension host's working directory. "cwd": "scripts" would then set $env.PWD to something unrelated to the workspace.
Setting config.cwd = path.resolve(folder.uri.fsPath, config.cwd) here (when there's a folder) would keep the two consistent.
| config: vscode.DebugConfiguration, | ||
| def: NuDefinition, | ||
| ): Promise<vscode.DebugConfiguration | undefined> { | ||
| if (config.args !== undefined) { |
There was a problem hiding this comment.
args: [] counts as "args given", so the prompt is skipped. The debug example in the README includes "args": [], so anyone who copies it gets no prompt for def main [name: string], and main fails on the missing argument.
Treating an empty array the same as a missing one (if (Array.isArray(config.args) && config.args.length > 0)) matches what people mean by []. If you want a way to force no prompt, removing args from the README example is the other option.
| throw new CancellationError(); | ||
| } | ||
|
|
||
| await this.checkDapSupport(nu); |
There was a problem hiding this comment.
Low priority. VS Code calls resolveDebugConfigurationWithSubstitutedVariables before createDebugAdapterDescriptor, so on a nu older than 0.116 the user picks an entry point and types arguments, and only then gets "Debugging is disabled because it needs Nushell 0.116.0". Running the version check in the configuration provider, before resolveEntryPoint, would stop the launch before any prompts.
| } | ||
|
|
||
| try { | ||
| const body = await fetchVisualize(session, address); |
There was a problem hiding this comment.
Low priority, same kind of race as the generation thread. This fetch has no guard. Visualize $big (slow), then $small (fast) before the first answer comes back. $small paints, then $big arrives and paints over it, and trackLive pins $big. Taking a generation number before the await and dropping a stale answer would handle it. pinned-variable.ts could export a helper for that.
| maxBuffer: 256 * 1024 * 1024, // the AST is far bigger than the source | ||
| }, | ||
| ); | ||
| parse.child.stdin?.end(source); |
There was a problem hiding this comment.
Low priority. Nothing listens for error on stdin. If nu exits before it reads the whole script, the write fails with EPIPE and Node raises it as an uncaught exception in the extension host. The try/catch around this doesn't see it. I reproduced it by passing /usr/bin/true as nu with a 5 MB script. readDefinitions still returns undefined, but the uncaught EPIPE shows up too. A real nu only hits this if it dies early. parse.child.stdin?.on('error', () => {}) before end is enough.
| @@ -0,0 +1,22 @@ | |||
| # Main entrypoint: `def main` makes this a proper CLI script. | |||
| # The debugger will prompt for arguments when launching. | |||
| # Try: name="world" count=3 --verbose --tag="release" | |||
There was a problem hiding this comment.
Nushell has no name=value syntax for positionals. Typing this at the prompt passes the literal string name=world as name, and count=3 fails to parse as an int. Something like # Try: world 3 --verbose --tag=release would work.
The PR for integrating the new nushell debugger protocol into vscode.
With this PR comes all the features of the original repo https://github.com/rvhelden/vscode-nushell-dap