1515
1616package org .eclipse .ui .console ;
1717
18+ import java .nio .charset .StandardCharsets ;
1819import java .util .ArrayList ;
20+ import java .util .Base64 ;
1921import java .util .HashMap ;
2022import java .util .Map ;
2123import java .util .Objects ;
2830import org .eclipse .jface .action .IToolBarManager ;
2931import org .eclipse .jface .action .MenuManager ;
3032import org .eclipse .jface .action .Separator ;
33+ import org .eclipse .jface .preference .IPreferenceStore ;
3134import org .eclipse .jface .resource .JFaceResources ;
3235import org .eclipse .jface .text .IDocument ;
3336import org .eclipse .jface .text .IFindReplaceTarget ;
3639import org .eclipse .jface .util .IPropertyChangeListener ;
3740import org .eclipse .jface .util .PropertyChangeEvent ;
3841import org .eclipse .jface .viewers .ISelectionChangedListener ;
42+ import org .eclipse .swt .SWT ;
43+ import org .eclipse .swt .custom .StyledText ;
44+ import org .eclipse .swt .events .KeyListener ;
45+ import org .eclipse .swt .graphics .Font ;
46+ import org .eclipse .swt .graphics .FontData ;
3947import org .eclipse .swt .widgets .Composite ;
4048import org .eclipse .swt .widgets .Control ;
4149import org .eclipse .swt .widgets .Menu ;
@@ -80,9 +88,39 @@ public class TextConsolePage implements IPageBookViewPage, IPropertyChangeListen
8088 private final IConsoleView fConsoleView ;
8189 private TextConsoleViewer fViewer ;
8290 private MenuManager fMenuManager ;
91+ // Font created from persisted preferences; disposed by this page to avoid leaks
92+ private Font fPersistedFont ;
8393 protected Map <String , IAction > fGlobalActions = new HashMap <>();
8494 protected ArrayList <String > fSelectionActions = new ArrayList <>();
8595 protected ClearOutputAction fClearOutputAction ;
96+ /**
97+ * Data key used to store a custom zoom font on a StyledText widget. The font
98+ * stored under this key is managed per-widget and disposed via a
99+ * DisposeListener on that widget.
100+ */
101+ private static final String ZOOM_FONT_KEY = TextConsolePage .class .getName () + ".zoomFont" ; //$NON-NLS-1$
102+
103+ /**
104+ * Minimum font size for console zoom.
105+ */
106+ private static final int MIN_FONT_SIZE = 6 ;
107+
108+ /**
109+ * Maximum font size for console zoom.
110+ */
111+ private static final int MAX_FONT_SIZE = 72 ;
112+
113+ /**
114+ * Font size change step for zoom in/out.
115+ */
116+ private static final int FONT_SIZE_STEP = 1 ;
117+
118+ /**
119+ * Preference key prefix used to store per-console-type font settings.
120+ * The full key is: console.font.<consoleType>
121+ * The value format is: name|height|style
122+ */
123+ private static final String FONT_PREF_KEY_PREFIX = "console.font." ; //$NON-NLS-1$
86124
87125 // text selection listener, used to update selection dependent actions on selection changes
88126 private final ISelectionChangedListener selectionChangedListener = event -> updateSelectionDependentActions ();
@@ -145,6 +183,13 @@ protected void updateSelectionDependentActions() {
145183 @ Override
146184 public void createControl (Composite parent ) {
147185 fViewer = createViewer (parent );
186+ // Restore font for this console type (if saved)
187+ Font prefFont = loadFontFromPreferences ();
188+ if (prefFont != null ) {
189+ // keep a reference so we can dispose it when the page is disposed
190+ fPersistedFont = prefFont ;
191+ fViewer .setFont (prefFont );
192+ }
148193 fViewer .setConsoleWidth (fConsole .getConsoleWidth ());
149194 fViewer .setTabWidth (fConsole .getTabWidth ());
150195 fConsole .addPropertyChangeListener (this );
@@ -168,6 +213,12 @@ public void createControl(Composite parent) {
168213
169214 fViewer .getSelectionProvider ().addSelectionChangedListener (selectionChangedListener );
170215 fViewer .addTextListener (textListener );
216+
217+ // Install font zoom key listener on the StyledText widget
218+ StyledText textWidget = fViewer .getTextWidget ();
219+ if (textWidget != null ) {
220+ installFontZoomKeyListener (textWidget );
221+ }
171222 }
172223
173224 @ Override
@@ -185,6 +236,11 @@ public void dispose() {
185236 fViewer .getSelectionProvider ().removeSelectionChangedListener (selectionChangedListener );
186237 fViewer .removeTextListener (textListener );
187238 fViewer = null ;
239+ // Dispose any font created from persisted preferences to avoid resource leaks
240+ if (fPersistedFont != null && !fPersistedFont .isDisposed ()) {
241+ fPersistedFont .dispose ();
242+ fPersistedFont = null ;
243+ }
188244 }
189245
190246 @ Override
@@ -393,4 +449,198 @@ public TextConsoleViewer getViewer() {
393449 public void setViewer (TextConsoleViewer viewer ) {
394450 this .fViewer = viewer ;
395451 }
452+
453+ /**
454+ * Installs the font zoom key listener on the given control.
455+ *
456+ * @param control the control to install the key listener on
457+ */
458+ private void installFontZoomKeyListener (Control control ) {
459+ control .addKeyListener (KeyListener .keyPressedAdapter (event -> {
460+ // Check for Ctrl key first.
461+ if ((event .stateMask & SWT .MOD1 ) == 0 ) {
462+ return ;
463+ }
464+
465+ // Check for + or - keys (including numpad)
466+ boolean isPlus = event .character == '=' || event .keyCode == SWT .KEYPAD_ADD
467+ || (event .character == '+' && (event .stateMask & SWT .SHIFT ) != 0 );
468+ boolean isMinus = event .character == '-' || event .keyCode == SWT .KEYPAD_SUBTRACT ;
469+
470+ if (isPlus ) {
471+ increaseFontSize ();
472+ event .doit = false ;
473+ } else if (isMinus ) {
474+ decreaseFontSize ();
475+ event .doit = false ;
476+ }
477+ }));
478+ }
479+
480+ /**
481+ * Increases the font size of the console by one step.
482+ */
483+ private void increaseFontSize () {
484+ changeFontSize (FONT_SIZE_STEP );
485+ }
486+
487+ /**
488+ * Decreases the font size of the console by one step.
489+ */
490+ private void decreaseFontSize () {
491+ changeFontSize (-FONT_SIZE_STEP );
492+ }
493+
494+ /**
495+ * Changes the font size of the console by the given delta.
496+ *
497+ * @param delta the amount to change the font size (positive to increase,
498+ * negative to decrease)
499+ */
500+ private void changeFontSize (int delta ) {
501+ StyledText styledText = fViewer .getTextWidget ();
502+ if (styledText == null || styledText .isDisposed ()) {
503+ return ;
504+ }
505+
506+ Font currentFont = styledText .getFont ();
507+ if (currentFont == null || currentFont .isDisposed ()) {
508+ return ;
509+ }
510+
511+ FontData [] fontData = currentFont .getFontData ();
512+ if (fontData == null || fontData .length == 0 ) {
513+ return ;
514+ }
515+
516+ int currentHeight = fontData [0 ].getHeight ();
517+ int newHeight = Math .max (MIN_FONT_SIZE , Math .min (MAX_FONT_SIZE , currentHeight + delta ));
518+
519+ if (newHeight == currentHeight ) {
520+ return ;
521+ }
522+
523+ // Copy the existing FontData array and set the new height on each element.
524+ FontData [] newFontData = fontData .clone ();
525+ for (FontData fd : newFontData ) {
526+ if (fd != null ) {
527+ fd .setHeight (newHeight );
528+ }
529+ }
530+
531+ // Get any existing zoom font for this specific StyledText
532+ Font oldZoomFont = (Font ) styledText .getData (ZOOM_FONT_KEY );
533+
534+ // Create and set the new font
535+ Font newZoomFont = new Font (styledText .getDisplay (), newFontData );
536+ styledText .setFont (newZoomFont );
537+
538+ // Store the new zoom font on this specific StyledText
539+ styledText .setData (ZOOM_FONT_KEY , newZoomFont );
540+
541+ // Install DisposeListener if this is the first zoom font for this widget
542+ if (oldZoomFont == null ) {
543+ styledText .addDisposeListener (e -> {
544+ Font zoomFont = (Font ) styledText .getData (ZOOM_FONT_KEY );
545+ if (zoomFont != null && !zoomFont .isDisposed ()) {
546+ zoomFont .dispose ();
547+ }
548+ });
549+ }
550+
551+ // Dispose the old zoom font for this widget after setting the new one
552+ if (oldZoomFont != null && !oldZoomFont .isDisposed ()) {
553+ oldZoomFont .dispose ();
554+ }
555+
556+ // Persist the changed font for this console type
557+ saveFontToPreferences (newZoomFont );
558+ }
559+
560+ /**
561+ * Returns the preference key for the font for this console's type.
562+ */
563+ private String getFontPrefKey () {
564+ String type = getConsole ().getType ();
565+ if (type == null || type .isEmpty ()) {
566+ return FONT_PREF_KEY_PREFIX + "default" ; //$NON-NLS-1$
567+ }
568+ return FONT_PREF_KEY_PREFIX + type ;
569+ }
570+
571+ /**
572+ * Loads a Font from the preference store for this console type, or null if none.
573+ */
574+ private Font loadFontFromPreferences () {
575+ IPreferenceStore store = ConsolePlugin .getDefault ().getPreferenceStore ();
576+ String value = store .getString (getFontPrefKey ());
577+ if (value == null || value .isEmpty ()) {
578+ return null ;
579+ }
580+ // Stored as a comma-separated list of Base64 encoded tokens.
581+ // Each token is either a platform FontData(String) (starting with "1|")
582+ // or our compact format: name|height|style|locale
583+ try {
584+ String [] parts = value .split ("," ); //$NON-NLS-1$
585+ FontData [] fds = new FontData [parts .length ];
586+ for (int i = 0 ; i < parts .length ; i ++) {
587+ String decoded = new String (Base64 .getDecoder ().decode (parts [i ]), StandardCharsets .UTF_8 );
588+
589+ if (decoded .startsWith ("1|" )) { //$NON-NLS-1$
590+ // platform-native FontData string
591+ fds [i ] = new FontData (decoded );
592+ } else {
593+ // our compact format: name|height|style|locale
594+ String [] fields = decoded .split ("\\ |" , -1 ); //$NON-NLS-1$
595+ if (fields .length >= 4 ) {
596+ String name = fields [0 ];
597+ int height = Integer .parseInt (fields [1 ]);
598+ int style = Integer .parseInt (fields [2 ]);
599+ String locale = fields [3 ];
600+ FontData fd = new FontData (name , height , style );
601+ if (locale != null && !locale .isEmpty ()) {
602+ fd .setLocale (locale );
603+ }
604+ fds [i ] = fd ;
605+ } else {
606+ fds [i ] = new FontData (decoded );
607+ }
608+ }
609+ }
610+ return new Font (ConsolePlugin .getStandardDisplay (), fds );
611+ } catch (RuntimeException e ) {
612+ return null ;
613+ }
614+ }
615+
616+ /**
617+ * Saves the given font into the preference store for this console type.
618+ */
619+ private void saveFontToPreferences (Font font ) {
620+ if (font == null )
621+ return ;
622+ FontData [] fds = font .getFontData ();
623+ if (fds == null || fds .length == 0 )
624+ return ;
625+ // Persist only name, height, style and locale in a compact token and Base64-encode it.
626+ StringBuilder sb = new StringBuilder ();
627+ for (int i = 0 ; i < fds .length ; i ++) {
628+ if (i > 0 ) {
629+ sb .append (',' );
630+ }
631+ FontData fd = fds [i ];
632+ String name = fd .getName ();
633+ int height = fd .getHeight ();
634+ int style = fd .getStyle ();
635+ String locale = fd .getLocale ();
636+ if (locale == null ) {
637+ locale = "" ; //$NON-NLS-1$
638+ }
639+ String token = name + "|" + height + "|" + style + "|" + locale ; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
640+ String enc = Base64 .getEncoder ().encodeToString (token .getBytes (StandardCharsets .UTF_8 ));
641+ sb .append (enc );
642+ }
643+ IPreferenceStore store = ConsolePlugin .getDefault ().getPreferenceStore ();
644+ store .setValue (getFontPrefKey (), sb .toString ());
645+ }
396646}
0 commit comments