Visualize inline ChatGPT comments for deleted code

Upgraded the code lookup process to attempt displaying ChatGPT's
suggestions for deleted code inline, similar to how it is done for new
and existing code.

Jira-Id: IT-103
Change-Id: Iaa3bff8f465900c639ec16fb0582b7f70e470a2a
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
index 381cd63..6bbb579 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -9,6 +9,7 @@
 import com.googlesource.gerrit.plugins.chatgpt.client.InlineCode;
 import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.ChatGptSuggestionPoint;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.FileDiffProcessed;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.GerritCommentRange;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
@@ -30,7 +31,7 @@
 
     private List<HashMap<String, Object>> reviewBatches;
     private List<JsonObject> commentProperties;
-    private HashMap<String, List<String>> filesNewContent;
+    private HashMap<String, FileDiffProcessed> fileDiffsProcessed;
     private boolean isCommentEvent;
 
     @Inject
@@ -50,7 +51,7 @@
         config.configureDynamically(Configuration.KEY_GPT_USER_PROMPT, gerritClient.getUserPrompt());
 
         String reviewSuggestion = getReviewSuggestion(config, fullChangeId, patchSet);
-        log.info("ChatGPT response: {}", reviewSuggestion);
+        log.debug("ChatGPT response: {}", reviewSuggestion);
         if (isCommentEvent || config.getGptReviewByPoints()) {
             retrieveReviewFromJson(reviewSuggestion);
         }
@@ -105,11 +106,11 @@
         if (filename.equals("/COMMIT_MSG")) {
             return gerritCommentRange;
         }
