added new options to file search
This commit is contained in:
parent
474f1fd38f
commit
461bb74503
@ -656,7 +656,7 @@ public class FileOperations {
|
||||
* Search files by filename pattern and/or content text.
|
||||
* If both are provided, both must match.
|
||||
*/
|
||||
public static void search(File directory, String filenamePattern, String contentText, boolean recursive, boolean searchArchives, SearchCallback callback) throws IOException {
|
||||
public static void search(File directory, String filenamePattern, String contentText, boolean recursive, boolean searchArchives, boolean wholeWord, boolean caseSensitive, SearchCallback callback) throws IOException {
|
||||
Pattern filenameRegex = null;
|
||||
String filenameLower = null;
|
||||
if (filenamePattern != null && !filenamePattern.isEmpty()) {
|
||||
@ -672,7 +672,13 @@ public class FileOperations {
|
||||
|
||||
Pattern contentPattern = null;
|
||||
if (contentText != null && !contentText.isEmpty()) {
|
||||
contentPattern = Pattern.compile(Pattern.quote(contentText), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
||||
String quote = Pattern.quote(contentText);
|
||||
String regex = wholeWord ? "(?<!\\w)" + quote + "(?!\\w)" : quote;
|
||||
int flags = Pattern.UNICODE_CASE;
|
||||
if (!caseSensitive) {
|
||||
flags |= Pattern.CASE_INSENSITIVE;
|
||||
}
|
||||
contentPattern = Pattern.compile(regex, flags);
|
||||
}
|
||||
|
||||
searchRecursive(directory.toPath(), filenameLower, filenameRegex, contentPattern, recursive, searchArchives, callback);
|
||||
|
||||
@ -11,6 +11,8 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* Internal file editor/viewer
|
||||
@ -49,12 +51,22 @@ public class FileEditor extends JFrame {
|
||||
// Search support
|
||||
private JPanel searchPanel;
|
||||
private JTextField searchField;
|
||||
private JCheckBox wholeWordCheckBox;
|
||||
private JCheckBox caseSensitiveCheckBox;
|
||||
private JLabel searchStatusLabel;
|
||||
private static String lastSearchValue = "";
|
||||
private static boolean lastWholeWord = false;
|
||||
private static boolean lastCaseSensitive = false;
|
||||
|
||||
public static void setLastSearchValue(String value) {
|
||||
lastSearchValue = value;
|
||||
}
|
||||
|
||||
public static void setLastSearchOptions(String value, boolean wholeWord, boolean caseSensitive) {
|
||||
lastSearchValue = value;
|
||||
lastWholeWord = wholeWord;
|
||||
lastCaseSensitive = caseSensitive;
|
||||
}
|
||||
|
||||
public FileEditor(Window parent, File file, AppConfig config, boolean readOnly) {
|
||||
this(parent, file, null, config, readOnly);
|
||||
@ -104,6 +116,9 @@ public class FileEditor extends JFrame {
|
||||
searchField = new JTextField(20);
|
||||
searchField.addActionListener(e -> findNext());
|
||||
|
||||
wholeWordCheckBox = new JCheckBox("Whole word");
|
||||
caseSensitiveCheckBox = new JCheckBox("Case sensitive");
|
||||
|
||||
JButton nextBtn = new JButton("Next");
|
||||
nextBtn.addActionListener(e -> findNext());
|
||||
|
||||
@ -118,6 +133,8 @@ public class FileEditor extends JFrame {
|
||||
|
||||
searchPanel.add(new JLabel("Search:"));
|
||||
searchPanel.add(searchField);
|
||||
searchPanel.add(wholeWordCheckBox);
|
||||
searchPanel.add(caseSensitiveCheckBox);
|
||||
searchPanel.add(nextBtn);
|
||||
searchPanel.add(prevBtn);
|
||||
searchPanel.add(closeBtn);
|
||||
@ -128,6 +145,8 @@ public class FileEditor extends JFrame {
|
||||
|
||||
private void showSearchPanel(boolean focusField) {
|
||||
searchPanel.setVisible(true);
|
||||
wholeWordCheckBox.setSelected(lastWholeWord);
|
||||
caseSensitiveCheckBox.setSelected(lastCaseSensitive);
|
||||
String selection = textArea.getSelectedText();
|
||||
if (selection != null && !selection.isEmpty() && !selection.contains("\n")) {
|
||||
searchField.setText(selection);
|
||||
@ -171,7 +190,10 @@ public class FileEditor extends JFrame {
|
||||
String text = searchField.getText();
|
||||
if (text.isEmpty()) text = lastSearchValue;
|
||||
if (text == null || text.isEmpty()) return;
|
||||
|
||||
lastSearchValue = text;
|
||||
lastWholeWord = wholeWordCheckBox.isSelected();
|
||||
lastCaseSensitive = caseSensitiveCheckBox.isSelected();
|
||||
updateSearchHistory(text);
|
||||
|
||||
if (searchField.getText().isEmpty()) searchField.setText(text);
|
||||
@ -181,64 +203,132 @@ public class FileEditor extends JFrame {
|
||||
|
||||
// If we have a selection that matches, start after it
|
||||
if (textArea.getSelectionEnd() > textArea.getSelectionStart()) {
|
||||
if (content.substring(textArea.getSelectionStart(), textArea.getSelectionEnd()).equalsIgnoreCase(text)) {
|
||||
String selected = content.substring(textArea.getSelectionStart(), textArea.getSelectionEnd());
|
||||
boolean match;
|
||||
if (lastCaseSensitive) {
|
||||
match = selected.equals(text);
|
||||
} else {
|
||||
match = selected.equalsIgnoreCase(text);
|
||||
}
|
||||
if (match) {
|
||||
start = textArea.getSelectionEnd();
|
||||
}
|
||||
}
|
||||
|
||||
int idx = content.toLowerCase().indexOf(text.toLowerCase(), start);
|
||||
if (idx == -1) {
|
||||
// wrap around
|
||||
idx = content.toLowerCase().indexOf(text.toLowerCase(), 0);
|
||||
String quote = Pattern.quote(text);
|
||||
String regex = lastWholeWord ? "(?<!\\w)" + quote + "(?!\\w)" : quote;
|
||||
int flags = 0;
|
||||
if (!lastCaseSensitive) {
|
||||
flags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
|
||||
}
|
||||
Pattern pattern = Pattern.compile(regex, flags);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
|
||||
if (idx != -1) {
|
||||
textArea.setSelectionStart(idx);
|
||||
textArea.setSelectionEnd(idx + text.length());
|
||||
textArea.getCaret().setSelectionVisible(true);
|
||||
try {
|
||||
Rectangle rect = textArea.modelToView2D(idx).getBounds();
|
||||
textArea.scrollRectToVisible(rect);
|
||||
} catch (Exception ignore) {}
|
||||
searchStatusLabel.setText("");
|
||||
if (matcher.find(start)) {
|
||||
selectSearchResult(matcher.start(), matcher.end());
|
||||
} else {
|
||||
searchStatusLabel.setText("Not found");
|
||||
// wrap around
|
||||
if (matcher.find(0)) {
|
||||
int res = JOptionPane.showConfirmDialog(this,
|
||||
"Reached the end of the file. Start searching from the beginning?",
|
||||
"Search",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
if (res == JOptionPane.YES_OPTION) {
|
||||
selectSearchResult(matcher.start(), matcher.end());
|
||||
}
|
||||
} else {
|
||||
showNotFoundDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showNotFoundDialog() {
|
||||
JOptionPane pane = new JOptionPane("Text not found", JOptionPane.INFORMATION_MESSAGE);
|
||||
JDialog dialog = pane.createDialog(this, "Search");
|
||||
|
||||
// Escape to close
|
||||
dialog.getRootPane().registerKeyboardAction(e -> {
|
||||
dialog.dispose();
|
||||
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
private void findPrevious() {
|
||||
String text = searchField.getText();
|
||||
if (text.isEmpty()) text = lastSearchValue;
|
||||
if (text == null || text.isEmpty()) return;
|
||||
|
||||
lastSearchValue = text;
|
||||
lastWholeWord = wholeWordCheckBox.isSelected();
|
||||
lastCaseSensitive = caseSensitiveCheckBox.isSelected();
|
||||
updateSearchHistory(text);
|
||||
|
||||
if (searchField.getText().isEmpty()) searchField.setText(text);
|
||||
|
||||
String content = textArea.getText();
|
||||
int start = textArea.getSelectionStart() - 1;
|
||||
if (start < 0) start = content.length() - 1;
|
||||
int start = textArea.getSelectionStart();
|
||||
if (start < 0) start = content.length();
|
||||
|
||||
int idx = content.toLowerCase().lastIndexOf(text.toLowerCase(), start);
|
||||
if (idx == -1) {
|
||||
// wrap around
|
||||
idx = content.toLowerCase().lastIndexOf(text.toLowerCase(), content.length() - 1);
|
||||
String quote = Pattern.quote(text);
|
||||
String regex = lastWholeWord ? "(?<!\\w)" + quote + "(?!\\w)" : quote;
|
||||
int flags = 0;
|
||||
if (!lastCaseSensitive) {
|
||||
flags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
|
||||
}
|
||||
Pattern pattern = Pattern.compile(regex, flags);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
|
||||
int lastMatchStart = -1;
|
||||
int lastMatchEnd = -1;
|
||||
int searchIdx = 0;
|
||||
while (matcher.find(searchIdx)) {
|
||||
if (matcher.start() < start) {
|
||||
lastMatchStart = matcher.start();
|
||||
lastMatchEnd = matcher.end();
|
||||
searchIdx = matcher.start() + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx != -1) {
|
||||
textArea.setSelectionStart(idx);
|
||||
textArea.setSelectionEnd(idx + text.length());
|
||||
textArea.getCaret().setSelectionVisible(true);
|
||||
try {
|
||||
Rectangle rect = textArea.modelToView2D(idx).getBounds();
|
||||
textArea.scrollRectToVisible(rect);
|
||||
} catch (Exception ignore) {}
|
||||
searchStatusLabel.setText("");
|
||||
if (lastMatchStart != -1) {
|
||||
selectSearchResult(lastMatchStart, lastMatchEnd);
|
||||
} else {
|
||||
searchStatusLabel.setText("Not found");
|
||||
// wrap around to the very last match in the file
|
||||
searchIdx = 0;
|
||||
while (matcher.find(searchIdx)) {
|
||||
lastMatchStart = matcher.start();
|
||||
lastMatchEnd = matcher.end();
|
||||
searchIdx = matcher.start() + 1;
|
||||
}
|
||||
if (lastMatchStart != -1) {
|
||||
int res = JOptionPane.showConfirmDialog(this,
|
||||
"Reached the beginning of the file. Continue from the end?",
|
||||
"Search",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
if (res == JOptionPane.YES_OPTION) {
|
||||
selectSearchResult(lastMatchStart, lastMatchEnd);
|
||||
}
|
||||
} else {
|
||||
showNotFoundDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void selectSearchResult(int start, int end) {
|
||||
textArea.setSelectionStart(start);
|
||||
textArea.setSelectionEnd(end);
|
||||
textArea.getCaret().setSelectionVisible(true);
|
||||
try {
|
||||
Rectangle rect = textArea.modelToView2D(start).getBounds();
|
||||
textArea.scrollRectToVisible(rect);
|
||||
} catch (Exception ignore) {}
|
||||
searchStatusLabel.setText("");
|
||||
}
|
||||
|
||||
private void updateSearchHistory(String text) {
|
||||
if (config == null || text == null || text.isEmpty()) return;
|
||||
java.util.List<String> hist = new java.util.ArrayList<>(config.getContentSearchHistory());
|
||||
|
||||
@ -23,6 +23,8 @@ public class SearchDialog extends JDialog {
|
||||
private JCheckBox recursiveCheckBox;
|
||||
private JCheckBox contentSearchCheckBox;
|
||||
private JCheckBox archiveSearchCheckBox;
|
||||
private JCheckBox wholeWordCheckBox;
|
||||
private JCheckBox caseSensitiveCheckBox;
|
||||
private JTable resultsTable;
|
||||
private ResultsTableModel tableModel;
|
||||
private JButton searchButton;
|
||||
@ -175,17 +177,30 @@ public class SearchDialog extends JDialog {
|
||||
contentPatternCombo.setToolTipText("Text to search inside files");
|
||||
searchPanel.add(contentPatternCombo, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 3;
|
||||
gbc.gridwidth = 2;
|
||||
contentSearchCheckBox = new JCheckBox("Search inside file contents", false);
|
||||
searchPanel.add(contentSearchCheckBox, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
gbc.gridwidth = 1;
|
||||
wholeWordCheckBox = new JCheckBox("Whole word only", false);
|
||||
searchPanel.add(wholeWordCheckBox, gbc);
|
||||
|
||||
gbc.gridx = 1;
|
||||
caseSensitiveCheckBox = new JCheckBox("Case sensitive", false);
|
||||
searchPanel.add(caseSensitiveCheckBox, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 5;
|
||||
gbc.gridwidth = 2;
|
||||
archiveSearchCheckBox = new JCheckBox("Search inside archives", false);
|
||||
archiveSearchCheckBox.setMnemonic(KeyEvent.VK_R);
|
||||
searchPanel.add(archiveSearchCheckBox, gbc);
|
||||
|
||||
gbc.gridy = 5;
|
||||
gbc.gridy = 6;
|
||||
JLabel pathLabel = new JLabel("Directory: " + searchDirectory.getAbsolutePath());
|
||||
pathLabel.setFont(pathLabel.getFont().deriveFont(Font.ITALIC));
|
||||
searchPanel.add(pathLabel, gbc);
|
||||
@ -543,6 +558,8 @@ public class SearchDialog extends JDialog {
|
||||
final String finalNamePat = namePat;
|
||||
final String finalContentPat = isContentSearch ? contentPat : null;
|
||||
final boolean searchArchives = archiveSearchCheckBox != null && archiveSearchCheckBox.isSelected();
|
||||
final boolean wholeWord = wholeWordCheckBox != null && wholeWordCheckBox.isSelected();
|
||||
final boolean caseSensitive = caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected();
|
||||
|
||||
// Reset and show status
|
||||
foundCount = 0;
|
||||
@ -556,7 +573,7 @@ public class SearchDialog extends JDialog {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
FileOperations.search(searchDirectory, finalNamePat, finalContentPat, recursiveCheckBox.isSelected(), searchArchives, new FileOperations.SearchCallback() {
|
||||
FileOperations.search(searchDirectory, finalNamePat, finalContentPat, recursiveCheckBox.isSelected(), searchArchives, wholeWord, caseSensitive, new FileOperations.SearchCallback() {
|
||||
@Override
|
||||
public void onFileFound(File file, String virtualPath) {
|
||||
publish(new Object[]{"file", file, virtualPath});
|
||||
@ -701,7 +718,9 @@ public class SearchDialog extends JDialog {
|
||||
contentPat = cit != null ? cit.toString().trim() : "";
|
||||
} catch (Exception ex) {}
|
||||
if (!contentPat.isEmpty()) {
|
||||
FileEditor.setLastSearchValue(contentPat);
|
||||
FileEditor.setLastSearchOptions(contentPat,
|
||||
wholeWordCheckBox != null && wholeWordCheckBox.isSelected(),
|
||||
caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected());
|
||||
}
|
||||
|
||||
FileEditor viewer = new FileEditor(owner, item.getFile(), vPath, config, true);
|
||||
@ -745,7 +764,9 @@ public class SearchDialog extends JDialog {
|
||||
contentPat = cit != null ? cit.toString().trim() : "";
|
||||
} catch (Exception ex) {}
|
||||
if (!contentPat.isEmpty()) {
|
||||
FileEditor.setLastSearchValue(contentPat);
|
||||
FileEditor.setLastSearchOptions(contentPat,
|
||||
wholeWordCheckBox != null && wholeWordCheckBox.isSelected(),
|
||||
caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected());
|
||||
}
|
||||
|
||||
FileEditor editor = new FileEditor(owner, item.getFile(), config, false);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user