4747import java .io .IOException ;
4848import java .io .InputStreamReader ;
4949import java .io .OutputStreamWriter ;
50+ import java .net .URL ;
51+ import java .util .Arrays ;
5052import java .util .Collection ;
5153import java .util .List ;
54+ import java .util .regex .Matcher ;
55+ import java .util .regex .Pattern ;
5256
5357import javax .swing .Action ;
5458import javax .swing .ImageIcon ;
7882import org .fife .ui .rsyntaxtextarea .RSyntaxTextArea ;
7983import org .fife .ui .rsyntaxtextarea .RSyntaxTextAreaEditorKit ;
8084import org .fife .ui .rsyntaxtextarea .RSyntaxTextAreaEditorKit .CopyAsStyledTextAction ;
81- import org .fife .ui .rsyntaxtextarea .RSyntaxTextAreaEditorKit .ToggleCommentAction ;
8285import org .fife .ui .rsyntaxtextarea .RSyntaxTextAreaEditorKit .DecreaseIndentAction ;
83- import org .fife .ui .rtextarea . RTextAreaEditorKit .* ;
86+ import org .fife .ui .rsyntaxtextarea . RSyntaxTextAreaEditorKit . ToggleCommentAction ;
8487import org .fife .ui .rsyntaxtextarea .Style ;
8588import org .fife .ui .rsyntaxtextarea .SyntaxScheme ;
8689import org .fife .ui .rsyntaxtextarea .Theme ;
8790import org .fife .ui .rtextarea .Gutter ;
8891import org .fife .ui .rtextarea .GutterIconInfo ;
8992import org .fife .ui .rtextarea .RTextArea ;
9093import 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 ;
91100import org .fife .ui .rtextarea .RTextScrollPane ;
92101import org .fife .ui .rtextarea .RecordableTextAction ;
93102import 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}
0 commit comments