fix(fs): report read errors of readTextFileLines (#3625)

* fix(fs): report read errors of readTextFileLines

In plugins/fs/src/commands.rs read_text_file_lines_next, a read error was
mapped to "not done, empty line", so a persistent error such as EISDIR
(File::open of a directory succeeds on Unix) made the iterator yield ""
forever and the error was swallowed.

The command now closes the resource and returns the error; the JS iterator
resets its rid so a new iteration starts over. api-iife.js regenerated. E2E
spec iterates a directory and expects a rejection.

* fix(fs): close the file when a readTextFileLines loop exits early

In plugins/fs/guest-js/index.ts readTextFileLines, the async iterator did
not implement return(), so breaking out of a for await loop left the
StdLinesResource (an open file) in the webview resource table.

return() now closes the resource and resets the iterator. api-iife.js
regenerated. E2E spec breaks out of a loop, checks the resource id is no
longer valid and that iterating again starts over.
This commit is contained in:
Lucas Fernandes Nogueira
2026-09-23 13:05:04 -03:00
committed by GitHub
parent 9106093f77
commit 1cae06a55b
6 changed files with 92 additions and 6 deletions
File diff suppressed because one or more lines are too long
+22 -4
View File
@@ -887,10 +887,17 @@ async function readTextFileLines(
})
}
const arr = await invoke<ArrayBuffer | number[]>(
'plugin:fs|read_text_file_lines_next',
{ rid: this.rid }
)
let arr: ArrayBuffer | number[]
try {
arr = await invoke<ArrayBuffer | number[]>(
'plugin:fs|read_text_file_lines_next',
{ rid: this.rid }
)
} catch (error) {
// the resource is closed on errors, the next iteration starts over
this.rid = null
throw error
}
const bytes =
arr instanceof ArrayBuffer ? new Uint8Array(arr) : Uint8Array.from(arr)
@@ -916,6 +923,17 @@ async function readTextFileLines(
}
},
// called when a `for await` loop exits early (`break`, `return` or `throw`)
async return(): Promise<IteratorResult<string>> {
if (this.rid !== null) {
const rid = this.rid
this.rid = null
// close the file, otherwise it stays open until the webview is destroyed
await new Resource(rid).close()
}
return { value: null, done: true }
},
[Symbol.asyncIterator](): AsyncIterableIterator<string> {
return this
}
+6 -1
View File
@@ -703,7 +703,12 @@ pub async fn read_text_file_lines_next<R: Runtime>(
bytes.push(false as u8);
Ok(bytes)
}
Some(Err(_)) => Ok(vec![false as u8]),
Some(Err(e)) => {
// the error may be persistent (e.g. reading a directory), do not report
// an empty line and let the caller loop forever
resource_table.close(rid)?;
Err(format!("failed to read line with error: {e}").into())
}
None => {
resource_table.close(rid)?;
Ok(vec![true as u8])