Skip to content

Commit

Permalink
Fix #90: Auto wrap selected text in quotes and parens
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbylight committed Nov 25, 2022
1 parent 5403728 commit f3f91b3
Show file tree
Hide file tree
Showing 10 changed files with 404 additions and 7 deletions.
Expand Up @@ -131,6 +131,7 @@ public class RSyntaxTextArea extends RTextArea implements SyntaxConstants {
public static final String FRACTIONAL_FONTMETRICS_PROPERTY = "RSTA.fractionalFontMetrics";
public static final String HIGHLIGHT_SECONDARY_LANGUAGES_PROPERTY = "RSTA.highlightSecondaryLanguages";
public static final String HYPERLINKS_ENABLED_PROPERTY = "RSTA.hyperlinksEnabled";
public static final String INSERT_PAIRED_CHARS_PROPERTY = "RSTA.insertPairedChars";
public static final String MARK_OCCURRENCES_PROPERTY = "RSTA.markOccurrences";
public static final String MARKED_OCCURRENCES_CHANGED_PROPERTY = "RSTA.markedOccurrencesChanged";
public static final String PAINT_MATCHED_BRACKET_PAIR_PROPERTY = "RSTA.paintMatchedBracketPair";
Expand Down Expand Up @@ -322,6 +323,7 @@ public class RSyntaxTextArea extends RTextArea implements SyntaxConstants {

private Color[] secondaryLanguageBackgrounds;

private boolean insertPairedCharacters;

/**
* Constructor.
Expand Down Expand Up @@ -1311,6 +1313,35 @@ public boolean getHyperlinksEnabled() {
}


/**
* Returns whether paired characters should be inserted when there is
* a selection. For example, If the following text is selected:
* <p>
* {@code something}
* </p>
* And the double quote character, {@code "}, is typed, the selection
* will be replaced with:
* <p>
* {@code "something"}
* </p>
* If enabled, this occurs for the following characters:
* <ul>
* <li>{@code "}</li>
* <li>{@code '}</li>
* <li>{@code [}</li>
* <li>{@code (}</li>
* <li>{@code &#123;}</li>
* </ul>
*
* @return Whether to insert paired characters if a relevant key is typed
* while there is a selection.
* @see #setInsertPairedCharacters(boolean)
*/
public boolean getInsertPairedCharacters() {
return insertPairedCharacters;
}


/**
* Returns the last visible offset in this text area. This may not be the
* length of the document if code folding is enabled.
Expand Down Expand Up @@ -2011,6 +2042,7 @@ protected void init() {
setTabLineColor(null);
setMarkOccurrencesColor(MarkOccurrencesSupport.DEFAULT_COLOR);
setMarkOccurrencesDelay(MarkOccurrencesSupport.DEFAULT_DELAY_MS);
setInsertPairedCharacters(true);

foldManager = new DefaultFoldManager(this);

Expand Down Expand Up @@ -2679,6 +2711,39 @@ public void setHyperlinksEnabled(boolean enabled) {
}


/**
* Toggles whether paired characters should be inserted when there is
* a selection. For example, If the following text is selected:
* <p>
* {@code something}
* </p>
* And the double quote character, {@code "}, is typed, the selection
* can be replaced with:
* <p>
* {@code "something"}
* </p>
* If enabled, this occurs for the following characters:
* <ul>
* <li>{@code "}</li>
* <li>{@code '}</li>
* <li>{@code [}</li>
* <li>{@code (}</li>
* <li>{@code &#123;}</li>
* </ul>
*
* @param insertPairedCharacters Whether to insert paired characters if a
* relevant key is typed while there is a selection.
* @see #getInsertPairedCharacters()
*/
public void setInsertPairedCharacters(boolean insertPairedCharacters) {
if (this.insertPairedCharacters != insertPairedCharacters) {
this.insertPairedCharacters = insertPairedCharacters;
firePropertyChange(INSERT_PAIRED_CHARS_PROPERTY,
!insertPairedCharacters, insertPairedCharacters);
}
}


public void setLinkGenerator(LinkGenerator generator) {
this.linkGenerator = generator;
}
Expand Down
Expand Up @@ -42,9 +42,14 @@ public RSyntaxTextAreaDefaultInputMap() {
int defaultShift = defaultMod|shift;

put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, shift), RSyntaxTextAreaEditorKit.rstaDecreaseIndentAction);
put(KeyStroke.getKeyStroke('}'), RSyntaxTextAreaEditorKit.rstaCloseCurlyBraceAction);

put(KeyStroke.getKeyStroke('/'), RSyntaxTextAreaEditorKit.rstaCloseMarkupTagAction);
put(KeyStroke.getKeyStroke('}'), RSyntaxTextAreaEditorKit.rstaCloseCurlyBraceAction);
put(KeyStroke.getKeyStroke('('), RSyntaxTextAreaEditorKit.rstaOpenParenAction);
put(KeyStroke.getKeyStroke('['), RSyntaxTextAreaEditorKit.rstaOpenSquareBracketAction);
put(KeyStroke.getKeyStroke('{'), RSyntaxTextAreaEditorKit.rstaOpenCurlyAction);
put(KeyStroke.getKeyStroke('\''), RSyntaxTextAreaEditorKit.rstaSingleQuoteAction);
put(KeyStroke.getKeyStroke('"'), RSyntaxTextAreaEditorKit.rstaDoubleQuoteAction);

put(KeyStroke.getKeyStroke('/'), RSyntaxTextAreaEditorKit.rstaCloseMarkupTagAction);
int os = RSyntaxUtilities.getOS();
if (os==RSyntaxUtilities.OS_WINDOWS || os==RSyntaxUtilities.OS_MAC_OSX) {
// *nix causes trouble with CloseMarkupTagAction and ToggleCommentAction.
Expand Down
Expand Up @@ -82,6 +82,11 @@ public class RSyntaxTextAreaEditorKit extends RTextAreaEditorKit {
public static final String rstaExpandAllFoldsAction = "RSTA.ExpandAllFoldsAction";
public static final String rstaExpandFoldAction = "RSTA.ExpandFoldAction";
public static final String rstaGoToMatchingBracketAction = "RSTA.GoToMatchingBracketAction";
public static final String rstaOpenParenAction = "RSTA.openParenAction";
public static final String rstaOpenSquareBracketAction = "RSTA.openSquareBracketAction";
public static final String rstaOpenCurlyAction = "RSTA.openCurlyAction";
public static final String rstaDoubleQuoteAction = "RSTA.doubleQuoteAction";
public static final String rstaSingleQuoteAction = "RSTA.singleQuoteAction";
public static final String rstaPossiblyInsertTemplateAction = "RSTA.TemplateAction";
public static final String rstaToggleCommentAction = "RSTA.ToggleCommentAction";
public static final String rstaToggleCurrentFoldAction = "RSTA.ToggleCurrentFoldAction";
Expand Down Expand Up @@ -114,8 +119,13 @@ public class RSyntaxTextAreaEditorKit extends RTextAreaEditorKit {
new EndWordAction(endWordAction, true),
new ExpandAllFoldsAction(),
new GoToMatchingBracketAction(),
new InsertBreakAction(),
//new IncreaseFontSizeAction(),
new InsertBreakAction(),
new InsertPairedCharacterAction(rstaOpenParenAction, '(', ')'),
new InsertPairedCharacterAction(rstaOpenSquareBracketAction, '[', ']'),
new InsertPairedCharacterAction(rstaOpenCurlyAction, '{', '}'),
new InsertPairedCharacterAction(rstaDoubleQuoteAction, '"', '"'),
new InsertPairedCharacterAction(rstaSingleQuoteAction, '\'', '\''),
new InsertTabAction(),
new NextWordAction(nextWordAction, false),
new NextWordAction(selectionNextWordAction, true),
Expand Down Expand Up @@ -322,6 +332,11 @@ public CloseCurlyBraceAction() {
@Override
public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

if (!textArea.isEditable() || !textArea.isEnabled()) {
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
return;
}

RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();

Expand Down Expand Up @@ -1634,6 +1649,63 @@ private void possiblyCloseCurlyBrace(RSyntaxTextArea textArea,
}


/**
* If there is no selection, a character is inserted. If there is a selection,
* it is wrapped by the character and its pair. Useful for e.g. quotes, parens,
* etc.
*/
public static class InsertPairedCharacterAction extends DefaultKeyTypedAction {

private static final long serialVersionUID = 1L;

private final char ch;
private final char pairedCh;

public InsertPairedCharacterAction(String actionName, char ch, char pairedCh) {
super(actionName);
this.ch = ch;
this.pairedCh = pairedCh;
}

@Override
public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

if (!textArea.isEditable() || !textArea.isEnabled()) {
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
return;
}

RSyntaxTextArea sta = (RSyntaxTextArea)textArea;
boolean noSelection = sta.getSelectionStart() == sta.getSelectionEnd();

if (noSelection || !sta.getInsertPairedCharacters()) {
// Default action can be unique across OS's
super.actionPerformedImpl(e, textArea);
}
else {
wrapSelection(textArea);
}
}

private void wrapSelection(RTextArea textArea) {

int selStart = textArea.getSelectionStart();
int selEnd = textArea.getSelectionEnd();

textArea.beginAtomicEdit();
try {
textArea.insert(String.valueOf(ch), selStart);
textArea.insert(String.valueOf(pairedCh), selEnd + 1);
// Remove the auto-increase from insertion
textArea.setSelectionEnd(selEnd + 1);
} finally {
textArea.endAtomicEdit();
}
}

}


/**
* Action for inserting tabs. This is extended to "block indent" a
* group of contiguous lines if they are selected.
Expand Down
Expand Up @@ -846,8 +846,11 @@ public static class DefaultKeyTypedAction extends RecordableTextAction {
private Action delegate;

public DefaultKeyTypedAction() {
super(DefaultEditorKit.defaultKeyTypedAction, null, null, null,
null);
this(DefaultEditorKit.defaultKeyTypedAction);
}

protected DefaultKeyTypedAction(String name) {
super(name, null, null, null, null);
delegate = new DefaultEditorKit.DefaultKeyTypedAction();
}

Expand All @@ -864,7 +867,7 @@ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

@Override
public final String getMacroID() {
return DefaultEditorKit.defaultKeyTypedAction;
return getName();
}

}
Expand Down
Expand Up @@ -7,6 +7,7 @@
package org.fife.ui.rsyntaxtextarea;

import org.fife.ui.SwingRunnerExtension;
import org.fife.ui.rtextarea.RTextArea;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -23,6 +24,44 @@
@ExtendWith(SwingRunnerExtension.class)
class RSyntaxTextAreaCloseCurlyBraceActionTest extends AbstractRSyntaxTextAreaTest {

@Test
void testActionPerformedImpl_notEditable() {

RSyntaxTextArea textArea = createTextArea();

// Remove the final closing curly
textArea.setSelectionStart(textArea.getDocument().getLength() - 2);
textArea.setSelectionEnd(textArea.getDocument().getLength());
textArea.replaceSelection("\n\n ");
String expected = textArea.getText();
textArea.setEditable(false);

RSyntaxTextAreaEditorKit.CloseCurlyBraceAction a = new RSyntaxTextAreaEditorKit.CloseCurlyBraceAction();
ActionEvent e = createActionEvent(textArea, RSyntaxTextAreaEditorKit.rstaCloseCurlyBraceAction);
a.actionPerformedImpl(e, textArea);

Assertions.assertEquals(expected, textArea.getText());
}

@Test
void testActionPerformedImpl_notEnabled() {

RSyntaxTextArea textArea = createTextArea();

// Remove the final closing curly
textArea.setSelectionStart(textArea.getDocument().getLength() - 2);
textArea.setSelectionEnd(textArea.getDocument().getLength());
textArea.replaceSelection("\n\n ");
String expected = textArea.getText();
textArea.setEnabled(false);

RSyntaxTextAreaEditorKit.CloseCurlyBraceAction a = new RSyntaxTextAreaEditorKit.CloseCurlyBraceAction();
ActionEvent e = createActionEvent(textArea, RSyntaxTextAreaEditorKit.rstaCloseCurlyBraceAction);
a.actionPerformedImpl(e, textArea);

Assertions.assertEquals(expected, textArea.getText());
}

@Test
void testActionPerformedImpl_closeCurlyBrace() {

Expand Down
Expand Up @@ -7,6 +7,8 @@
package org.fife.ui.rsyntaxtextarea;

import org.fife.ui.SwingRunnerExtension;
import org.fife.ui.rtextarea.RTextArea;
import org.fife.ui.rtextarea.RTextAreaEditorKit;
import org.fife.ui.rtextarea.RecordableTextAction;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand All @@ -24,6 +26,32 @@
@ExtendWith(SwingRunnerExtension.class)
class RSyntaxTextAreaEditorKitInsertBreakActionTest extends AbstractRSyntaxTextAreaTest {

@Test
void testActionPerformedImpl_notEditable() {

RTextArea textArea = new RTextArea("hello world");
textArea.setCaretPosition(textArea.getText().indexOf(' '));
textArea.setEditable(false);

ActionEvent e = new ActionEvent(textArea, 0, "command");
new RSyntaxTextAreaEditorKit.InsertBreakAction().actionPerformedImpl(e, textArea);

Assertions.assertEquals("hello world", textArea.getText());
}

@Test
void testActionPerformedImpl_notEnabled() {

RTextArea textArea = new RTextArea("hello world");
textArea.setCaretPosition(textArea.getText().indexOf(' '));
textArea.setEnabled(false);

ActionEvent e = new ActionEvent(textArea, 0, "command");
new RSyntaxTextAreaEditorKit.InsertBreakAction().actionPerformedImpl(e, textArea);

Assertions.assertEquals("hello world", textArea.getText());
}

@Test
void testActionPerformedImpl_closingCurlyBraceAdded() {

Expand Down

0 comments on commit f3f91b3

Please sign in to comment.