diff --git a/pom.xml b/pom.xml index f048854..8a65ff8 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,18 @@ org.netbeans.api:org-netbeans-modules-db-core impl + + org.netbeans.api:org-netbeans-modules-diff + impl + + + org.netbeans.modules:org-netbeans-modules-maven + impl + + + org.netbeans.modules:org-netbeans-modules-maven-embedder + impl + io.github.jeddict.ai @@ -440,7 +452,21 @@ org-openide-text RELEASE230 - + + org.netbeans.api + org-netbeans-modules-diff + RELEASE230 + + + org.netbeans.modules + org-netbeans-modules-maven + RELEASE230 + + + org.netbeans.modules + org-netbeans-modules-maven-embedder + RELEASE230 + UTF-8 diff --git a/src/main/java/io/github/jeddict/ai/actions/OpenChatAction.java b/src/main/java/io/github/jeddict/ai/actions/OpenChatAction.java index 83f370c..33be695 100644 --- a/src/main/java/io/github/jeddict/ai/actions/OpenChatAction.java +++ b/src/main/java/io/github/jeddict/ai/actions/OpenChatAction.java @@ -77,13 +77,18 @@ public void actionPerformed(ActionEvent e) { String currentSelectedText = currenteditor.getSelectedText(); final StyledDocument currentDocument = (StyledDocument) currenteditor.getDocument(); int currentSelectionStartPosition = currenteditor.getSelectionStart(); - FileObject currentfile = NbEditorUtilities.getDataObject(currentDocument).getPrimaryFile(); - if (currentfile != null) { - if (currentSelectedText == null || currentSelectedText.isEmpty()) { - insertAndReformat(currentDocument, content, currentSelectionStartPosition, 0); - } else { - insertAndReformat(currentDocument, content, currentSelectionStartPosition, currentSelectedText.length()); + DataObject currentDO = NbEditorUtilities.getDataObject(currentDocument); + if (currentDO != null) { + FileObject currentfile = currentDO.getPrimaryFile(); + if (currentfile != null) { + if (currentSelectedText == null || currentSelectedText.isEmpty()) { + insertAndReformat(currentDocument, content, currentSelectionStartPosition, 0); + } else { + insertAndReformat(currentDocument, content, currentSelectionStartPosition, currentSelectedText.length()); + } } + } else { + javax.swing.JOptionPane.showMessageDialog(null, "Please select text in the original editor before updating."); } } }); diff --git a/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java b/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java index ac2fe60..b556708 100644 --- a/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java +++ b/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java @@ -58,24 +58,40 @@ import com.github.javaparser.ast.body.MethodDeclaration; import com.sun.source.tree.MethodTree; import com.sun.source.util.TreePathScanner; +import java.awt.Dimension; import java.util.List; import java.util.stream.Collectors; import javax.lang.model.element.Name; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; import java.util.ArrayList; +import javax.swing.BorderFactory; +import javax.swing.JButton; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.Document; +import org.netbeans.api.diff.Diff; +import org.netbeans.api.diff.DiffView; +import org.netbeans.api.diff.StreamSource; +import org.netbeans.modules.diff.builtin.SingleDiffPanel; import org.netbeans.modules.editor.indent.api.Reformat; import org.openide.awt.StatusDisplayer; import org.openide.cookies.EditorCookie; import org.openide.util.Exceptions; - import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.windows.TopComponent; +import javax.swing.filechooser.FileSystemView; +import javax.swing.text.JTextComponent; +import javax.swing.text.StyledDocument; +import org.netbeans.api.editor.EditorRegistry; +import org.netbeans.modules.editor.NbEditorUtilities; + /** * * @author Shiwani Gupta @@ -400,34 +416,36 @@ public int getParseCodeEditor(List fileObjects) { .collect(Collectors.joining(",")) + ")"; methodSignatures.put(signature, editorPane.getText()); } catch (Exception e) { - CompilationUnit edCu = StaticJavaParser.parse("class Tmp {" + editorPane.getText() + "}"); - List edMethods = edCu.findAll(MethodDeclaration.class); - for (MethodDeclaration edMethod : edMethods) { - String signature = edMethod.getNameAsString() + "(" - + edMethod.getParameters().stream() - .map(param -> param.getType().asString()) - .collect(Collectors.joining(",")) + ")"; - methodSignatures.put(signature, edMethod.toString()); + try { + CompilationUnit edCu = StaticJavaParser.parse("class Tmp {" + editorPane.getText() + "}"); + List edMethods = edCu.findAll(MethodDeclaration.class); + for (MethodDeclaration edMethod : edMethods) { + String signature = edMethod.getNameAsString() + "(" + + edMethod.getParameters().stream() + .map(param -> param.getType().asString()) + .collect(Collectors.joining(",")) + ")"; + methodSignatures.put(signature, edMethod.toString()); + } + } catch (Exception e1) { + CompilationUnit edCu = StaticJavaParser.parse(editorPane.getText()); + if (edCu.getTypes().isNonEmpty()) { + methodSignatures.put(edCu.getType(0).getNameAsString(), edCu.toString()); + } } } return methodSignatures; }); try { + int menuCreationCount = 0; for (String signature : fileMethodSignatures) { - if (cachedMethodSignatures.get(signature) != null) { - JMenuItem methodItem = new JMenuItem("Update " + signature + " in " + fileObject.getName()); - methodItem.addActionListener(e -> { - SwingUtilities.invokeLater(() -> { - updateMethodInSource(fileObject, signature, cachedMethodSignatures.get(signature)); - }); - }); - if (menuItems.get(editorPane) == null) { - menuItems.put(editorPane, new ArrayList<>()); - } - menuItems.get(editorPane).add(methodItem); + if (createEditorPaneMenus(fileObject, signature, editorPane, cachedMethodSignatures)) { + menuCreationCount++; } } + if (menuCreationCount == 0) { + createEditorPaneMenus(fileObject, fileObject.getName(), editorPane, cachedMethodSignatures); + } } catch (Exception e) { System.out.println("Error parsing single method declaration from editor content: " + e.getMessage()); } @@ -438,6 +456,36 @@ public int getParseCodeEditor(List fileObjects) { System.out.println("Error parsing file: " + fileObject.getName() + " - " + e.getMessage()); } } + + for (int i = 0; i < parentPanel.getComponentCount(); i++) { + if (parentPanel.getComponent(i) instanceof JEditorPane editorPane) { + if (menuItems.get(editorPane) == null) { + menuItems.put(editorPane, new ArrayList<>()); + } + if (editorPane.getEditorKit().getContentType().equals("text/x-java")) { + JMenuItem diffMethodItem = new JMenuItem("Diff with Selected Snippet"); + diffMethodItem.addActionListener(e -> { + SwingUtilities.invokeLater(() -> { + JTextComponent currenteditor = EditorRegistry.lastFocusedComponent(); + String currentSelectedText = currenteditor.getSelectedText(); + final StyledDocument currentDocument = (StyledDocument) currenteditor.getDocument(); + DataObject currentDO = NbEditorUtilities.getDataObject(currentDocument); + if (currentDO != null) { + FileObject focusedfile = currentDO.getPrimaryFile(); + if (focusedfile != null && !currentSelectedText.trim().isEmpty()) { + diffActionWithSelected(currentSelectedText, focusedfile, editorPane); + } else { + javax.swing.JOptionPane.showMessageDialog(null, "Please select text in the source editor."); + } + } else { + javax.swing.JOptionPane.showMessageDialog(null, "Please select text in the source editor."); + } + }); + }); + menuItems.get(editorPane).add(diffMethodItem); + } + } + } for (Map.Entry> entry : menuItems.entrySet()) { if (entry.getValue().size() > 3) { @@ -455,6 +503,145 @@ public int getParseCodeEditor(List fileObjects) { } return 0; } + + private boolean createEditorPaneMenus(FileObject fileObject, String signature, JEditorPane editorPane, Map cachedMethodSignatures) { + boolean classSignature = fileObject.getName().equals(signature); + if (cachedMethodSignatures.get(signature) != null) { + if (menuItems.get(editorPane) == null) { + menuItems.put(editorPane, new ArrayList<>()); + } + String menuSubText = (classSignature ? "" : (signature + " in ")); + JMenuItem updateMethodItem = new JMenuItem("Update " + menuSubText + fileObject.getName()); + updateMethodItem.addActionListener(e -> { + SwingUtilities.invokeLater(() -> { + if (signature.equals(fileObject.getName())) { + updateFullSourceInFile(fileObject, cachedMethodSignatures.get(signature)); + } else { + updateMethodInSource(fileObject, signature, cachedMethodSignatures.get(signature)); + } + }); + }); + menuItems.get(editorPane).add(updateMethodItem); + + JMenuItem diffMethodItem = new JMenuItem("Diff " + menuSubText + fileObject.getName()); + diffMethodItem.addActionListener(e -> { + SwingUtilities.invokeLater(() -> { + diffAction(classSignature, fileObject, signature, editorPane, cachedMethodSignatures); + }); + }); + menuItems.get(editorPane).add(diffMethodItem); + return true; + } + return false; + } + + + private void diffAction(boolean classSignature, FileObject fileObject, String signature, JEditorPane editorPane, Map cachedMethodSignatures) { + try { + String origin; + if (signature.equals(fileObject.getName())) { + origin = fileObject.asText(); + } else { + origin = findMethodSourceInFileObject(fileObject, signature); + } + JPanel editorParent = (JPanel) editorPane.getParent(); + JPanel diffPanel = new JPanel(); + diffPanel.setLayout(new BorderLayout()); + + if (classSignature) { + SingleDiffPanel sdp = new SingleDiffPanel(createTempFileObject(fileObject.getName(), cachedMethodSignatures.get(signature)), fileObject, null); + diffPanel.add(sdp, BorderLayout.CENTER); + } else { + StreamSource ss1 = StreamSource.createSource( + "Source " + signature, + fileObject.getNameExt() + (classSignature ? "" : ("#" + signature)), + "text/java", + new StringReader(origin.trim()) + ); + StreamSource ss2 = StreamSource.createSource( + "Target " + signature, + "AI Generated " + signature, + "text/java", + new StringReader(cachedMethodSignatures.get(signature)) + ); + DiffView diffView = Diff.getDefault().createDiff(ss2, ss1); + diffPanel.add(diffView.getComponent(), BorderLayout.CENTER); + } + + JButton closeButton = new JButton("Hide Diff View"); + closeButton.setPreferredSize(new Dimension(30, 30)); + closeButton.setContentAreaFilled(false); + + closeButton.addActionListener(e1 -> { + diffPanel.setVisible(false); + editorPane.setVisible(true); + editorParent.revalidate(); + editorParent.repaint(); + }); + diffPanel.add(closeButton, BorderLayout.NORTH); + int index = editorParent.getComponentZOrder(editorPane); + editorParent.add(diffPanel, index + 1); + editorPane.setVisible(false); + editorParent.revalidate(); + editorParent.repaint(); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + + private void diffActionWithSelected(String origin, FileObject fileObject, JEditorPane editorPane) { + try { + JPanel editorParent = (JPanel) editorPane.getParent(); + JPanel diffPanel = new JPanel(); + diffPanel.setLayout(new BorderLayout()); + + StreamSource ss1 = StreamSource.createSource( + "Source", + fileObject.getNameExt(), + "text/java", + new StringReader(origin.trim()) + ); + StreamSource ss2 = StreamSource.createSource( + "Target", + "AI Generated", + "text/java", + new StringReader(editorPane.getText()) + ); + DiffView diffView = Diff.getDefault().createDiff(ss2, ss1); + diffPanel.add(diffView.getComponent(), BorderLayout.CENTER); + + JButton closeButton = new JButton("Hide Diff View"); + closeButton.setPreferredSize(new Dimension(30, 30)); + closeButton.setContentAreaFilled(false); + + closeButton.addActionListener(e1 -> { + diffPanel.setVisible(false); + editorPane.setVisible(true); + editorParent.revalidate(); + editorParent.repaint(); + }); + diffPanel.add(closeButton, BorderLayout.NORTH); + int index = editorParent.getComponentZOrder(editorPane); + editorParent.add(diffPanel, index + 1); + editorPane.setVisible(false); + editorParent.revalidate(); + editorParent.repaint(); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + public FileObject createTempFileObject(String name, String content) throws IOException { + File tempFile = File.createTempFile("GenAI-"+name, ".java"); + tempFile.deleteOnExit(); + try (FileWriter writer = new FileWriter(tempFile)) { + writer.write(content); + } + FileObject fileObject = FileUtil.toFileObject(tempFile); + return fileObject; + } + private void updateMethodInSource(FileObject fileObject, String sourceMethodSignature, String methodContent) { JavaSource javaSource = JavaSource.forFileObject(fileObject); try { @@ -473,7 +660,7 @@ public Void visitMethod(MethodTree methodTree, Void v) { if (targetMethodSignature.equals(sourceMethodSignature)) { long startPos = copy.getTrees().getSourcePositions().getStartPosition(copy.getCompilationUnit(), methodTree); long endPos = copy.getTrees().getSourcePositions().getEndPosition(copy.getCompilationUnit(), methodTree); - + try { if (copy.getDocument() == null) { openFileInEditor(fileObject); @@ -492,16 +679,74 @@ public Void visitMethod(MethodTree methodTree, Void v) { System.out.println("Error updating method " + sourceMethodSignature + " in file " + fileObject.getName() + ": " + e.getMessage()); } } - -public void openFileInEditor(FileObject fileObject) { - try { - // Get the DataObject associated with the FileObject - DataObject dataObject = DataObject.find(fileObject); - - // Lookup for the EditorCookie from the DataObject + private void updateFullSourceInFile(FileObject fileObject, String newSourceContent) { + JavaSource javaSource = JavaSource.forFileObject(fileObject); + try { + javaSource.runModificationTask(copy -> { + copy.toPhase(JavaSource.Phase.RESOLVED); + Document document = copy.getDocument(); + + try { + // Open the file in the editor if it is not already open + if (document == null) { + openFileInEditor(fileObject); + document = copy.getDocument(); // Re-fetch the document after opening + } + + // Replace the entire document content with the new source content + document.remove(0, document.getLength()); + document.insertString(0, newSourceContent, null); + + } catch (BadLocationException | IOException ex) { + Exceptions.printStackTrace(ex); + } + }).commit(); + } catch (IOException e) { + System.out.println("Error updating source in file " + fileObject.getName() + ": " + e.getMessage()); + } + } + + private String findMethodSourceInFileObject(FileObject fileObject, String sourceMethodSignature) { + JavaSource javaSource = JavaSource.forFileObject(fileObject); + StringBuilder methodSource = new StringBuilder(); + + try { + javaSource.runModificationTask(copy -> { + copy.toPhase(JavaSource.Phase.RESOLVED); + new TreePathScanner() { + @Override + public Void visitMethod(MethodTree methodTree, Void v) { + Name name = methodTree.getName(); + String targetMethodSignature = name.toString() + "(" + + methodTree.getParameters().stream() + .map(param -> param.getType().toString()) + .collect(Collectors.joining(",")) + ")"; + + // Check if the signatures match + if (targetMethodSignature.equals(sourceMethodSignature)) { + // Construct the method source code + methodSource.append(methodTree.toString()); + } + return super.visitMethod(methodTree, v); + } + }.scan(copy.getCompilationUnit(), null); + }).commit(); + } catch (IOException e) { + System.out.println("Error finding method " + sourceMethodSignature + " in file " + fileObject.getName() + ": " + e.getMessage()); + } + + return methodSource.toString(); // Return the method source code + } + + public void openFileInEditor(FileObject fileObject) { + try { + // Get the DataObject associated with the FileObject + DataObject dataObject = DataObject.find(fileObject); + + // Lookup for the EditorCookie from the DataObject EditorCookie editorCookie = dataObject.getLookup().lookup(EditorCookie.class); - + if (editorCookie != null) { // Open the file in the editor editorCookie.open(); @@ -509,28 +754,29 @@ public void openFileInEditor(FileObject fileObject) { } else { StatusDisplayer.getDefault().setStatusText("Failed to find EditorCookie for file: " + fileObject.getNameExt()); } - } catch (DataObjectNotFoundException e) { - e.printStackTrace(); - } -} -private void insertAndReformat(Document document, String content, int startPosition, int lengthToRemove) { - try { - if (lengthToRemove > 0) { - document.remove(startPosition, lengthToRemove); + } catch (DataObjectNotFoundException e) { + e.printStackTrace(); } - document.insertString(startPosition, content, null); - Reformat reformat = Reformat.get(document); - reformat.lock(); + } + + private void insertAndReformat(Document document, String content, int startPosition, int lengthToRemove) { try { - reformat.reformat(startPosition, startPosition + content.length()); - } finally { - reformat.unlock(); + if (lengthToRemove > 0) { + document.remove(startPosition, lengthToRemove); + } + document.insertString(startPosition, content, null); + Reformat reformat = Reformat.get(document); + reformat.lock(); + try { + reformat.reformat(startPosition, startPosition + content.length()); + } finally { + reformat.unlock(); + } + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); } - } catch (BadLocationException ex) { - Exceptions.printStackTrace(ex); } -} - + public int getAllEditorCount() { int count = 0; for (int i = 0; i < parentPanel.getComponentCount(); i++) {