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