Skip to content

Commit 6c8cefa

Browse files
committed
Add more utilities commands
- Revert File - 'camelCase' and 'lower_underscore_case' transformations - Open URL under cursor - Search web for selected text
1 parent ca4580e commit 6c8cefa

File tree

2 files changed

+239
-17
lines changed

2 files changed

+239
-17
lines changed

src/main/java/org/scijava/ui/swing/script/EditorPane.java

Lines changed: 213 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@
4747
import java.io.IOException;
4848
import java.io.InputStreamReader;
4949
import java.io.OutputStreamWriter;
50+
import java.net.URL;
51+
import java.util.Arrays;
5052
import java.util.Collection;
5153
import java.util.List;
54+
import java.util.regex.Matcher;
55+
import java.util.regex.Pattern;
5256

5357
import javax.swing.Action;
5458
import javax.swing.ImageIcon;
@@ -78,16 +82,21 @@
7882
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
7983
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
8084
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.CopyAsStyledTextAction;
81-
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.ToggleCommentAction;
8285
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.DecreaseIndentAction;
83-
import org.fife.ui.rtextarea.RTextAreaEditorKit.*;
86+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.ToggleCommentAction;
8487
import org.fife.ui.rsyntaxtextarea.Style;
8588
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
8689
import org.fife.ui.rsyntaxtextarea.Theme;
8790
import org.fife.ui.rtextarea.Gutter;
8891
import org.fife.ui.rtextarea.GutterIconInfo;
8992
import org.fife.ui.rtextarea.RTextArea;
9093
import org.fife.ui.rtextarea.RTextAreaEditorKit;
94+
import org.fife.ui.rtextarea.RTextAreaEditorKit.ClipboardHistoryAction;
95+
import org.fife.ui.rtextarea.RTextAreaEditorKit.InvertSelectionCaseAction;
96+
import org.fife.ui.rtextarea.RTextAreaEditorKit.LineMoveAction;
97+
import org.fife.ui.rtextarea.RTextAreaEditorKit.LowerSelectionCaseAction;
98+
import org.fife.ui.rtextarea.RTextAreaEditorKit.TimeDateAction;
99+
import org.fife.ui.rtextarea.RTextAreaEditorKit.UpperSelectionCaseAction;
91100
import org.fife.ui.rtextarea.RTextScrollPane;
92101
import org.fife.ui.rtextarea.RecordableTextAction;
93102
import org.fife.ui.rtextarea.SearchContext;
@@ -171,7 +180,7 @@ public void hyperlinkUpdate(final HyperlinkEvent hle) {
171180
}
172181
}
173182
});
174-
183+
175184
// load preferences
176185
loadPreferences();
177186

