ek9_edit_file
Answers first: status (applied | ready | would-fail | conflicts), written, edits[{file, startLine, endLine, before, after}], wouldBeDiagnostics from the verifying compile and contentHash on an applied edit - an atomic batch of text edits to one file, or to several as one apply.
When to use it
You want a few exact text changes in an EXISTING file - one call with path + edits:[{mode:'find-replace', find:'old', replace:'new'}]. The line-replace and regex-replace modes mix freely in the same atomic batch, and files:[{path, edits}] lands several files as one apply. Cheaper than ek9_write_file for small surgical changes; untouched bytes are preserved exactly.
When not to use it
- You hold the FULL content of the file (a new file, or an editor tool returned modifiedSource) - use
ek9_write_file. - Renaming an EK9 identifier (class/method/field/local) — use
ek9_rename_symbol; it rewrites the workspace-wide reference graph, this tool is text-only and single-file. - Structural EK9 edits (changing a signature, adding a method, extracting a variable) — use the semantic editor (
ek9_change_signature,ek9_add_method,ek9_extract_variable, …); it understands EK9 grammar, this tool does not. - Removing the whole file — use
ek9_delete_file.
Details
find-replace is literal + case-sensitive (whitespace/indentation must match exactly - it is NOT re-indented), guarded by expectedOccurrences (integer, default 1, or 'all' — regex-replace shares the guard); line-replace takes 1-based inclusive startLine/endLine + replace (empty deletes) and a replacement written flush-left is re-indented to the replaced range's indentation (relative indentation kept; a later line shallower than the range is kept where written when only that compiles, said in corrected[]); a 'default operator X' line for a construct that already declares X is left out, and a find-replace whose find is gone because what it adds is already in the file is not applied - both said in corrected[] (nothing else to write is status:'unchanged'); regex-replace takes a Java pattern + replace with $1 backreferences. Every mode reads find/replace (replacement, replaceWith, newText, search, oldText and kind are accepted spellings). Edits apply sequentially (edits[1] sees edits[0]'s output) - a batch whose line-replace ranges were all numbered against the file as read, and fails in order but compiles applied bottom-up, is applied that way and says so in corrected[]; validation failures return status:'conflicts' (NO edits applied) with per-edit, mechanically repairable kinds — edit-position-out-of-range / find-not-found (carries a whitespace near-miss hint) / expected-occurrences-mismatch / cascading-edit-conflict / regex-invalid. Non-.ek9 targets are written without compile checking (non-ek9-file-not-compile-checked advisory); expectedContentHash gives whole-file lost-update protection.
Arguments
| Argument | Type | Required | What it is |
|---|---|---|---|
edits | array of object | Ordered edit operations, applied sequentially (edits[1] sees edits[0]'s output); one object per edit with mode + find/replace, or startLine/endLine + replace, or pattern + replace. | |
expectedContentHash | string | The contentHash ek9_read_file returned for the file's CURRENT content; the call is refused when the on-disk content differs (lost-update protection, same semantics as ek9_write_file). | |
files | array of object | MULTI-FILE form: [{path, edits}] instead of path + edits - every file's edits are applied and the workspace compiled ONCE, so a change that only compiles when several files move together (a signature changed here, its callers there) lands as one atomic apply; any file failing rolls back all of them. Use it whenever a single-file edit would be rolled back for diagnostics in OTHER files. | |
path | string | File path: workspace-relative (preferred) or absolute inside the workspace. Must exist. |
It also takes the arguments every tool takes.
Recorded calls
Real calls, recorded against small projects and checked on every build - a changed answer fails it. 3 of the 46 recorded are shown.
Find equals replace unchanged
Request
{
"edits" : [ {
"find" : "stdout.println(report(100.0, 0.25))",
"mode" : "find-replace",
"replace" : "stdout.println(report(100.0, 0.25))"
} ],
"path" : "task.ek9"
}
Response
{
"conflicts" : [ ],
"corrected" : [ "edits[0]: its find and replace are the same text, so it changes nothing - nothing is written for it" ],
"edits" : [ ],
"meta" : {
"serverMs" : "<timing>"
},
"risk" : {
"crossesModuleBoundary" : false,
"estimatedSeverity" : "trivial",
"fileCount" : 0,
"lineCount" : 0,
"touchesPublicApi" : false,
"touchesTests" : false
},
"status" : "unchanged",
"summary" : "Nothing to apply: task.ek9 already has what the edits would write",
"workspaceHash" : "<workspace-hash>",
"wouldBeDiagnostics" : [ ],
"written" : false
}
Edit file dry run ready
Request
{
"dryRun" : true,
"edits" : [ {
"find" : "\"Goodbye\"",
"mode" : "find-replace",
"replace" : "\"Farewell\""
} ],
"path" : "main.ek9"
}
Response
{
"conflicts" : [ ],
"edits" : [ {
"after" : " <- result as String: \"Farewell\"",
"before" : " <- result as String: \"Goodbye\"",
"endLine" : 10,
"file" : "main.ek9",
"startLine" : 10,
"type" : "replace"
} ],
"meta" : {
"serverMs" : "<timing>"
},
"risk" : {
"crossesModuleBoundary" : false,
"estimatedSeverity" : "trivial",
"fileCount" : 1,
"lineCount" : 1,
"touchesPublicApi" : false,
"touchesTests" : false
},
"status" : "ready",
"summary" : "Would apply 1 edit (1 line affected)",
"workspaceHash" : "<workspace-hash>",
...
Cut short here: 3 more lines. The whole recorded call.
Default operator already declared left out
Request
{
"edits" : [ {
"find" : " Basket()\n this.items :=? List() of String\n\n add()",
"mode" : "find-replace",
"replace" : " Basket()\n this.items :=? List() of String\n\n default operator ?\n\n add()"
} ],
"path" : "task.ek9"
}
Response
{
"conflicts" : [ ],
"corrected" : [ "edits[0]: 'default operator ?' left out - Basket already has 'default operator ?' (line 18); it was all the edit added, so nothing is written for it" ],
"edits" : [ ],
"meta" : {
"serverMs" : "<timing>"
},
"risk" : {
"crossesModuleBoundary" : false,
"estimatedSeverity" : "trivial",
"fileCount" : 0,
"lineCount" : 0,
"touchesPublicApi" : false,
"touchesTests" : false
},
"status" : "unchanged",
"summary" : "Nothing to apply: task.ek9 already has what the edits would write",
"workspaceHash" : "<workspace-hash>",
"wouldBeDiagnostics" : [ ],
"written" : false
}