-        if (!filesNewContent.containsKey(filename)) {
-            log.info("Suggestion filename {} not found in the patch", suggestion);
+        if (!fileDiffsProcessed.containsKey(filename)) {
+            log.info("Suggestion filename '{}' not found in the patch", suggestion);
             return gerritCommentRange;
         }
-        InlineCode inlineCode = new InlineCode(filesNewContent.get(filename));
+        InlineCode inlineCode = new InlineCode(fileDiffsProcessed.get(filename));
         gerritCommentRange = inlineCode.findCommentRange(suggestion);
         if (gerritCommentRange.isEmpty()) {
             log.info("Inline code not found for suggestion {}", suggestion);
@@ -121,7 +122,7 @@
         review = review.replaceAll("^`*(?:json)?\\s*|\\s*`+$", "");
         Type chatGptResponseListType = new TypeToken<List<ChatGptSuggestionPoint>>(){}.getType();
         List<ChatGptSuggestionPoint> reviewJson = gson.fromJson(review, chatGptResponseListType);
-        filesNewContent = gerritClient.getFilesNewContent();
+        fileDiffsProcessed = gerritClient.getFileDiffsProcessed();
         for (ChatGptSuggestionPoint suggestion : reviewJson) {
             HashMap<String, Object> batchMap = new HashMap<>();
             if (suggestion.getId() != null) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/CodeFinder.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/CodeFinder.java
new file mode 100644
index 0000000..9c8d715
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/CodeFinder.java
@@ -0,0 +1,99 @@
+package com.googlesource.gerrit.plugins.chatgpt.client;
+
+import com.googlesource.gerrit.plugins.chatgpt.client.model.ChatGptSuggestionPoint;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.GerritCommentRange;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.InputFileDiff;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+@Slf4j
+public class CodeFinder {
+    private final List<InputFileDiff.Content> diff;
+    private int commentedLine;
+    private String[] commentedCode;
+    private int lastCommentedCodeLineNum ;
+    private GerritCommentRange currentCommentRange;
+    private GerritCommentRange closestCommentRange;
+    private int lineNum;
+
+    public CodeFinder(List<InputFileDiff.Content> diff) {
+        this.diff = diff;
+    }
+
+    private double calcCodeDistance(GerritCommentRange range, int fromLine) {
+        return Math.abs((range.end_line - range.start_line) / 2 - fromLine);
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<String> getDiffItem(Field diffField, InputFileDiff.Content diffItem) {
+        try {
+            return (List<String>) diffField.get(diffItem);
+        }
+        catch (IllegalAccessException e) {
+            log.error("Error while processing file difference (diff type: {})", diffField.getName(), e);
+            return null;
+        }
+    }
+
+    private void findCodeLines(String diffType, List<String> diffLines) {
+        int codeLinePointer = 0;
+        for (String newContentLine : diffLines) {
+            String commentedCodeLine = commentedCode[codeLinePointer];
+            // Search for the commented code in the content
+            int codeCharacter = newContentLine.indexOf(commentedCodeLine);
+            if (codeCharacter != -1) {
+                // If the beginning of a commented code is found, currentCommentRange is initialized
+                if (codeLinePointer == 0) {
+                    currentCommentRange = GerritCommentRange.builder()
+                            .start_line(lineNum)
+                            .start_character(codeCharacter)
+                            .build();
+                }
+                // If the ending of a commented code is found, the currentCommentRange ending values are set
+                if (codeLinePointer >= lastCommentedCodeLineNum) {
+                    currentCommentRange.setEnd_line(lineNum);
+                    currentCommentRange.setEnd_character(codeCharacter + commentedCodeLine.length());
+                    // If multiple commented code portions are found and currentCommentRange is closer to the line
+                    // number suggested by ChatGPT than closestCommentRange, it becomes the new closestCommentRange
+                    if (closestCommentRange == null || calcCodeDistance(currentCommentRange, commentedLine) <
+                            calcCodeDistance(closestCommentRange, commentedLine)) {
+                        closestCommentRange = currentCommentRange.toBuilder().build();
+                    }
+                }
+                else {
+                    codeLinePointer++;
+                }
+            }
+            else {
+                codeLinePointer = 0;
+            }
+            if (diffType.contains("b")) {
+                lineNum++;
+            }
+        }
+    }
+
+    public GerritCommentRange findCode(ChatGptSuggestionPoint suggestion, int commentedLine) {
+        this.commentedLine = commentedLine;
+        // Split the commented code into lines and remove the trailing spaces from each line
+        commentedCode = suggestion.getCodeSnippet().trim().split("\\s*\n\\s*");
+        lastCommentedCodeLineNum = commentedCode.length -1;
+        currentCommentRange = null;
+        closestCommentRange = null;
+        lineNum = 1;
+        for (InputFileDiff.Content diffItem : diff) {
+            for (Field diffField : InputFileDiff.Content.class.getDeclaredFields()) {
+                String diffType = diffField.getName();
+                List<String> diffLines = getDiffItem(diffField, diffItem);
+                if (diffLines != null) {
+                    findCodeLines(diffType, diffLines);
+                }
+            }
+        }
+
+        return closestCommentRange;
+    }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
index 5afda10..c2e8f73 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
@@ -3,6 +3,7 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gson.JsonObject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.FileDiffProcessed;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
 
@@ -37,8 +38,8 @@
         return gerritClientPatchSet.isDisabledTopic(topic);
     }
 
-    public HashMap<String, List<String>> getFilesNewContent() {
-        return gerritClientPatchSet.getFilesNewContent();
+    public HashMap<String, FileDiffProcessed> getFileDiffsProcessed() {
+        return gerritClientPatchSet.getFileDiffsProcessed();
     }
 
     public List<JsonObject> getCommentProperties() {
@@ -54,7 +55,7 @@
     }
 
     public String getUserPrompt() {
-        return gerritClientComments.getUserPrompt(getFilesNewContent());
+        return gerritClientComments.getUserPrompt(getFileDiffsProcessed());
     }
 
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java
index 8225747..884897f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java
@@ -4,6 +4,7 @@
 import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.FileDiffProcessed;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
 
@@ -21,12 +22,12 @@
 public class GerritClientBase {
     protected final Gson gson = new Gson();
     protected final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry();
-    protected HashMap<String, List<String>> filesNewContent;
+    protected HashMap<String, FileDiffProcessed> fileDiffsProcessed;
     protected Configuration config;
 
     public GerritClientBase(Configuration config) {
         this.config = config;
-        filesNewContent = new HashMap<>();
+        fileDiffsProcessed = new HashMap<>();
         config.resetDynamicConfiguration();
     }
 
@@ -64,8 +65,8 @@
         return gson.fromJson(responseBody, JsonObject.class);
     }
 
-    public HashMap<String, List<String>> getFilesNewContent() {
-        return filesNewContent;
+    public HashMap<String, FileDiffProcessed> getFileDiffsProcessed() {
+        return fileDiffsProcessed;
     }
 
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java
index d1be462..1126572 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java
@@ -8,6 +8,7 @@
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.ChatGptRequestPoint;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.FileDiffProcessed;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.entity.ContentType;
@@ -160,7 +161,7 @@
         requestPoint.setId(i);
         if (commentProperty.has("line") || commentProperty.has("range")) {
             String filename = commentProperty.get("filename").getAsString();
-            InlineCode inlineCode = new InlineCode(filesNewContent.get(filename));
+            InlineCode inlineCode = new InlineCode(fileDiffsProcessed.get(filename));
             requestPoint.setFilename(filename);
             requestPoint.setLineNumber(commentProperty.get("line").getAsInt());
             requestPoint.setCodeSnippet(inlineCode.getInlineCode(commentProperty));
@@ -219,8 +220,8 @@
         }
     }
 
-    public String getUserPrompt(HashMap<String, List<String>> filesNewContent) {
-        this.filesNewContent = filesNewContent;
+    public String getUserPrompt(HashMap<String, FileDiffProcessed> fileDiffsProcessed) {
+        this.fileDiffsProcessed = fileDiffsProcessed;
         List<ChatGptRequestPoint> requestPoints = new ArrayList<>();
         for (int i = 0; i < commentProperties.size(); i++) {
             requestPoints.add(getRequestPoint(i));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java
index d4813e6..8a2a691 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java
@@ -4,6 +4,7 @@
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.FileDiffProcessed;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.InputFileDiff;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.OutputFileDiff;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
@@ -82,29 +83,29 @@
 
     private void processFileDiffItem(Field inputDiffField, InputFileDiff.Content contentItem,
                                      OutputFileDiff.Content outputContentItem) {
-        String fieldName = inputDiffField.getName();
+        String diffType = inputDiffField.getName();
         try {
             // Get the `a`, `b` or `ab` field's value from the input diff content
             @SuppressWarnings("unchecked")
-            List<String> fieldValue = (List<String>) inputDiffField.get(contentItem);
-            if (fieldValue == null) {
+            List<String> diffLines = (List<String>) inputDiffField.get(contentItem);
+            if (diffLines == null) {
                 return;
             }
             if (isCommitMessage) {
-                filterCommitMessageContent(fieldValue);
+                filterCommitMessageContent(diffLines);
             }
-            if (config.getGptFullFileReview() || !fieldName.equals("ab")) {
+            if (config.getGptFullFileReview() || !diffType.equals("ab")) {
                 // Get the corresponding `a`, `b` or `ab` field from the output diff class
-                Field outputDiffField = OutputFileDiff.Content.class.getDeclaredField(fieldName);
+                Field outputDiffField = OutputFileDiff.Content.class.getDeclaredField(diffType);
                 // Store the new field's value in the output diff content `outputContentItem`
-                outputDiffField.set(outputContentItem, String.join("\n", fieldValue));
+                outputDiffField.set(outputContentItem, String.join("\n", diffLines));
             }
             // If the lines modified in the PatchSet are not deleted, they are utilized to populate newFileContent
-            if (fieldName.contains("b")) {
-                newFileContent.addAll(fieldValue);
+            if (diffType.contains("b")) {
+                newFileContent.addAll(diffLines);
             }
         } catch (IllegalAccessException | NoSuchFieldException e) {
-            log.error("Error while processing file difference (field name: {})", fieldName, e);
+            log.error("Error while processing file difference (diff type: {})", diffType, e);
         }
     }
 
@@ -127,7 +128,7 @@
             }
             outputDiffContent.add(outputContentItem);
         }
-        filesNewContent.put(filename, newFileContent);
+        fileDiffsProcessed.put(filename, new FileDiffProcessed(inputDiffContent, newFileContent));
         outputFileDiff.setContent(outputDiffContent);
         diffs.add(gson.toJson(outputFileDiff));
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/InlineCode.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/InlineCode.java
index 3d9123d..4063fb9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/InlineCode.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/InlineCode.java
@@ -3,6 +3,7 @@
 import com.google.gson.JsonObject;
 import com.google.gson.Gson;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.ChatGptSuggestionPoint;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.FileDiffProcessed;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.GerritCommentRange;
 import lombok.extern.slf4j.Slf4j;
 
@@ -13,11 +14,13 @@
 @Slf4j
 public class InlineCode {
     private final Gson gson = new Gson();
+    private final CodeFinder codeFinder;
     private final List<String> newContent;
     private GerritCommentRange range;
 
-    public InlineCode(List<String> newContent) {
-        this.newContent = newContent;
+    public InlineCode(FileDiffProcessed fileDiffProcessed) {
+        codeFinder = new CodeFinder(fileDiffProcessed.getDiff());
+        newContent = fileDiffProcessed.getNewContent();
     }
 
     private String getLineSlice(int line_num) {
@@ -45,10 +48,6 @@
         }
     }
 
-    public double calcCodeDistance(GerritCommentRange range, int fromLine) {
-        return Math.abs((range.end_line - range.start_line) / 2 - fromLine);
-    }
-
     public Optional<GerritCommentRange> findCommentRange(ChatGptSuggestionPoint suggestion) {
         int commentedLine;
         try {
@@ -58,46 +57,8 @@
             // If the line number is not passed, a line in the middle of the code is used as best guess
             commentedLine = newContent.size() / 2;
         }
-        // Split the commented code into lines and remove the trailing spaces from each line
-        String[] commentedCode = suggestion.getCodeSnippet().trim().split("\\s*\n\\s*");
-        int codeLinePointer = 0;
-        int lastCommentedCodeLineNum = commentedCode.length -1;
-        GerritCommentRange currentCommentRange = null;
-        GerritCommentRange closestCommentRange = null;
 
-        for (int lineNum = 1; lineNum < newContent.size(); lineNum++) {
-            String commentedCodeLine = commentedCode[codeLinePointer];
-            String newContentLine = newContent.get(lineNum);
-            // Search for the commented code in the new content
-            int codeCharacter = newContentLine.indexOf(commentedCodeLine);
-            if (codeCharacter != -1) {
-                // If the beginning of a commented code is found, currentCommentRange is initialized
-                if (codeLinePointer == 0) {
-                    currentCommentRange = GerritCommentRange.builder()
-                            .start_line(lineNum)
-                            .start_character(codeCharacter)
-                            .build();
-                }
-                // If the ending of a commented code is found, the currentCommentRange ending values are set
-                if (codeLinePointer >= lastCommentedCodeLineNum) {
-                    currentCommentRange.setEnd_line(lineNum);
-                    currentCommentRange.setEnd_character(codeCharacter + commentedCodeLine.length());
-                    // If multiple commented code portions are found and currentCommentRange is closer to the line
-                    // number suggested by ChatGPT than closestCommentRange, it becomes the new closestCommentRange
-                    if (closestCommentRange == null || calcCodeDistance(currentCommentRange, commentedLine) <
-                            calcCodeDistance(closestCommentRange, commentedLine)) {
-                        closestCommentRange = currentCommentRange.toBuilder().build();
-                    }
-                }
-                else {
-                    codeLinePointer++;
-                }
-            }
-            else {
-                codeLinePointer = 0;
-            }
-        }
-        return Optional.ofNullable(closestCommentRange);
+        return Optional.ofNullable(codeFinder.findCode(suggestion, commentedLine));
     }
 
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/FileDiffProcessed.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/FileDiffProcessed.java
new file mode 100644
index 0000000..3dc2015
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/FileDiffProcessed.java
@@ -0,0 +1,13 @@
+package com.googlesource.gerrit.plugins.chatgpt.client.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+public class FileDiffProcessed {
+    private List<InputFileDiff.Content> diff;
+    private List<String> newContent;
+}