@@ -207,9 +216,10 @@ public void mousePressed(final MouseEvent me) {
207216
// https://github.com/bobbylight/RSyntaxTextArea/issues/88
208217
if (getMarkOccurrences() && 2 == me.getClickCount()) {
209218

210-
// Do nothing if getMarkOccurrences() is unset or no selection exists
219+
// Do nothing if getMarkOccurrences() is unset or no valid
220+
// selection exists (we'll skip white space selection)
211221
final String str = getSelectedText();
212-
if (str == null) return;
222+
if (str == null || str.trim().isEmpty()) return;
213223

214224
if (context != null && str.equals(context.getSearchFor())) {
215225
// Selection is the previously 'marked all' scope. Clear it
@@ -234,17 +244,24 @@ private void adjustPopupMenu() {
234244
final JPopupMenu popup = super.getPopupMenu();
235245
JMenu menu = new JMenu("Move");
236246
popup.add(menu);
237-
menu.add(getMenuItem("Decrease Indent", new DecreaseIndentAction()));
238-
menu.add(getMenuItem("Increase Indent", new IncreaseIndentAction()));
239-
menu.addSeparator();
247+
menu.add(getMenuItem("Shift Left (Decrease Indent)", new DecreaseIndentAction()));
248+
menu.add(getMenuItem("Shift Right (Increase Indent)", new IncreaseIndentAction()));
249+
menu.addSeparator();
240250
menu.add(getMenuItem("Move Up", new LineMoveAction(RTextAreaEditorKit.rtaLineUpAction, -1)));
241251
menu.add(getMenuItem("Move Down", new LineMoveAction(RTextAreaEditorKit.rtaLineDownAction, 1)));
242252
menu = new JMenu("Transform");
243253
popup.add(menu);
244-
menu.add(getMenuItem("Camel Case", new CamelCaseAction()));
245254
menu.add(getMenuItem("Invert Case", new InvertSelectionCaseAction()));
255+
menu.addSeparator();
256+
menu.add(getMenuItem("Camel Case", new CamelCaseAction()));
246257
menu.add(getMenuItem("Lower Case", new LowerSelectionCaseAction()));
258+
menu.add(getMenuItem("Lower Case ('_' Sep.)", new LowerCaseUnderscoreAction()));
259+
menu.add(getMenuItem("Title Case", new TitleCaseAction()));
247260
menu.add(getMenuItem("Upper Case", new UpperSelectionCaseAction()));
261+
menu = new JMenu("Actions");
262+
popup.add(menu);
263+
menu.add(getMenuItem("Open URL Under Cursor", new OpenLinkUnderCursor()));
264+
menu.add(getMenuItem("Search the Web for Selected Text", new SearchWebOnSelectedText()));
248265
}
249266

250267
private JMenuItem getMenuItem(final String label, final RecordableTextAction a) {
@@ -1061,6 +1078,17 @@ String getSupportStatus() {
10611078
return supportStatus;
10621079
}
10631080

1081+
private void openLinkInBrowser(String link) {
1082+
try {
1083+
if (!link.startsWith("http"))
1084+
link = "https://" + link; // or it won't work
1085+
platformService.open(new URL(link));
1086+
} catch (final IOException exc) {
1087+
UIManager.getLookAndFeel().provideErrorFeedback(this);
1088+
System.out.println(exc.getMessage());
1089+
}
1090+
}
1091+
10641092
static class CamelCaseAction extends RecordableTextAction {
10651093
private static final long serialVersionUID = 1L;
10661094

@@ -1100,6 +1128,68 @@ public String getMacroID() {
11001128

11011129
}
11021130

1131+
static class TitleCaseAction extends RecordableTextAction {
1132+
private static final long serialVersionUID = 1L;
1133+
1134+
TitleCaseAction() {
1135+
super("RTA.TitleCaseAction");
1136+
}
1137+
1138+
@Override
1139+
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1140+
if (!textArea.isEditable() || !textArea.isEnabled()) {
1141+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1142+
return;
1143+
}
1144+
final String selection = textArea.getSelectedText();
1145+
if (selection != null) {
1146+
final String[] words = selection.split("[\\W_]+");
1147+
final StringBuilder buffer = new StringBuilder();
1148+
for (int i = 0; i < words.length; i++) {
1149+
String word = words[i];
1150+
word = word.isEmpty() ? word
1151+
: Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase();
1152+
buffer.append(word);
1153+
if (i < words.length-1) buffer.append(" ");
1154+
}
1155+
textArea.replaceSelection(buffer.toString());
1156+
}
1157+
textArea.requestFocusInWindow();
1158+
}
1159+
1160+
@Override
1161+
public String getMacroID() {
1162+
return getName();
1163+
}
1164+
1165+
}
1166+
1167+
static class LowerCaseUnderscoreAction extends RecordableTextAction {
1168+
private static final long serialVersionUID = 1L;
1169+
1170+
LowerCaseUnderscoreAction() {
1171+
super("RTA.LowerCaseUnderscoreSep.Action");
1172+
}
1173+
1174+
@Override
1175+
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1176+
if (!textArea.isEditable() || !textArea.isEnabled()) {
1177+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1178+
return;
1179+
}
1180+
final String selection = textArea.getSelectedText();
1181+
if (selection != null)
1182+
textArea.replaceSelection(selection.trim().replaceAll("\\s", "_").toLowerCase());
1183+
textArea.requestFocusInWindow();
1184+
}
1185+
1186+
@Override
1187+
public String getMacroID() {
1188+
return getName();
1189+
}
1190+
1191+
}
1192+
11031193
/** Modified from DecreaseIndentAction */
11041194
static class IncreaseIndentAction extends RecordableTextAction {
11051195

@@ -1196,4 +1286,118 @@ private void handleIncreaseIndent(final Element elem, final Document doc, final
11961286

11971287
}
11981288

1289+
class SearchWebOnSelectedText extends RecordableTextAction {
1290+
private static final long serialVersionUID = 1L;
1291+
1292+
SearchWebOnSelectedText() {
1293+
super("RTA.SearchWebOnSelectedTextAction");
1294+
}
1295+
1296+
@Override
1297+
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1298+
if (!textArea.isEditable() || !textArea.isEnabled()) {
1299+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1300+
return;
1301+
}
1302+
final String selection = textArea.getSelectedText();
1303+
if (selection == null) {
1304+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1305+
} else {
1306+
final String link = "https://duckduckgo.com/?q=" + selection.trim().replace(" ", "+");
1307+
openLinkInBrowser(link);
1308+
}
1309+
textArea.requestFocusInWindow();
1310+
}
1311+
1312+
@Override
1313+
public String getMacroID() {
1314+
return getName();
1315+
}
1316+
1317+
}
1318+
1319+
class OpenLinkUnderCursor extends RecordableTextAction {
1320+
private static final long serialVersionUID = 1L;
1321+
1322+
OpenLinkUnderCursor() {
1323+
super("RTA.OpenLinkUnderCursor.Action");
1324+
}
1325+
1326+
@Override
1327+
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1328+
if (!textArea.isEditable() || !textArea.isEnabled()) {
1329+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1330+
return;
1331+
}
1332+
String link = new CursorUtils(textArea).getLinkAtCursor();
1333+
if (link == null) {
1334+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1335+
} else {
1336+
openLinkInBrowser(link);
1337+
}
1338+
textArea.requestFocusInWindow();
1339+
}
1340+
1341+
@Override
1342+
public String getMacroID() {
1343+
return getName();
1344+
}
1345+
1346+
}
1347+
1348+
private static class CursorUtils {
1349+
1350+
final List<String> SPACE_SEPARATORS = Arrays.asList(" ", "\t", "\f", "\n", "\r");
1351+
final String URL_REGEX = "^((https?|ftp)://|(www|ftp)\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$";
1352+
final Pattern pattern = Pattern.compile(URL_REGEX);
1353+
final RTextArea pane;
1354+
1355+
private CursorUtils(RTextArea textArea) {
1356+
this.pane = textArea;
1357+
}
1358+
1359+
String getLineAtCursor() {
1360+
final int start = pane.getLineStartOffsetOfCurrentLine();
1361+
final int end = pane.getLineEndOffsetOfCurrentLine();
1362+
try {
1363+
return pane.getDocument().getText(start, end - start);
1364+
} catch (BadLocationException ignored) {
1365+
// do nothing
1366+
}
1367+
return null;
1368+
}
1369+
1370+
String getWordAtCursor() {
1371+
final String text = getLineAtCursor();
1372+
final int pos = pane.getCaretOffsetFromLineStart();
1373+
final int wordStart = getWordStart(text, pos);
1374+
final int wordEnd = getWordEnd(text, pos);
1375+
return text.substring(wordStart, wordEnd);
1376+
}
1377+
1378+
String getLinkAtCursor() {
1379+
String text = getWordAtCursor();
1380+
if (text == null)
1381+
return null;
1382+
final Matcher m = pattern.matcher(text);
1383+
return (m.find()) ? m.group() : null;
1384+
}
1385+
1386+
int getWordStart(final String text, final int location) {
1387+
int wordStart = location;
1388+
while (wordStart > 0 && !SPACE_SEPARATORS.contains(text.substring(wordStart - 1, wordStart))) {
1389+
wordStart--;
1390+
}
1391+
return wordStart;
1392+
}
1393+
1394+
int getWordEnd(final String text, final int location) {
1395+
int wordEnd = location;
1396+
while (wordEnd < text.length() - 1
1397+
&& !SPACE_SEPARATORS.contains(text.substring(wordEnd, wordEnd + 1))) {
1398+
wordEnd++;
1399+
}
1400+
return wordEnd;
1401+
}
1402+
}
11991403
}

src/main/java/org/scijava/ui/swing/script/TextEditor.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ public TextEditor(final Context context) {
346346
makeJarWithSource = addToMenu(file, "Export as JAR (With Source)", 0, 0);
347347
makeJarWithSource.setMnemonic(KeyEvent.VK_X);
348348
file.addSeparator();
349-
final JCheckBoxMenuItem lock = new JCheckBoxMenuItem("Lock File (Make Read Only)");
349+
final JCheckBoxMenuItem lock = new JCheckBoxMenuItem("Lock (Make Read Only)");
350350
file.add(lock);
351351
lock.addActionListener( e -> {
352352
if (lock.isSelected()) {
@@ -355,11 +355,25 @@ public TextEditor(final Context context) {
355355
new SetWritableAction().actionPerformedImpl(e, getTextArea());
356356
}
357357
});
358+
JMenuItem jmi = new JMenuItem("Revert");
359+
jmi.addActionListener(e -> {
360+
if (lock.isSelected()) {
361+
error("File is locked (read only).");
362+
return;
363+
}
364+
final File f = getEditorPane().getFile();
365+
if (f == null || !f.exists()) {
366+
error(getEditorPane().getFileName() + "\nhas not been saved or its file is not available.");
367+
} else {
368+
reloadRevert("Revert to Saved File? Any unsaved changes will be lost.", "Revert");
369+
}
370+
});
371+
file.add(jmi);
358372
file.addSeparator();
359-
final JMenuItem jmi = new JMenuItem("Show in System Explorer");
373+
jmi = new JMenuItem("Show in System Explorer");
360374
jmi.addActionListener(e -> {
361375
final File f = getEditorPane().getFile();
362-
if (f == null) {
376+
if (f == null || !f.exists()) {
363377
error(getEditorPane().getFileName() + "\nhas not been saved or its file is not available.");
364378
} else {
365379
try {
@@ -1200,8 +1214,8 @@ private void initializeDynamicMenuComponents() {
12001214
public void checkForOutsideChanges() {
12011215
final EditorPane editorPane = getEditorPane();
12021216
if (editorPane.wasChangedOutside()) {
1203-
reload("The file " + editorPane.getFile().getName() +
1204-
" was changed outside of the editor");
1217+
reload(editorPane.getFile().getName() +
1218+
"\nwas changed outside of the editor.");
12051219
}
12061220

12071221
}
@@ -1968,15 +1982,19 @@ public boolean reload() {
19681982
}
19691983

19701984
public boolean reload(final String message) {
1985+
return reloadRevert(message, "Reload");
1986+
}
1987+
1988+
private boolean reloadRevert(final String message, final String title) {
19711989
final EditorPane editorPane = getEditorPane();
19721990

19731991
final File file = editorPane.getFile();
19741992
if (file == null || !file.exists()) return true;
19751993

19761994
final boolean modified = editorPane.fileChanged();
1977-
final String[] options = { "Reload", "Do not reload" };
1978-
if (modified) options[0] = "Reload (discarding changes)";
1979-
switch (JOptionPane.showOptionDialog(this, message, "Reload",
1995+
final String[] options = { title, "Do Not " + title };
1996+
if (modified) options[0] = title + " (Discard Changes)";
1997+
switch (JOptionPane.showOptionDialog(this, message, title + "?",
19801998
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options,
19811999
options[0])) {
19822000
case 0:

0 commit comments

Comments
 (0)