modifiedContents = new ArrayList<>();
+
+ // Collect modified contents from code blocks
+ Node node = document.getFirstChild();
+ while (node != null) {
+ if (node instanceof FencedCodeBlock codeBlock) {
+ modifiedContents.add(codeBlock.getLiteral());
+ }
+ node = node.getNext();
+ }
+
+ GitMergeService.getInstance().showDiffView(
+ chatMessageContext.getProject(),
+ files.get(0),
+ modifiedContents.get(0));
+ }
+ }
+
/**
* Ollama does not count the input context tokens in the token usage, this method fixes this.
*
diff --git a/src/main/java/com/devoxx/genie/ui/settings/DevoxxGenieStateService.java b/src/main/java/com/devoxx/genie/ui/settings/DevoxxGenieStateService.java
index 4849b512..0b4412a5 100644
--- a/src/main/java/com/devoxx/genie/ui/settings/DevoxxGenieStateService.java
+++ b/src/main/java/com/devoxx/genie/ui/settings/DevoxxGenieStateService.java
@@ -42,6 +42,10 @@ public static DevoxxGenieStateService getInstance() {
private Boolean showExecutionTime = true;
+ // Git Diff features
+ private Boolean useDiffMerge = false;
+ private Boolean useSimpleDiff = false;
+
// Local LLM URL fields
private String ollamaModelUrl = OLLAMA_MODEL_URL;
private String lmstudioModelUrl = LMSTUDIO_MODEL_URL;
diff --git a/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitDiffMode.java b/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitDiffMode.java
new file mode 100644
index 00000000..b749a572
--- /dev/null
+++ b/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitDiffMode.java
@@ -0,0 +1,92 @@
+package com.devoxx.genie.ui.settings.gitmerge;
+
+public enum GitDiffMode {
+ DISABLED("Disabled", "", ""),
+ DIFF_MERGE("Git Diff Merge", "/images/diff_merge.jpg",
+ """
+
+
+
+
+
+ Three-panel comparison
+
Shows three-way comparison between original, suggested, and merged changes
+
+ - Left panel: Original file labeled "Original code"
+ - Center panel: Merge result labeled "Merged"
+ - Right panel: LLM's modified version labeled "LLM suggested"
+
+
+ """),
+ SIMPLE_DIFF("Simple Git Diff", "/images/simple_diff.jpg",
+ """
+
+
+
+
+
+ Two-panel side-by-side comparison
+
Shows direct comparison between original and suggested changes
+
+ - Left panel: Original file labeled "Original content"
+ - Right panel: Modified version labeled "LLM suggested"
+
+
+ """);
+
+ private final String displayName;
+ private final String iconPath;
+ private final String description;
+
+ GitDiffMode(String displayName, String iconPath, String description) {
+ this.displayName = displayName;
+ this.iconPath = iconPath;
+ this.description = description;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public String getIconPath() {
+ return iconPath;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitMergeSettingsComponent.java b/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitMergeSettingsComponent.java
new file mode 100644
index 00000000..a4ef9a67
--- /dev/null
+++ b/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitMergeSettingsComponent.java
@@ -0,0 +1,116 @@
+package com.devoxx.genie.ui.settings.gitmerge;
+
+import com.devoxx.genie.ui.settings.AbstractSettingsComponent;
+import com.devoxx.genie.ui.settings.DevoxxGenieStateService;
+import com.intellij.ui.scale.JBUIScale;
+import com.intellij.util.ui.JBUI;
+import lombok.Getter;
+import org.jdesktop.swingx.JXTitledSeparator;
+import com.intellij.openapi.ui.ComboBox;
+import javax.swing.*;
+import java.awt.*;
+
+public class GitMergeSettingsComponent extends AbstractSettingsComponent {
+
+ private final DevoxxGenieStateService stateService = DevoxxGenieStateService.getInstance();
+
+ @Getter
+ private final ComboBox gitDiffModeComboBox;
+
+ @Getter
+ private final JLabel previewImageLabel = new JLabel();
+
+ @Getter
+ private final JEditorPane descriptionLabel = new JEditorPane("text/html", "");
+
+ public GitMergeSettingsComponent() {
+ // Create combobox with Git Diff modes
+ gitDiffModeComboBox = new ComboBox<>(GitDiffMode.values());
+
+ // Set custom renderer to display the enum's display name
+ gitDiffModeComboBox.setRenderer(new DefaultListCellRenderer() {
+ @Override
+ public Component getListCellRendererComponent(JList> list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ if (value instanceof GitDiffMode) {
+ setText(((GitDiffMode) value).getDisplayName());
+ }
+ return this;
+ }
+ });
+
+ // Set initial selected mode from state service
+ GitDiffMode currentMode = determineCurrentMode();
+ gitDiffModeComboBox.setSelectedItem(currentMode);
+
+ // Add listener to update preview when selection changes
+ gitDiffModeComboBox.addActionListener(e -> {
+ GitDiffMode selectedMode = (GitDiffMode) gitDiffModeComboBox.getSelectedItem();
+ updatePreviewImage(selectedMode);
+ });
+ }
+
+ private GitDiffMode determineCurrentMode() {
+ if (stateService.getUseDiffMerge()) {
+ return GitDiffMode.DIFF_MERGE;
+ } else if (stateService.getUseSimpleDiff()) {
+ return GitDiffMode.SIMPLE_DIFF;
+ }
+ return GitDiffMode.DISABLED;
+ }
+
+ private void updatePreviewImage(GitDiffMode mode) {
+ float scaleFactor = JBUIScale.scale(1f);
+ try {
+ try (var imageStream = getClass().getResourceAsStream(mode.getIconPath())) {
+ if (imageStream != null) {
+ byte[] imageBytes = imageStream.readAllBytes();
+ ImageIcon icon = new ImageIcon(imageBytes);
+ previewImageLabel.setIcon(icon);
+ descriptionLabel.setText(
+ mode.getDescription()
+ .formatted(scaleFactor == 1.0f ? "normal" : scaleFactor * 100 + "%"));
+ } else {
+ throw new IllegalStateException("Image not found: " + mode.getIconPath());
+ }
+ }
+ } catch (Exception e) {
+ previewImageLabel.setIcon(null);
+ }
+ }
+
+ @Override
+ public JPanel createPanel() {
+ panel.setLayout(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.insets = JBUI.insets(5);
+
+ // Add title
+ panel.add(new JXTitledSeparator("Git Diff Mode"), gbc);
+
+ gbc.gridy++;
+ panel.add(new JLabel("Commit LLM suggestions using a Git Diff/Merge view"), gbc);
+
+ // Add combobox
+ gbc.gridy++;
+ gbc.gridx = 1;
+ panel.add(new JLabel("Select Git Diff Mode:"), gbc);
+ gbc.gridy++;
+ panel.add(gitDiffModeComboBox, gbc);
+
+ // Add description
+ gbc.gridy++;
+ panel.add(descriptionLabel, gbc);
+
+ // Add preview image
+ gbc.gridy++;
+ panel.add(previewImageLabel, gbc);
+
+ return panel;
+ }
+}
diff --git a/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitMergeSettingsConfigurable.java b/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitMergeSettingsConfigurable.java
new file mode 100644
index 00000000..88d66f87
--- /dev/null
+++ b/src/main/java/com/devoxx/genie/ui/settings/gitmerge/GitMergeSettingsConfigurable.java
@@ -0,0 +1,71 @@
+package com.devoxx.genie.ui.settings.gitmerge;
+
+import com.devoxx.genie.ui.settings.DevoxxGenieStateService;
+import com.intellij.openapi.options.Configurable;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class GitMergeSettingsConfigurable implements Configurable {
+
+ private final GitMergeSettingsComponent diffSettingsComponent;
+
+ public GitMergeSettingsConfigurable() {
+ diffSettingsComponent = new GitMergeSettingsComponent();
+ }
+
+ /**
+ * Get the display name
+ * @return the display name
+ */
+ @Nls
+ @Override
+ public String getDisplayName() {
+ return "LLM Git Merge";
+ }
+
+ /**
+ * Get the Prompt Settings component
+ *
+ * @return the component
+ */
+ @Nullable
+ @Override
+ public JComponent createComponent() {
+ return diffSettingsComponent.createPanel();
+ }
+
+ @Override
+ public boolean isModified() {
+ DevoxxGenieStateService stateService = DevoxxGenieStateService.getInstance();
+ GitDiffMode currentMode = determineCurrentMode(stateService);
+ return currentMode != diffSettingsComponent.getGitDiffModeComboBox().getSelectedItem();
+ }
+
+ @Override
+ public void apply() {
+ DevoxxGenieStateService stateService = DevoxxGenieStateService.getInstance();
+ GitDiffMode selectedMode = (GitDiffMode) diffSettingsComponent.getGitDiffModeComboBox().getSelectedItem();
+
+ stateService.setUseDiffMerge(selectedMode == GitDiffMode.DIFF_MERGE);
+ stateService.setUseSimpleDiff(selectedMode == GitDiffMode.SIMPLE_DIFF);
+ }
+
+ @Override
+ public void reset() {
+ DevoxxGenieStateService stateService = DevoxxGenieStateService.getInstance();
+ GitDiffMode currentMode = determineCurrentMode(stateService);
+ diffSettingsComponent.getGitDiffModeComboBox().setSelectedItem(currentMode);
+ }
+
+ private GitDiffMode determineCurrentMode(@NotNull DevoxxGenieStateService stateService) {
+ if (stateService.getUseDiffMerge()) {
+ return GitDiffMode.DIFF_MERGE;
+ } else if (stateService.getUseSimpleDiff()) {
+ return GitDiffMode.SIMPLE_DIFF;
+ }
+ return GitDiffMode.DISABLED;
+ }
+}
diff --git a/src/main/java/com/devoxx/genie/ui/util/WelcomeUtil.java b/src/main/java/com/devoxx/genie/ui/util/WelcomeUtil.java
index de3241c1..09aa623e 100644
--- a/src/main/java/com/devoxx/genie/ui/util/WelcomeUtil.java
+++ b/src/main/java/com/devoxx/genie/ui/util/WelcomeUtil.java
@@ -43,6 +43,7 @@ public class WelcomeUtil {
New features 🚀
Configure features in the settings page.
+ - 💪🏻Git Diff/Merge: Show Git Diff/Merge dialog to commit LLM suggestion
- ❌.gitignore: Exclude files and directories based on .gitignore file
- 👀Chat History: All chats are saved and can be restored or removed
- 🧠Project Scanner: Add source code (full project or by package) to prompt context (or clipboard) when using Anthropic, OpenAI or Gemini.
diff --git a/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java b/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java
index 6d8459fe..7fdb442b 100644
--- a/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java
+++ b/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java
@@ -49,7 +49,8 @@ public class ChatMessageContextUtil {
.webSearchRequested(actionCommand.equals(TAVILY_SEARCH_ACTION) || actionCommand.equals(GOOGLE_SEARCH_ACTION))
.totalFileCount(FileListManager.getInstance().size())
.executionTimeMs(0)
- .cost(0).build();
+ .cost(0)
+ .build();
boolean isStreamMode = stateService.getStreamMode() && actionCommand.equals(Constant.SUBMIT_ACTION);
if (isStreamMode) {
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 20cae54e..4d4ae2ce 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -35,9 +35,9 @@
]]>
v0.2.28
+ v0.3.0
-
+ - Feature #339: Git Merge Diff
v0.2.27
@@ -415,6 +415,11 @@
instance="com.devoxx.genie.ui.settings.costsettings.LanguageModelCostSettingsConfigurable"
displayName="Token Cost & Context Window"/>
+
+
+
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index abf54537..cd4109b7 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,2 +1,2 @@
-#Fri Nov 08 19:17:06 CET 2024
-version=0.2.27
+#Mon Dec 02 18:40:46 CET 2024
+version=0.2.30
diff --git a/src/main/resources/images/diff_merge.jpg b/src/main/resources/images/diff_merge.jpg
new file mode 100644
index 00000000..f87acb17
Binary files /dev/null and b/src/main/resources/images/diff_merge.jpg differ
diff --git a/src/main/resources/images/simple_diff.jpg b/src/main/resources/images/simple_diff.jpg
new file mode 100644
index 00000000..281738bc
Binary files /dev/null and b/src/main/resources/images/simple_diff.jpg differ