Skip to content

Commit 68c021d

Browse files
committed
Wrap the formatter type on the settings page and let it be copied
The embedded binary's path, deep in the IDE's system directory, went into a label on one line: the page grew wider than the dialog, which then scrolled sideways, and a label's text cannot be selected. A long path of one's own in Binary widened the page the same way. Formatter type is now read-only text that wraps at the page's width and can be selected and copied, under its label and across the full width, as "Native binary, embedded: <path>". Binary has a fixed preferred width that the form stretches, rather than the width of its path.
1 parent 5e273ea commit 68c021d

4 files changed

Lines changed: 83 additions & 7 deletions

File tree

‎open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/FormatterSelection.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ static FormatterSelection of(PalantirJavaFormatSettings settings, Supplier<Optio
4444
record NativeImage(Path path, boolean embedded) implements FormatterSelection {
4545
@Override
4646
public String describe() {
47-
return "Native binary, " + (embedded ? "embedded" : "custom") + " (" + path + ")";
47+
return "Native binary, " + (embedded ? "embedded" : "custom") + ": " + path;
4848
}
4949
}
5050

‎open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatConfigurable.java‎

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@
2626
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
2727
import com.intellij.openapi.util.Disposer;
2828
import com.intellij.ui.components.ActionLink;
29+
import com.intellij.ui.components.JBTextArea;
2930
import com.intellij.ui.components.JBTextField;
3031
import com.intellij.util.ui.FormBuilder;
3132
import com.intellij.util.ui.JBUI;
3233
import com.palantir.javaformat.intellij.EmbeddedNativeImage.Status;
3334
import com.palantir.javaformat.intellij.PalantirJavaFormatSettings.EnabledState;
35+
import java.awt.Dimension;
3436
import java.awt.FlowLayout;
37+
import java.awt.event.ComponentAdapter;
38+
import java.awt.event.ComponentEvent;
3539
import java.nio.file.Files;
3640
import java.nio.file.InvalidPathException;
3741
import java.nio.file.Path;
@@ -42,6 +46,7 @@
4246
import javax.swing.JComponent;
4347
import javax.swing.JLabel;
4448
import javax.swing.JPanel;
49+
import javax.swing.JTextArea;
4550
import org.jetbrains.annotations.Nls;
4651
import org.jetbrains.annotations.NotNull;
4752

@@ -56,7 +61,7 @@ class PalantirJavaFormatConfigurable extends BaseConfigurable implements Searcha
5661
private final JLabel embeddedStatus;
5762
private final ActionLink embeddedAction;
5863
private final TextFieldWithBrowseButton nativeImagePath;
59-
private final JLabel formatterType;
64+
private final JTextArea formatterType;
6065
private final JLabel formatterVersion;
6166
private final JLabel pluginVersion;
6267

@@ -78,12 +83,14 @@ public PalantirJavaFormatConfigurable(Project project) {
7883
embeddedAction.addActionListener(event -> embeddedNativeImage.download(project, pluginVersion()));
7984
var pathField = new JBTextField();
8085
pathField.getEmptyText().setText("Leave empty to use the embedded version");
86+
// A fixed preferred width, which the form stretches: sized to a long path, it would widen the page instead.
87+
pathField.setColumns(30);
8188
this.nativeImagePath = new TextFieldWithBrowseButton(pathField);
8289
nativeImagePath.addBrowseFolderListener(new TextBrowseFolderListener(
8390
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor().withTitle("Native Binary"), project));
8491
this.pluginVersion = new JLabel();
8592
this.formatterVersion = new JLabel();
86-
this.formatterType = new JLabel();
93+
this.formatterType = selectableText();
8794

8895
var statusRow = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
8996
statusRow.add(embeddedStatus);
@@ -105,8 +112,9 @@ public PalantirJavaFormatConfigurable(Project project) {
105112
.addComponent(nativeImageOptions)
106113
.addLabeledComponent("Plugin version", pluginVersion)
107114
.addLabeledComponent("Implementation version", formatterVersion)
108-
.addLabeledComponent("Formatter type", formatterType)
109-
.addComponentFillVertically(new JPanel(), 0)
115+
// Last, and filling the rest of the page: only then does the form put the label at the text's first
116+
// line rather than in the middle of a wrapped path.
117+
.addLabeledComponentFillVertically("Formatter type", formatterType)
110118
.getPanel();
111119
}
112120

@@ -257,6 +265,37 @@ private Optional<Path> enteredNativeImagePath() throws ConfigurationException {
257265
}
258266
}
259267

268+
/**
269+
* Read-only text that wraps at the page's width and can be selected and copied: the formatter type names the
270+
* binary that runs, and the embedded one's path, deep in the IDE's system directory, would otherwise widen the
271+
* page past the dialog.
272+
*/
273+
private static JTextArea selectableText() {
274+
var text = new JBTextArea() {
275+
@Override
276+
public Dimension getPreferredSize() {
277+
// No wider than a short line: the form stretches it to the page, and once it has that width, the
278+
// height is that of its lines wrapped to it.
279+
var preferred = super.getPreferredSize();
280+
return new Dimension(Math.min(preferred.width, JBUI.scale(200)), preferred.height);
281+
}
282+
};
283+
// The height depends on the width the layout gives, which is known only after the layout.
284+
text.addComponentListener(new ComponentAdapter() {
285+
@Override
286+
public void componentResized(ComponentEvent event) {
287+
text.revalidate();
288+
}
289+
});
290+
text.setEditable(false);
291+
text.setLineWrap(true);
292+
text.setWrapStyleWord(true);
293+
text.setOpaque(false);
294+
text.setBorder(JBUI.Borders.empty());
295+
text.setFont(JBUI.Fonts.label());
296+
return text;
297+
}
298+
260299
private static String getFormatterVersionText(PalantirJavaFormatSettings settings) {
261300
String suffix = settings.injectedVersionIsOutdated() ? " (using bundled)" : " (using injected)";
262301
return settings.computeFormatterVersion().orElse("(bundled)") + suffix;

‎open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/FormatterSelectionTest.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ void looksForTheEmbeddedBinaryOnlyWhenItWouldRun() {
7676

7777
@Test
7878
void describesWhatRunsForTheSettingsPage() {
79-
assertThat(new NativeImage(CUSTOM, false).describe()).isEqualTo("Native binary, custom (" + CUSTOM + ")");
80-
assertThat(new NativeImage(EMBEDDED, true).describe()).isEqualTo("Native binary, embedded (" + EMBEDDED + ")");
79+
assertThat(new NativeImage(CUSTOM, false).describe()).isEqualTo("Native binary, custom: " + CUSTOM);
80+
assertThat(new NativeImage(EMBEDDED, true).describe()).isEqualTo("Native binary, embedded: " + EMBEDDED);
8181
assertThat(new JavaBased(false).describe()).isEqualTo("Java-based formatter");
8282
assertThat(new JavaBased(true).describe())
8383
.isEqualTo("Java-based formatter: the native binary is not downloaded");

‎open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatConfigurableTest.java‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import javax.swing.JCheckBox;
4545
import javax.swing.JComponent;
4646
import javax.swing.JLabel;
47+
import javax.swing.JTextArea;
4748
import org.junit.jupiter.api.AfterEach;
4849
import org.junit.jupiter.api.BeforeEach;
4950
import org.junit.jupiter.api.Test;
@@ -208,6 +209,35 @@ void offersADownloadBeforeTheFirstOne() throws Throwable {
208209
});
209210
}
210211

212+
@Test
213+
void wrapsTheFormatterTypeAndLetsItsPathBeCopied() throws Throwable {
214+
// The embedded binary lives deep in the IDE's system directory: on one line its path made the page wider than
215+
// the dialog, and a label would not let anyone copy it. A long path of one's own in Binary must not either.
216+
var path = Path.of("/Users/someone/Library/Caches/JetBrains/IntelliJIdea2026.2/open-java-format/native/"
217+
+ "21.0.1/".repeat(40) + "open-java-format-macos_aarch64");
218+
settings.setUseNativeImage(true);
219+
settings.setNativeImagePath(Optional.of(path));
220+
221+
EdtTestUtil.runInEdtAndWait(() -> {
222+
configurable.reset();
223+
224+
var formatterType = findAll(component, JTextArea.class).stream()
225+
.filter(text -> text.getText().contains(path.toString()))
226+
.findFirst()
227+
.orElseThrow();
228+
assertThat(formatterType.isEditable()).isFalse();
229+
assertThat(formatterType.getLineWrap()).isTrue();
230+
assertThat(component.getPreferredSize().width).isLessThan(1000);
231+
232+
// Laid out at a dialog's width, the path takes several lines rather than one cut off.
233+
component.setSize(600, 800);
234+
layOut(component);
235+
layOut(component);
236+
var line = formatterType.getFontMetrics(formatterType.getFont()).getHeight();
237+
assertThat(formatterType.getHeight()).isGreaterThan(2 * line);
238+
});
239+
}
240+
211241
@Test
212242
void describesEveryStateOfTheEmbeddedBinary() {
213243
var installed = new Installed("21.0.1", Path.of("/system/open-java-format/native/21.0.1/open-java-format"));
@@ -252,6 +282,13 @@ private Path executable(String name) throws IOException {
252282
return binary;
253283
}
254284

285+
private static void layOut(Component root) {
286+
if (root instanceof Container container) {
287+
container.doLayout();
288+
Arrays.stream(container.getComponents()).forEach(PalantirJavaFormatConfigurableTest::layOut);
289+
}
290+
}
291+
255292
private static <T extends Component> List<T> findAll(Component root, Class<T> type) {
256293
if (type.isInstance(root)) {
257294
return List.of(type.cast(root));

0 commit comments

Comments
 (0)