-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE REQUEST] side-by-side diff window with editing before apply AI generated changes #339
Comments
This would be indeed an interesting feature and would make it easier to apply changes |
Asked Claude how to implement this :) I'll explain how to implement a side-by-side diff window with editing capabilities for an IntelliJ plugin, similar to what's shown in the image above. This implementation will allow users to see and edit AI-generated changes before applying them. First, let's create the diff window UI component: public class DiffWindowPanel extends JPanel {
private final Project project;
private final Editor originalEditor;
private final Editor modifiedEditor;
private final EditorTextField acceptButton;
private final String originalContent;
private final String modifiedContent;
public DiffWindowPanel(Project project, String originalContent, String modifiedContent) {
super(new BorderLayout());
this.project = project;
this.originalContent = originalContent;
this.modifiedContent = modifiedContent;
// Create two editors side by side
JPanel editorsPanel = new JPanel(new GridLayout(1, 2));
// Create original editor
originalEditor = createEditor("Original", originalContent, false);
// Create modified editor with editing enabled
modifiedEditor = createEditor("Modified", modifiedContent, true);
editorsPanel.add(originalEditor.getComponent());
editorsPanel.add(modifiedEditor.getComponent());
add(editorsPanel, BorderLayout.CENTER);
add(createButtonPanel(), BorderLayout.SOUTH);
}
private Editor createEditor(String title, String content, boolean editable) {
EditorFactory factory = EditorFactory.getInstance();
Document document = factory.createDocument(content);
Editor editor = factory.createEditor(
document,
project,
FileTypeManager.getInstance().getFileTypeByExtension("java"),
!editable
);
// Configure editor settings
EditorSettings settings = editor.getSettings();
settings.setFoldingOutlineShown(true);
settings.setLineNumbersShown(true);
settings.setLineMarkerAreaShown(true);
settings.setIndentGuidesShown(true);
settings.setGutterIconsShown(true);
return editor;
}
private JPanel createButtonPanel() {
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton acceptButton = new JButton("Accept Changes");
JButton rejectButton = new JButton("Reject");
acceptButton.addActionListener(e -> applyChanges());
rejectButton.addActionListener(e -> reject());
buttonPanel.add(rejectButton);
buttonPanel.add(acceptButton);
return buttonPanel;
}
private void applyChanges() {
ApplicationManager.getApplication().runWriteAction(() -> {
// Get the modified content
String modifiedText = modifiedEditor.getDocument().getText();
// Apply changes to the original file
Document originalDocument = originalEditor.getDocument();
originalDocument.setText(modifiedText);
// Notify success
NotificationUtil.showInfo(project, "Changes applied successfully");
// Close diff window
close();
});
}
private void reject() {
close();
}
private void close() {
// Dispose editors
EditorFactory.getInstance().releaseEditor(originalEditor);
EditorFactory.getInstance().releaseEditor(modifiedEditor);
// Close containing window
Window window = SwingUtilities.getWindowAncestor(this);
if (window != null) {
window.dispose();
}
}
} Now, let's create a service to show the diff window: public class DiffWindowService {
public static void showDiff(Project project, String originalContent, String modifiedContent) {
ApplicationManager.getApplication().invokeLater(() -> {
// Create diff panel
DiffWindowPanel diffPanel = new DiffWindowPanel(project, originalContent, modifiedContent);
// Create dialog
DialogWrapper dialog = new DialogWrapper(project) {
{
init();
setTitle("Review AI Generated Changes");
}
@Override
protected @Nullable JComponent createCenterPanel() {
return diffPanel;
}
@Override
protected Action @NotNull [] createActions() {
// Remove default dialog buttons since we have custom ones
return new Action[0];
}
};
dialog.show();
});
}
} To use this in your plugin when showing AI-generated changes: public class AIChangeAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
if (project == null) return;
// Get current file editor
FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor();
if (!(fileEditor instanceof TextEditor)) return;
Editor editor = ((TextEditor) fileEditor).getEditor();
String originalContent = editor.getDocument().getText();
// Get AI generated changes
String aiModifiedContent = getAIModifiedContent(originalContent);
// Show diff window
DiffWindowService.showDiff(project, originalContent, aiModifiedContent);
}
private String getAIModifiedContent(String originalContent) {
// Implement your AI modification logic here
return "AI modified content";
}
} Key features of this implementation:
To enhance this further, you could:
private void highlightDifferences() {
DiffManager diffManager = DiffManager.getInstance();
SimpleDiffRequest request = new SimpleDiffRequest(
"Review Changes",
DiffContentFactory.getInstance().create(originalContent),
DiffContentFactory.getInstance().create(modifiedContent),
"Original",
"Modified"
);
diffManager.showDiff(project, request);
}
private void setupUndoRedo(Editor editor) {
UndoManager undoManager = UndoManager.getInstance(project);
undoManager.setEditorProvider(new EditorProvider() {
@Override
public Editor getEditor() {
return editor;
}
@Override
public void dispose() {
}
});
}
private JButton createPreviewButton() {
JButton previewButton = new JButton("Preview");
previewButton.addActionListener(e -> {
String modifiedText = modifiedEditor.getDocument().getText();
FileEditorManager.getInstance(project).openTextEditor(
new OpenFileDescriptor(project, originalFile),
true
).getDocument().setText(modifiedText);
});
return previewButton;
} This implementation provides a professional-grade diff window similar to IntelliJ's built-in diff tool, but customized for reviewing and applying AI-generated changes. Users can see exactly what changes are being proposed, edit them if needed, and choose whether to apply or reject them. How would the ai prompt look like to support this feature? I'll help you create an effective prompt structure that supports a diff window feature for reviewing AI-generated code changes. Here's how to structure the prompt: public class DiffPromptBuilder {
private static final String DIFF_TEMPLATE = """
Act as a code editor that proposes changes to the given code. Format your response using XML tags as follows:
<original_code>
// Include the original code here exactly as provided
</original_code>
<modified_code>
// Include your proposed changes here with full context
</modified_code>
<explanation>
// Explain the changes you made and why
</explanation>
Follow these rules:
1. Always include complete, compilable code in both sections
2. Maintain the same package and import statements
3. Keep the same file structure
4. Include inline comments explaining key changes
5. Preserve formatting style of the original code
""";
public static String buildPrompt(String originalCode, String requestedChanges) {
return String.format("""
%s
Original code to modify:
```java
%s
```
Requested changes:
%s
Return the original and modified code in XML tags as specified above.
""", DIFF_TEMPLATE, originalCode, requestedChanges);
}
} Let's implement a service to handle the AI response: public class AIDiffResponseParser {
public static DiffContent parseDiffResponse(String aiResponse) {
try {
// Extract content between XML tags
String originalCode = extractContent(aiResponse, "original_code");
String modifiedCode = extractContent(aiResponse, "modified_code");
String explanation = extractContent(aiResponse, "explanation");
return new DiffContent(originalCode, modifiedCode, explanation);
} catch (Exception e) {
throw new AIResponseParseException("Failed to parse AI response", e);
}
}
private static String extractContent(String response, String tag) {
Pattern pattern = Pattern.compile(
String.format("<%s>(.*?)</%s>", tag, tag),
Pattern.DOTALL
);
Matcher matcher = pattern.matcher(response);
if (matcher.find()) {
return matcher.group(1).trim();
}
throw new AIResponseParseException("Could not find " + tag + " in response");
}
} Here's how to use it in your plugin: public class AICodeModificationService {
private final Project project;
private final LangChainService langChainService; // Your AI service
public void requestCodeModification(PsiFile file, String requestedChanges) {
String originalCode = file.getText();
// Build the prompt
String prompt = DiffPromptBuilder.buildPrompt(originalCode, requestedChanges);
// Get AI response
langChainService.generateResponse(prompt)
.thenAccept(aiResponse -> {
DiffContent diffContent = AIDiffResponseParser.parseDiffResponse(aiResponse);
// Show diff window on EDT
ApplicationManager.getApplication().invokeLater(() -> {
DiffWindowService.showDiff(
project,
diffContent.originalCode(),
diffContent.modifiedCode(),
diffContent.explanation()
);
});
})
.exceptionally(throwable -> {
NotificationUtil.showError(project, "Failed to generate code changes: " +
throwable.getMessage());
return null;
});
}
} Example interaction with the AI: Request:
AI Response: <original_code>
public class UserRegistration {
public void registerUser(String username, String email) {
User user = new User(username, email);
userRepository.save(user);
}
}
</original_code>
<modified_code>
public class UserRegistration {
/**
* Registers a new user with validation
* @throws IllegalArgumentException if username or email is invalid
*/
public void registerUser(String username, String email) {
// Validate username
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("Username cannot be empty");
}
if (username.length() < 3 || username.length() > 50) {
throw new IllegalArgumentException("Username must be between 3 and 50 characters");
}
// Validate email
if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
throw new IllegalArgumentException("Invalid email format");
}
User user = new User(username.trim(), email.trim());
userRepository.save(user);
}
}
</modified_code>
<explanation>
Changes made:
1. Added input validation for username:
- Checks for null or empty values
- Enforces length requirements
2. Added email format validation
3. Added trimming of input values
4. Added JavaDoc with throws clause
5. Improved error messages for better debugging
</explanation> The parser would extract these sections and display them in the diff window, allowing the developer to:
A more complex example with the assistant modifying multiple files would look like: <change_set>
<file path="src/main/java/com/example/UserRegistration.java">
<original_code>
// Original code here
</original_code>
<modified_code>
// Modified code here
</modified_code>
</file>
<file path="src/main/java/com/example/User.java">
<original_code>
// Original code here
</original_code>
<modified_code>
// Modified code here
</modified_code>
</file>
<explanation>
Changes made across multiple files:
1. UserRegistration.java:
- Added input validation
- Improved error handling
2. User.java:
- Added validation annotations
- Added toString method
</explanation>
</change_set> This structured format allows your plugin to:
The key is having a well-defined response format that your plugin can parse and display in the diff window, while maintaining all the context and structure needed for safe code modifications. |
Looking good, didn't follow the above Claude suggestions 😅 AddJavaDocUsingOllama.mp4 |
@stephanj main approach don't apply changes to local files before review and changing |
@stephanj 3-way merge is not necessary, 2-way merge side-by-side diff looks from a cognitive perspective much better |
Here you go, will release v0.3.0 so we can test. Once okay'ed by several people we can push it to the IDEA marketplace. JavadocGitSimple.mp4 |
I've actually tested it with Ollama and Llama 3.2 3B and it actually worked amazingly well 👀 |
Two comments:
|
|
Jetbrains informed me to use DiffManagerEx (External) which did the trick |
So will you release 0.3.1 with DiffManagerEx? |
Done and available on the marketplace, enjoy. Make sure to let us know what works and what doesn't. |
If you develop Cursor.com Composer with Side-by-Side Diff Mode functionality, you will change the market.
Look to following screenshot:
On the right side generation window
On the center side generation represents as side-by-side diff, which allow review changes and even edit it before save and apply to the project
Mental it looks like you say task to junior coder and after generation review it
The text was updated successfully, but these errors were encountered: