Skip to content

Commit 7910253

Browse files
sbraconniertonygermano
authored andcommitted
Add the ability to manually format Javascript code
This change allows the user to format code within the code editor areas. The user can format the currently selected lines or the entire document. A default hotkey of ctrl+shift+F has been added to run this action, but it may be changed by the user. Additionally, this action can be performed in the right-click context menu by selecting "Code->Format" Signed-off-by: Tony Germano <tony@germano.name> Original-sign-off: 132de74 Issue: #87
1 parent 7ecf54b commit 7910253

File tree

8 files changed

+4180
-2484
lines changed

8 files changed

+4180
-2484
lines changed

client/src/com/mirth/connect/client/ui/components/rsta/MirthRSyntaxTextArea.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import com.mirth.connect.client.ui.components.rsta.actions.ExpandFoldAction;
6363
import com.mirth.connect.client.ui.components.rsta.actions.FindNextAction;
6464
import com.mirth.connect.client.ui.components.rsta.actions.FindReplaceAction;
65+
import com.mirth.connect.client.ui.components.rsta.actions.FormatCodeAction;
6566
import com.mirth.connect.client.ui.components.rsta.actions.GoToMatchingBracketAction;
6667
import com.mirth.connect.client.ui.components.rsta.actions.HorizontalPageAction;
6768
import com.mirth.connect.client.ui.components.rsta.actions.InsertBreakAction;
@@ -127,6 +128,8 @@ public class MirthRSyntaxTextArea extends RSyntaxTextArea implements MirthTextIn
127128
private CustomJCheckBoxMenuItem showWhitespaceMenuItem;
128129
private CustomJCheckBoxMenuItem showLineEndingsMenuItem;
129130
private CustomJCheckBoxMenuItem wrapLinesMenuItem;
131+
private JMenu codeMenu;
132+
private CustomMenuItem formatCodeMenuItem;
130133
private JMenu macroMenu;
131134
private CustomMenuItem beginMacroMenuItem;
132135
private CustomMenuItem endMacroMenuItem;
@@ -228,6 +231,8 @@ public void keyPressed(KeyEvent e) {
228231
showWhitespaceMenuItem = new CustomJCheckBoxMenuItem(this, new ShowWhitespaceAction(this), ActionInfo.DISPLAY_SHOW_WHITESPACE);
229232
showLineEndingsMenuItem = new CustomJCheckBoxMenuItem(this, new ShowLineEndingsAction(this), ActionInfo.DISPLAY_SHOW_LINE_ENDINGS);
230233
wrapLinesMenuItem = new CustomJCheckBoxMenuItem(this, new WrapLinesAction(this), ActionInfo.DISPLAY_WRAP_LINES);
234+
codeMenu = new JMenu("Code");
235+
formatCodeMenuItem = new CustomMenuItem(this, new FormatCodeAction(this), ActionInfo.FORMAT_CODE);
231236
macroMenu = new JMenu("Macro");
232237
beginMacroMenuItem = new CustomMenuItem(this, new BeginMacroAction(), ActionInfo.MACRO_BEGIN);
233238
endMacroMenuItem = new CustomMenuItem(this, new EndMacroAction(), ActionInfo.MACRO_END);
@@ -239,6 +244,7 @@ public void keyPressed(KeyEvent e) {
239244
getActionMap().put(ActionInfo.DELETE_LINE.getActionMapKey(), new DeleteLineAction());
240245
getActionMap().put(ActionInfo.JOIN_LINE.getActionMapKey(), new JoinLineAction());
241246
getActionMap().put(ActionInfo.GO_TO_MATCHING_BRACKET.getActionMapKey(), new GoToMatchingBracketAction());
247+
getActionMap().put(ActionInfo.FORMAT_CODE.getActionMapKey(), new FormatCodeAction(this));
242248
getActionMap().put(ActionInfo.TOGGLE_COMMENT.getActionMapKey(), new ToggleCommentAction());
243249
getActionMap().put(ActionInfo.DOCUMENT_START.getActionMapKey(), new DocumentStartAction(false));
244250
getActionMap().put(ActionInfo.DOCUMENT_SELECT_START.getActionMapKey(), new DocumentStartAction(true));
@@ -489,6 +495,10 @@ protected JPopupMenu createPopupMenu() {
489495
menu.add(displayMenu);
490496
menu.addSeparator();
491497

498+
codeMenu.add(formatCodeMenuItem);
499+
menu.add(codeMenu);
500+
menu.addSeparator();
501+
492502
macroMenu.add(beginMacroMenuItem);
493503
macroMenu.add(endMacroMenuItem);
494504
macroMenu.add(playbackMacroMenuItem);
@@ -514,6 +524,7 @@ protected void configurePopupMenu(JPopupMenu popupMenu) {
514524
findNextMenuItem.setEnabled(findNextMenuItem.getAction().isEnabled() && CollectionUtils.isNotEmpty(rstaPreferences.getFindReplaceProperties().getFindHistory()));
515525
clearMarkedOccurrencesMenuItem.setEnabled(clearMarkedOccurrencesMenuItem.getAction().isEnabled() && canType && ((RSyntaxTextAreaHighlighter) getHighlighter()).getMarkAllHighlightCount() > 0);
516526
foldingMenu.setEnabled(getFoldManager().isCodeFoldingSupportedAndEnabled());
527+
formatCodeMenuItem.setEnabled(formatCodeMenuItem.getAction().isEnabled());
517528
beginMacroMenuItem.setEnabled(!isRecordingMacro());
518529
endMacroMenuItem.setEnabled(isRecordingMacro());
519530
playbackMacroMenuItem.setEnabled(!isRecordingMacro() && getCurrentMacro() != null);
@@ -533,6 +544,7 @@ protected void configurePopupMenu(JPopupMenu popupMenu) {
533544
collapseAllFoldsMenuItem.updateAccelerator();
534545
collapseAllCommentFoldsMenuItem.updateAccelerator();
535546
expandAllFoldsMenuItem.updateAccelerator();
547+
formatCodeMenuItem.updateAccelerator();
536548
beginMacroMenuItem.updateAccelerator();
537549
endMacroMenuItem.updateAccelerator();
538550
playbackMacroMenuItem.updateAccelerator();
@@ -550,4 +562,4 @@ public void setSyntaxEditingStyle(String styleKey) {
550562
public Action[] getActions() {
551563
return actions != null ? actions : getUI().getEditorKit(this).getActions();
552564
}
553-
}
565+
}

client/src/com/mirth/connect/client/ui/components/rsta/MirthRSyntaxTextArea.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ TOGGLE_COMMENT.Name=Toggle Comment
118118
TOGGLE_COMMENT.Desc=Comments / uncomments the current selected line(s).
119119
TOGGLE_COMMENT.Toggle=false
120120

121+
FORMAT_CODE.Mnemonic=F
122+
FORMAT_CODE.Name=Format
123+
FORMAT_CODE.Desc=Format the current script / selected line(s).
124+
FORMAT_CODE.Toggle=false
125+
121126
VIEW_USER_API.Mnemonic=V
122127
VIEW_USER_API.Name=View User API
123128
VIEW_USER_API.Desc=Opens up the User API Javadoc in a browser.

client/src/com/mirth/connect/client/ui/components/rsta/RSTAPreferences.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ private void setDefaultKeyStrokeMap() {
234234
putKeyStroke(ActionInfo.FOLD_EXPAND_ALL, KeyEvent.VK_MULTIPLY, defaultModifier);
235235
putKeyStroke(ActionInfo.GO_TO_MATCHING_BRACKET, KeyEvent.VK_OPEN_BRACKET, defaultModifier);
236236
putKeyStroke(ActionInfo.TOGGLE_COMMENT, KeyEvent.VK_SLASH, defaultModifier);
237+
putKeyStroke(ActionInfo.FORMAT_CODE, KeyEvent.VK_F, ctrl + shift);
237238
putKeyStroke(ActionInfo.AUTO_COMPLETE, KeyEvent.VK_SPACE, ctrl);
238239

239240
if (isOSX) {

client/src/com/mirth/connect/client/ui/components/rsta/actions/ActionInfo.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public enum ActionInfo {
4040
DISPLAY_WRAP_LINES ("mirth-wrap-lines"),
4141
GO_TO_MATCHING_BRACKET (RSyntaxTextAreaEditorKit.rstaGoToMatchingBracketAction),
4242
TOGGLE_COMMENT (RSyntaxTextAreaEditorKit.rstaToggleCommentAction),
43+
FORMAT_CODE ("mirth-format-code"),
4344
VIEW_USER_API ("mirth-view-user-api"),
4445
AUTO_COMPLETE ("AutoComplete"),
4546
DOCUMENT_START (DefaultEditorKit.beginAction),
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// SPDX-FileCopyrightText: 2025 Simon Braconnier <simonbraconnier@gmail.com>
3+
4+
package com.mirth.connect.client.ui.components.rsta.actions;
5+
6+
import java.awt.event.ActionEvent;
7+
8+
import javax.swing.UIManager;
9+
import javax.swing.text.BadLocationException;
10+
import javax.swing.text.Caret;
11+
import javax.swing.text.Element;
12+
13+
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
14+
15+
import com.mirth.connect.client.ui.components.rsta.MirthRSyntaxTextArea;
16+
import com.mirth.connect.util.JavaScriptSharedUtil;
17+
18+
public class FormatCodeAction extends MirthRecordableTextAction {
19+
20+
public FormatCodeAction(MirthRSyntaxTextArea textArea) {
21+
super(textArea, ActionInfo.FORMAT_CODE);
22+
}
23+
24+
@Override
25+
public void actionPerformedImpl(ActionEvent evt) {
26+
27+
if (!textArea.isEditable() || !textArea.isEnabled()) {
28+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
29+
return;
30+
}
31+
32+
Caret c = textArea.getCaret();
33+
int start = Math.min(c.getDot(), c.getMark());
34+
int end = Math.max(c.getDot(), c.getMark());
35+
if (start == end) {
36+
formatAll();
37+
} else {
38+
formatRange(start, end);
39+
}
40+
41+
}
42+
43+
@Override
44+
public boolean isEnabled() {
45+
return textArea.isEditable() && textArea.isEnabled();
46+
}
47+
48+
private void formatAll() {
49+
50+
String code = textArea.getText();
51+
formatAndReplace(code, 0, code.length());
52+
}
53+
54+
private void formatRange(int start, int end) {
55+
56+
// We want to format all the lines of the selection (not only the selected text)
57+
58+
try {
59+
RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
60+
Element map = doc.getDefaultRootElement();
61+
62+
// Get the lines indexes from the selected text indexes.
63+
int startLine = map.getElementIndex(start);
64+
int endLine = map.getElementIndex(end);
65+
66+
int replaceRangeStart = 0;
67+
int replaceRangeEnd = 0;
68+
69+
// Build a string from the selected lines.
70+
StringBuilder sb = new StringBuilder();
71+
for (int line = startLine; line <= endLine; line++) {
72+
Element elem = map.getElement(line);
73+
74+
int startOffset = elem.getStartOffset();
75+
int endOffset = elem.getEndOffset() - 1;
76+
77+
// Save the start index of the first line for the final replacement.
78+
if (line == startLine) {
79+
replaceRangeStart = startOffset;
80+
}
81+
82+
// Save the end index of the last line for the final replacement.
83+
if (line == endLine) {
84+
replaceRangeEnd = endOffset;
85+
}
86+
87+
sb.append(doc.getText(startOffset, endOffset - startOffset + 1));
88+
}
89+
90+
// Format the code and replace the selected lines.
91+
formatAndReplace(sb.toString(), replaceRangeStart, replaceRangeEnd);
92+
93+
} catch (BadLocationException ble) {
94+
ble.printStackTrace();
95+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
96+
}
97+
}
98+
99+
private void formatAndReplace(String code, int start, int end) {
100+
101+
String formattedCode = JavaScriptSharedUtil.prettyPrint(code);
102+
textArea.replaceRange(formattedCode, start, end);
103+
textArea.setCaretPosition(start);
104+
}
105+
}

server/src/com/mirth/connect/util/JavaScriptSharedUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,12 @@ private static ScriptableObject getFormatterScope() {
198198
InputStream is = null;
199199

200200
try {
201-
is = JavaScriptSharedUtil.class.getResourceAsStream("beautify-1.6.8.js");
201+
is = JavaScriptSharedUtil.class.getResourceAsStream("beautify-1.15.3.js");
202202
String script = IOUtils.toString(is);
203203
ScriptableObject scope = context.initStandardObjects();
204204
context.evaluateString(scope, "var global = {};", UUID.randomUUID().toString(), 1, null);
205205
context.evaluateString(scope, script, UUID.randomUUID().toString(), 1, null);
206-
context.evaluateString(scope, "var opts = { 'e4x': true };", UUID.randomUUID().toString(), 1, null);
206+
context.evaluateString(scope, "var opts = { 'e4x': true, 'indent_with_tabs': true };", UUID.randomUUID().toString(), 1, null);
207207
return scope;
208208
} catch (Exception e) {
209209
logger.error("Failed to load beautify library.");

0 commit comments

Comments
 (0)