Skip to content

Commit

Permalink
Merge pull request #116 from devoxx/issue-33
Browse files Browse the repository at this point in the history
Feat #33 : Search files by entering filter text
  • Loading branch information
stephanj authored Jun 25, 2024
2 parents f61b3cc + 6a594a2 commit b875a01
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 88 deletions.
48 changes: 27 additions & 21 deletions src/main/java/com/devoxx/genie/service/MessageCreationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import static com.devoxx.genie.action.AddSnippetAction.SELECTED_TEXT_KEY;

Expand Down Expand Up @@ -54,29 +55,34 @@ public static MessageCreationService getInstance() {
* @param files the files
* @return the user prompt with context
*/
public @NotNull String createUserPromptWithContext(Project project,
String userPrompt,
@NotNull List<VirtualFile> files) {
StringBuilder userPromptContext = new StringBuilder();
FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
files.forEach(file -> ApplicationManager.getApplication().runReadAction(() -> {
if (file.getFileType().getName().equals("UNKNOWN")) {
userPromptContext.append("Filename: ").append(file.getName()).append("\n");
userPromptContext.append("Code Snippet: ").append(file.getUserData(SELECTED_TEXT_KEY)).append("\n");
} else {
Document document = fileDocumentManager.getDocument(file);
if (document != null) {
userPromptContext.append("Filename: ").append(file.getName()).append("\n");
String content = document.getText();
userPromptContext.append(content).append("\n");
} else {
NotificationUtil.sendNotification(project, "Error reading file: " + file.getName());
}
public @NotNull CompletableFuture<String> createUserPromptWithContextAsync(Project project,
String userPrompt,
@NotNull List<VirtualFile> files) {
return CompletableFuture.supplyAsync(() -> {
StringBuilder userPromptContext = new StringBuilder();
FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();

for (VirtualFile file : files) {
ApplicationManager.getApplication().runReadAction(() -> {
if (file.getFileType().getName().equals("UNKNOWN")) {
userPromptContext.append("Filename: ").append(file.getName()).append("\n");
userPromptContext.append("Code Snippet: ").append(file.getUserData(SELECTED_TEXT_KEY)).append("\n");
} else {
Document document = fileDocumentManager.getDocument(file);
if (document != null) {
userPromptContext.append("Filename: ").append(file.getName()).append("\n");
String content = document.getText();
userPromptContext.append(content).append("\n");
} else {
NotificationUtil.sendNotification(project, "Error reading file: " + file.getName());
}
}
});
}
}));

userPromptContext.append(userPrompt);
return userPromptContext.toString();
userPromptContext.append(userPrompt);
return userPromptContext.toString();
});
}

/**
Expand Down
13 changes: 0 additions & 13 deletions src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,13 @@ public EditorFileButtonManager(Project project, JButton addFileBtn) {
}

private void handleFileOpenClose() {
if (fileEditorManager.getSelectedFiles().length == 0) {
addFileBtn.setEnabled(false);
addFileBtn.setToolTipText("No files open in the editor");
}

ApplicationManager.getApplication().getMessageBus().connect().subscribe(
FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
@Override
public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
addFileBtn.setEnabled(true);
addFileBtn.setToolTipText("Select file(s) for prompt context");
}

@Override
public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
if (fileEditorManager.getSelectedFiles().length == 0) {
addFileBtn.setEnabled(false);
addFileBtn.setToolTipText("No files open in the editor");
}
}
});
}

Expand Down
16 changes: 10 additions & 6 deletions src/main/java/com/devoxx/genie/ui/panel/ActionButtonsPanel.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.devoxx.genie.ui.panel;

import com.devoxx.genie.chatmodel.ChatModelProvider;
import com.devoxx.genie.error.ErrorHandler;
import com.devoxx.genie.model.Constant;
import com.devoxx.genie.model.request.ChatMessageContext;
import com.devoxx.genie.model.request.EditorInfo;
Expand Down Expand Up @@ -123,10 +124,11 @@ private void createSearchButton(@NotNull JPanel panel,
private void selectFilesForPromptContext(ActionEvent e) {
JBPopup popup = JBPopupFactory.getInstance()
.createComponentPopupBuilder(FileSelectionPanelFactory.createPanel(project), null)
.setTitle("Double-Click To Add To Prompt Context")
.setTitle("Filter and Double-Click To Add To Prompt Context")
.setRequestFocus(true)
.setResizable(true)
.setMovable(false)
.setMinSize(new Dimension(300, 350))
.createPopup();

if (addFileBtn.isShowing()) {
Expand Down Expand Up @@ -361,13 +363,15 @@ private boolean isWebSearchEnabled() {
private void addSelectedFiles(@NotNull ChatMessageContext chatMessageContext,
String userPrompt,
List<VirtualFile> files) {
chatMessageContext.setEditorInfo(new EditorInfo(files));

String userPromptWithContext = MessageCreationService
.getInstance()
.createUserPromptWithContext(chatMessageContext.getProject(), userPrompt, files);
chatMessageContext.setEditorInfo(new EditorInfo(files));

chatMessageContext.setContext(userPromptWithContext);
MessageCreationService.getInstance().createUserPromptWithContextAsync(project, userPrompt, files)
.thenAccept(chatMessageContext::setContext)
.exceptionally(ex -> {
ErrorHandler.handleError(project, ex);
return null;
});
}

/**
Expand Down
153 changes: 105 additions & 48 deletions src/main/java/com/devoxx/genie/ui/panel/FileSelectionPanelFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,100 +2,157 @@

import com.devoxx.genie.service.FileListManager;
import com.devoxx.genie.ui.renderer.FileListCellRenderer;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.ide.util.gotoByName.GotoFileModel;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTextField;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
* The factory class for creating a panel with a text field for filtering files and a list of open files
*/
public class FileSelectionPanelFactory implements DumbAware {

private static final int DOUBLE_CLICK = 2;
private static final int DEBOUNCE_DELAY = 300; // milliseconds

private FileSelectionPanelFactory() {
}

/**
* Creates a panel with a text field for filtering files and a list of files
*
* @param project The current project
* @return The panel
*/
public static @NotNull JPanel createPanel(Project project) {
JBList<VirtualFile> resultList = createResultList(project);
DefaultListModel<VirtualFile> listModel = new DefaultListModel<>();
JBList<VirtualFile> resultList = createResultList(project, listModel);
JBTextField filterField = createFilterField(project, listModel, resultList);

JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setMaximumSize(new Dimension(300, 60));
mainPanel.setMaximumSize(new Dimension(400, 400));

mainPanel.add(filterField, BorderLayout.NORTH);
JBScrollPane jScrollPane = new JBScrollPane(resultList);
jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
mainPanel.add(jScrollPane, BorderLayout.CENTER);

return mainPanel;
}

/**
* Creates a list of open files and files from the FilenameIndex search
*
* @param project The current project
* @return The list of files
*/
private static @NotNull JBList<VirtualFile> createResultList(Project project) {
DefaultListModel<VirtualFile> listModel = new DefaultListModel<>();
private static @NotNull JBList<VirtualFile> createResultList(Project project,
DefaultListModel<VirtualFile> listModel) {
JBList<VirtualFile> resultList = new JBList<>(listModel);
resultList.setCellRenderer(new FileListCellRenderer(project));

addMouseListenerToResultList(resultList);
populateListModelWithOpenFiles(project, listModel);

return resultList;
}

/**
* Adds a mouse listener to the result list to open the selected file
*
* @param resultList The list of files
*/
private static @NotNull JBTextField createFilterField(Project project,
DefaultListModel<VirtualFile> listModel,
JBList<VirtualFile> resultList) {
JBTextField filterField = new JBTextField();
filterField.getEmptyText().setText("Type to search for files...");

AtomicReference<Timer> debounceTimer = new AtomicReference<>(new Timer(DEBOUNCE_DELAY, null));
debounceTimer.get().setRepeats(false);

filterField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
debounceSearch(project, filterField, listModel, resultList, debounceTimer);
}

@Override
public void removeUpdate(DocumentEvent e) {
debounceSearch(project, filterField, listModel, resultList, debounceTimer);
}

@Override
public void changedUpdate(DocumentEvent e) {
debounceSearch(project, filterField, listModel, resultList, debounceTimer);
}
});

return filterField;
}

private static void debounceSearch(Project project,
JBTextField filterField,
DefaultListModel<VirtualFile> listModel,
JBList<VirtualFile> resultList,
@NotNull AtomicReference<Timer> debounceTimer) {
debounceTimer.get().stop();
debounceTimer.set(new Timer(DEBOUNCE_DELAY, e -> searchFiles(project, filterField.getText(), listModel, resultList)));
debounceTimer.get().setRepeats(false);
debounceTimer.get().start();
}

private static void searchFiles(Project project, String searchText, DefaultListModel<VirtualFile> listModel, JBList<VirtualFile> resultList) {
new Task.Backgroundable(project, "Searching Files", true) {
private final List<VirtualFile> foundFiles = new ArrayList<>();

@Override
public void run(@NotNull ProgressIndicator indicator) {
ReadAction.run(() -> {
GotoFileModel model = new GotoFileModel(project);
String[] names = model.getNames(false);
for (String name : names) {
if (indicator.isCanceled()) return;
if (name.toLowerCase().contains(searchText.toLowerCase())) {
Object[] objects = model.getElementsByName(name, false, name);
for (Object obj : objects) {
if (obj instanceof PsiFile) {
VirtualFile virtualFile = ((PsiFile) obj).getVirtualFile();
if (virtualFile != null) {
foundFiles.add(virtualFile);
}
}
}
}
}
});
}

@Override
public void onSuccess() {
ApplicationManager.getApplication().invokeLater(() -> {
listModel.clear();
for (VirtualFile file : foundFiles) {
listModel.addElement(file);
}
resultList.updateUI();
});
}
}.queue();
}

private static void addMouseListenerToResultList(@NotNull JBList<VirtualFile> resultList) {
resultList.addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == DOUBLE_CLICK) {
openSelectedFile(resultList);
addSelectedFile(resultList);
}
}
});
}

/**
* Opens the selected file in the editor
*
* @param resultList The list of files
*/
private static void openSelectedFile(@NotNull JBList<VirtualFile> resultList) {
private static void addSelectedFile(@NotNull JBList<VirtualFile> resultList) {
VirtualFile selectedFile = resultList.getSelectedValue();
FileListManager.getInstance().addFile(selectedFile);
}

/**
* Populates the list model with the open files
*
* @param project The current project
* @param listModel The list model
*/
private static void populateListModelWithOpenFiles(Project project,
DefaultListModel<VirtualFile> listModel) {
for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
listModel.addElement(file);
if (selectedFile != null) {
FileListManager.getInstance().addFile(selectedFile);
}
}
}

0 comments on commit b875a01

Please sign in to comment.