davsclaus commented on code in PR #26750:
URL: https://github.com/apache/camel/pull/26750#discussion_r4075068338


##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java:
##########
@@ -364,6 +384,175 @@ public static JsonObject validate(ToolContext ctx, Path 
dir, String file, String
     /** How long a write waits for the running integration's reload record 
before answering without it. */
     static final long RELOAD_WAIT_MILLIS = 8000;
 
+    /**
+     * Replaces one snippet of a file and writes the result through {@link 
#writeFile}, so a change to an existing file
+     * does not rewrite every line of it: a model that re-emits a whole file 
corrupts the lines it did not mean to touch
+     * (CAMEL-24909). The snippet must occur exactly once; the answer says 
what was replaced.
+     */
+    public static JsonObject editFile(ToolContext ctx, Path dir, String file, 
String find, String replace) {
+        JsonObject edit = editedContent(dir, file, find, replace);
+        String content = edit.getString("content");
+        if (content == null) {
+            return edit; // not-found or ambiguous: the answer says what to do 
instead
+        }
+        JsonObject result = writeFile(ctx, dir, file, content, true);
+        if (!"invalid".equals(result.getString("status"))) {
+            result.put("status", "edited");
+            result.put("editedAtLine", edit.getInteger("editedAtLine"));
+            result.put("replacedLines", edit.getInteger("replacedLines"));
+        } else {
+            result.put("message", "The file was not changed: the result has 
validation errors. Fix them and call"
+                                  + " camel_edit_file again.");
+        }
+        return result;
+    }
+
+    /**
+     * The content of the file with the snippet replaced, in {@code content}, 
with the line it changed and how many
+     * lines it replaced; or the answer of a miss (not-found, with the nearest 
lines) or of an ambiguous snippet. The
+     * TUI writes that content itself, so an edit is confirmed and replayed in 
the editor like a write.
+     */
+    public static JsonObject editedContent(Path dir, String file, String find, 
String replace) {
+        Path path = resolveFile(dir, file);
+        if (!Files.isRegularFile(path)) {
+            throw new ToolExecutionException(file + " does not exist: write 
the whole file with camel_write_file");
+        }
+        String content;
+        try {
+            content = Files.readString(path, StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new ToolExecutionException("Failed to read " + path + ": " + 
e.getMessage());
+        }
+        if (find == null || find.isEmpty()) {
+            throw new ToolExecutionException("find is required: the text to 
replace, as it stands in the file");
+        }
+        int first = content.indexOf(find);
+        int length = find.length();
+        if (first < 0) {
+            // the same lines with different indentation or trailing spaces: a 
model composes the snippet from the
+            // shape it has in mind rather than from the file (CAMEL-24909), 
so match on the trimmed lines when that
+            // names exactly one place
+            int[] window = uniqueTrimmedWindow(content, find);
+            if (window != null) {
+                first = window[0];
+                length = window[1] - window[0];
+            }
+        }
+        JsonObject result = new JsonObject();
+        result.put("file", file);
+        if (first < 0) {
+            result.put("status", "not-found");
+            String nearest = nearestBlock(content, find);
+            result.put("message", "The text to find is not in the file as 
given; copy the lines from the file"
+                                  + (nearest != null ? ", which has there:\n" 
+ nearest : " (camel_get_files reads it)"));
+            if (nearest != null) {
+                result.put("nearest", nearest);
+            }
+            return result;
+        }
+        if (content.indexOf(find, first + find.length()) >= 0) {
+            result.put("status", "ambiguous");
+            result.put("occurrences", count(content, find));
+            result.put("message", "The text to find occurs more than once: 
include the lines around it so it names one"
+                                  + " place, or write the whole file with 
camel_write_file");
+            return result;
+        }
+        int line = (int) content.substring(0, first).lines().count()
+                   + (first > 0 && content.charAt(first - 1) == '\n' ? 1 : 0);
+        result.put("content", content.substring(0, first) + replace + 
content.substring(first + length));
+        result.put("editedAtLine", Math.max(1, line));
+        result.put("replacedLines", (int) find.lines().count());

Review Comment:
   Right, on the trimmed path the window can be shorter than `find`. It now 
counts the lines of the file between the two offsets, which is the same number 
on the exact path and the correct one on the trimmed path; that also avoids a 
discriminator. Test added with a `find` ending in a blank line: `replacedLines` 
is 2, not 3.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to