From 8b4f16a8a5388681cb0b3c4036d35022791dc5a6 Mon Sep 17 00:00:00 2001 From: Denys Almazov Date: Thu, 18 Jun 2026 18:26:27 +0300 Subject: [PATCH 1/2] feat: replace program_esp_bins with userfriendly msg --- .../dsf/console/IdfProcessConsole.java | 105 ++++++++++++++++++ .../openocd/dsf/process/StreamListener.java | 38 ++++++- .../debug/gdbjtag/openocd/ui/Messages.java | 2 + .../gdbjtag/openocd/ui/messages.properties | 2 + 4 files changed, 146 insertions(+), 1 deletion(-) diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java index d36352b9d..6fdfcdbc6 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java @@ -15,6 +15,9 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.launchbar.core.ILaunchBarManager; +import org.eclipse.launchbar.core.target.ILaunchTarget; +import org.eclipse.launchbar.ui.target.ILaunchTargetUIManager; import org.eclipse.swt.program.Program; import org.eclipse.ui.console.IHyperlink; import org.eclipse.ui.console.IOConsole; @@ -25,6 +28,8 @@ import org.eclipse.ui.console.TextConsole; import com.espressif.idf.core.logging.Logger; +import com.espressif.idf.debug.gdbjtag.openocd.Activator; +import com.espressif.idf.debug.gdbjtag.openocd.ui.Messages; /** * Idf process console created for customization of the process output to filter and color code @@ -44,6 +49,7 @@ public class IdfProcessConsole extends IOConsole implements IPropertyChangeListe private IOConsoleInputStream inputStream; private URLPatternMatchListener urlPatternMatchListener; + private EditTargetPatternMatchListener editTargetPatternMatchListener; public IdfProcessConsole(Charset charset) { @@ -53,6 +59,7 @@ public IdfProcessConsole(Charset charset) errorStream = newOutputStream(); warnStream = newOutputStream(); urlPatternMatchListener = new URLPatternMatchListener(this); + editTargetPatternMatchListener = new EditTargetPatternMatchListener(this); } @Override @@ -86,6 +93,7 @@ public void init() }); addPatternMatchListener(urlPatternMatchListener); + addPatternMatchListener(editTargetPatternMatchListener); } @Override @@ -287,4 +295,101 @@ public String getLineQualifier() return null; } } + + /** + * Turns the {@link Messages#OpenOCDConsole_EditTargetLink} text printed in the console into a hyperlink that opens + * the launch target editor, so the user can select a board for the active debug target. + */ + private class EditTargetPatternMatchListener implements IPatternMatchListener + { + private IdfProcessConsole idfProcessConsole; + + public EditTargetPatternMatchListener(IdfProcessConsole idfProcessConsole) + { + this.idfProcessConsole = idfProcessConsole; + } + + @Override + public String getPattern() + { + return Pattern.quote(Messages.OpenOCDConsole_EditTargetLink); + } + + @Override + public void matchFound(PatternMatchEvent event) + { + try + { + int offset = event.getOffset(); + int length = event.getLength(); + IHyperlink link = new IHyperlink() + { + @Override + public void linkActivated() + { + editActiveLaunchTarget(); + } + + @Override + public void linkExited() + { + } + + @Override + public void linkEntered() + { + } + }; + idfProcessConsole.addHyperlink(link, offset, length); + } + catch (Exception e) + { + Logger.log(e); + } + } + + private void editActiveLaunchTarget() + { + try + { + ILaunchBarManager launchBarManager = Activator.getService(ILaunchBarManager.class); + ILaunchTargetUIManager targetUIManager = Activator.getService(ILaunchTargetUIManager.class); + if (launchBarManager == null || targetUIManager == null) + { + return; + } + ILaunchTarget target = launchBarManager.getActiveLaunchTarget(); + if (target != null) + { + targetUIManager.editLaunchTarget(target); + } + } + catch (Exception e) + { + Logger.log(e); + } + } + + @Override + public void connect(TextConsole console) + { + } + + @Override + public void disconnect() + { + } + + @Override + public int getCompilerFlags() + { + return 0; + } + + @Override + public String getLineQualifier() + { + return null; + } + } } diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java index 4fc342304..57d375aa1 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java @@ -39,6 +39,10 @@ public class StreamListener implements IStreamListener { private static final String OPENOCD_FAQ_LINK = "https://github.com/espressif/openocd-esp32/wiki/Troubleshooting-FAQ"; //$NON-NLS-1$ + // OpenOCD reports this low-level TCL error when the board configuration file (which registers the + // "program_esp_bins" command) was not loaded, i.e. no board was selected for the debug target. + private static final String PROGRAM_ESP_BINS_ERROR_MARKER = "invalid command name \"program_esp_bins\""; //$NON-NLS-1$ + private IOConsoleOutputStream fConsoleErrorOutputStream; private IOConsoleOutputStream fConsoleOutputStream; @@ -47,6 +51,7 @@ public class StreamListener implements IStreamListener private IdfProcessConsole idfProcessConsole; private boolean fStreamClosed = false; + private boolean fBoardNotSelectedReported = false; private List reHintsList; private final PrintWriter fileWriter; @@ -118,7 +123,11 @@ public void streamAppended(String text, IStreamMonitor monitor) try { - if (line.startsWith("Error:") && fConsoleErrorOutputStream != null) //$NON-NLS-1$ + if (line.contains(PROGRAM_ESP_BINS_ERROR_MARKER)) + { + reportBoardNotSelected(); + } + else if (line.startsWith("Error:") && fConsoleErrorOutputStream != null) //$NON-NLS-1$ { fConsoleErrorOutputStream.write((line + System.lineSeparator()).getBytes()); fConsoleErrorOutputStream.flush(); @@ -163,6 +172,33 @@ else if (fConsoleOutputStream != null) }); } + /** + * Replaces the raw OpenOCD {@code invalid command name "program_esp_bins"} error with a short, actionable message + * explaining that a board has to be selected for the debug target, followed by a clickable link that opens the + * launch target editor. The message is reported only once per launch so the console is not flooded by the repeated + * low-level TCL errors. + * + * @throws IOException if writing to the console streams fails + */ + private void reportBoardNotSelected() throws IOException + { + if (fBoardNotSelectedReported || fConsoleErrorOutputStream == null) + { + return; + } + fBoardNotSelectedReported = true; + fConsoleErrorOutputStream + .write((Messages.OpenOCDConsole_BoardNotSelectedMessage + System.lineSeparator()).getBytes()); + fConsoleErrorOutputStream.flush(); + + // The link text is turned into a clickable hyperlink by IdfProcessConsole, which opens the launch target editor. + if (fConsoleOutputStream != null) + { + fConsoleOutputStream.write((Messages.OpenOCDConsole_EditTargetLink + System.lineSeparator()).getBytes()); + fConsoleOutputStream.flush(); + } + } + public void closeStreams() { synchronized (fErrorStreamMonitor) diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java index c912f5414..af7d8671e 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java @@ -63,6 +63,8 @@ public class Messages public static String TabDebugger_SettingTargetJob; public static String OpenOCDConsole_ErrorGuideMessage; + public static String OpenOCDConsole_BoardNotSelectedMessage; + public static String OpenOCDConsole_EditTargetLink; public static String TabDebugger_noConfigOptions; public static String TabDebugger_noGdbClientExe; diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties index ffeeceab4..0556c5c30 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties @@ -414,6 +414,8 @@ TabMain_Launch_Config=Launch Configuration: ## Console Messages ## OpenOCDConsole_ErrorGuideMessage=Please refer to the troubleshooting guide below to identify the problem. +OpenOCDConsole_BoardNotSelectedMessage=No board is selected for the debug target. +OpenOCDConsole_EditTargetLink=Click here to edit the target and select a board. ## Timeout Exception Dialog ## ServerTimeoutErrorDialog_customAreaMessage=To increase timeout time visit the Espressif Preference Page. From da497a32c8ccdd8ba22abbae2414921f8692a344 Mon Sep 17 00:00:00 2001 From: Denys Almazov Date: Thu, 18 Jun 2026 19:04:58 +0300 Subject: [PATCH 2/2] feat: change the approach with early no board popuop --- .../core/variable/JtagVariableResolver.java | 42 +++++-- .../plugin.xml | 6 + .../dsf/LaunchConfigurationDelegate.java | 52 +++++++++ .../dsf/console/IdfProcessConsole.java | 105 ------------------ .../openocd/dsf/process/StreamListener.java | 38 +------ .../ui/BoardNotSelectedStatusHandler.java | 60 ++++++++++ .../debug/gdbjtag/openocd/ui/Messages.java | 5 +- .../gdbjtag/openocd/ui/messages.properties | 6 +- .../idf/launch/serial/internal/Messages.java | 2 + .../SerialFlashLaunchConfigDelegate.java | 54 +++++++++ .../serial/internal/messages.properties | 4 +- 11 files changed, 216 insertions(+), 158 deletions(-) create mode 100644 bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/BoardNotSelectedStatusHandler.java diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/variable/JtagVariableResolver.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/variable/JtagVariableResolver.java index 9f81c5ace..c9a3a812d 100644 --- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/variable/JtagVariableResolver.java +++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/variable/JtagVariableResolver.java @@ -67,6 +67,23 @@ private String generatePartOfConfigOptionsForVoltage() } private String generatePartOfConfigOptionsForBoard() + { + var result = new StringBuilder(); + for (Object config : resolveBoardConfigFiles()) + { + result.append(String.format("-f %s ", config)); //$NON-NLS-1$ + } + return result.toString(); + } + + /** + * Resolves the OpenOCD board configuration files for the active launch target. These config files register the + * board-specific OpenOCD commands (e.g. {@code program_esp_bins}). An empty result means no usable board is + * selected for the active target. + * + * @return the list of board config files, never {@code null} + */ + private List resolveBoardConfigFiles() { var parser = new EspConfigParser(); ILaunchTarget activeILaunchTarget = getActiveLaunchTarget().orElseGet(() -> ILaunchTarget.NULL_TARGET); @@ -76,17 +93,20 @@ private String generatePartOfConfigOptionsForBoard() int idx = board.lastIndexOf(" [usb://"); //$NON-NLS-1$ String boardKey = (idx != -1) ? board.substring(0, idx) : board; List boards = parser.getBoardsForTarget(targetName); - List boardConfigs = boards.stream().filter(b -> b.name().equals(boardKey)).findFirst() - .map(Board::config_files).orElse(List.of()); - var result = new StringBuilder(); - if (boardConfigs != null) - { - for (Object config : boardConfigs) - { - result.append(String.format("-f %s ", config)); //$NON-NLS-1$ - } - } - return result.toString(); + return boards.stream().filter(b -> b.name().equals(boardKey)).findFirst().map(Board::config_files) + .orElse(List.of()); + } + + /** + * Checks whether a board configuration is resolvable for the active launch target. When this returns + * {@code false}, OpenOCD would start without a board configuration and board-specific commands such as + * {@code program_esp_bins} would not be available, so the debug session cannot succeed. + * + * @return {@code true} if a board is selected and its OpenOCD config files were found, {@code false} otherwise + */ + public static boolean isBoardConfigResolvable() + { + return !new JtagVariableResolver().resolveBoardConfigFiles().isEmpty(); } } diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/plugin.xml b/bundles/com.espressif.idf.debug.gdbjtag.openocd/plugin.xml index 2fd06585f..64d432f19 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/plugin.xml +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/plugin.xml @@ -174,6 +174,12 @@ id="com.espressif.idf.debug.gdbjtag.openocd.openocdStatusHandler" plugin="com.espressif.idf.debug.gdbjtag.openocd"> + + diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java index 5f59eea21..bd9a0d823 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java @@ -53,12 +53,14 @@ import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.core.IStatusHandler; import org.eclipse.debug.core.model.ISourceLocator; import org.eclipse.embedcdt.core.StringUtils; import org.eclipse.embedcdt.debug.gdbjtag.core.DebugUtils; import org.eclipse.embedcdt.debug.gdbjtag.core.dsf.AbstractGnuMcuLaunchConfigurationDelegate; import org.eclipse.embedcdt.debug.gdbjtag.core.dsf.GnuMcuServerServicesLaunchSequence; +import com.espressif.idf.core.variable.JtagVariableResolver; import com.espressif.idf.debug.gdbjtag.openocd.Activator; import com.espressif.idf.debug.gdbjtag.openocd.Configuration; import com.espressif.idf.debug.gdbjtag.openocd.ui.Messages; @@ -79,6 +81,12 @@ public class LaunchConfigurationDelegate extends AbstractGnuMcuLaunchConfigurati private final static String NON_STOP_FIRST_VERSION = "6.8.50"; //$NON-NLS-1$ private final int STATUS_DLL_NOT_FOUND = -1073741515; + /** + * Status code used to route the "no board selected" failure through {@code BoardNotSelectedStatusHandler}. It must + * match the {@code code} of the corresponding {@code statusHandler} extension in plugin.xml. + */ + public static final int BOARD_NOT_SELECTED_STATUS_CODE = 6001; + ILaunchConfiguration fConfig = null; @SuppressWarnings("unused") private boolean fIsNonStopSession = false; @@ -736,12 +744,56 @@ protected IPath checkBinaryDetails(final ILaunchConfiguration config) throws Cor new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Missing mandatory configuration. " + "Fill-in the 'Config options:' field in the Debugger tab.")); //$NON-NLS-1$ } + + // Abort early with a clear, actionable message when no board is selected for the active target. Otherwise + // OpenOCD would start without a board configuration and fail later with the cryptic + // "invalid command name \"program_esp_bins\"" error. + if (!isBoardConfigured(config)) + { + IStatus status = new Status(IStatus.OK, Activator.PLUGIN_ID, BOARD_NOT_SELECTED_STATUS_CODE, + "No board is selected for the debug target.", null); //$NON-NLS-1$ + IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status); + if (handler != null) + { + handler.handleStatus(status, null); + } + throw new DebugException(Status.OK_STATUS); + } } IPath path = super.checkBinaryDetails(config); return path; } + /** + * Determines whether a board configuration is available for the given launch configuration. A board is considered + * configured when it is resolvable from the active launch target, or when the (possibly manually edited) resolved + * Config options already reference a board configuration file. Without either, OpenOCD would start without the + * board-specific commands (e.g. {@code program_esp_bins}) and the debug session would fail. + * + * @param config the debug launch configuration + * @return {@code true} if a board configuration is available, {@code false} otherwise + */ + private boolean isBoardConfigured(ILaunchConfiguration config) + { + if (JtagVariableResolver.isBoardConfigResolvable()) + { + return true; + } + + // Fall back to inspecting the resolved Config options for a manually configured board file. + try + { + String resolvedOptions = Configuration.resolveAll(Configuration.getGdbServerOtherConfig(config), config); + return resolvedOptions != null && resolvedOptions.contains("board/"); //$NON-NLS-1$ + } + catch (CoreException e) + { + Activator.log(e); + } + return false; + } + /** * Get a custom launch sequence, that inserts a GDB server starter. */ diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java index 6fdfcdbc6..d36352b9d 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/console/IdfProcessConsole.java @@ -15,9 +15,6 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; -import org.eclipse.launchbar.core.ILaunchBarManager; -import org.eclipse.launchbar.core.target.ILaunchTarget; -import org.eclipse.launchbar.ui.target.ILaunchTargetUIManager; import org.eclipse.swt.program.Program; import org.eclipse.ui.console.IHyperlink; import org.eclipse.ui.console.IOConsole; @@ -28,8 +25,6 @@ import org.eclipse.ui.console.TextConsole; import com.espressif.idf.core.logging.Logger; -import com.espressif.idf.debug.gdbjtag.openocd.Activator; -import com.espressif.idf.debug.gdbjtag.openocd.ui.Messages; /** * Idf process console created for customization of the process output to filter and color code @@ -49,7 +44,6 @@ public class IdfProcessConsole extends IOConsole implements IPropertyChangeListe private IOConsoleInputStream inputStream; private URLPatternMatchListener urlPatternMatchListener; - private EditTargetPatternMatchListener editTargetPatternMatchListener; public IdfProcessConsole(Charset charset) { @@ -59,7 +53,6 @@ public IdfProcessConsole(Charset charset) errorStream = newOutputStream(); warnStream = newOutputStream(); urlPatternMatchListener = new URLPatternMatchListener(this); - editTargetPatternMatchListener = new EditTargetPatternMatchListener(this); } @Override @@ -93,7 +86,6 @@ public void init() }); addPatternMatchListener(urlPatternMatchListener); - addPatternMatchListener(editTargetPatternMatchListener); } @Override @@ -295,101 +287,4 @@ public String getLineQualifier() return null; } } - - /** - * Turns the {@link Messages#OpenOCDConsole_EditTargetLink} text printed in the console into a hyperlink that opens - * the launch target editor, so the user can select a board for the active debug target. - */ - private class EditTargetPatternMatchListener implements IPatternMatchListener - { - private IdfProcessConsole idfProcessConsole; - - public EditTargetPatternMatchListener(IdfProcessConsole idfProcessConsole) - { - this.idfProcessConsole = idfProcessConsole; - } - - @Override - public String getPattern() - { - return Pattern.quote(Messages.OpenOCDConsole_EditTargetLink); - } - - @Override - public void matchFound(PatternMatchEvent event) - { - try - { - int offset = event.getOffset(); - int length = event.getLength(); - IHyperlink link = new IHyperlink() - { - @Override - public void linkActivated() - { - editActiveLaunchTarget(); - } - - @Override - public void linkExited() - { - } - - @Override - public void linkEntered() - { - } - }; - idfProcessConsole.addHyperlink(link, offset, length); - } - catch (Exception e) - { - Logger.log(e); - } - } - - private void editActiveLaunchTarget() - { - try - { - ILaunchBarManager launchBarManager = Activator.getService(ILaunchBarManager.class); - ILaunchTargetUIManager targetUIManager = Activator.getService(ILaunchTargetUIManager.class); - if (launchBarManager == null || targetUIManager == null) - { - return; - } - ILaunchTarget target = launchBarManager.getActiveLaunchTarget(); - if (target != null) - { - targetUIManager.editLaunchTarget(target); - } - } - catch (Exception e) - { - Logger.log(e); - } - } - - @Override - public void connect(TextConsole console) - { - } - - @Override - public void disconnect() - { - } - - @Override - public int getCompilerFlags() - { - return 0; - } - - @Override - public String getLineQualifier() - { - return null; - } - } } diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java index 57d375aa1..4fc342304 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/process/StreamListener.java @@ -39,10 +39,6 @@ public class StreamListener implements IStreamListener { private static final String OPENOCD_FAQ_LINK = "https://github.com/espressif/openocd-esp32/wiki/Troubleshooting-FAQ"; //$NON-NLS-1$ - // OpenOCD reports this low-level TCL error when the board configuration file (which registers the - // "program_esp_bins" command) was not loaded, i.e. no board was selected for the debug target. - private static final String PROGRAM_ESP_BINS_ERROR_MARKER = "invalid command name \"program_esp_bins\""; //$NON-NLS-1$ - private IOConsoleOutputStream fConsoleErrorOutputStream; private IOConsoleOutputStream fConsoleOutputStream; @@ -51,7 +47,6 @@ public class StreamListener implements IStreamListener private IdfProcessConsole idfProcessConsole; private boolean fStreamClosed = false; - private boolean fBoardNotSelectedReported = false; private List reHintsList; private final PrintWriter fileWriter; @@ -123,11 +118,7 @@ public void streamAppended(String text, IStreamMonitor monitor) try { - if (line.contains(PROGRAM_ESP_BINS_ERROR_MARKER)) - { - reportBoardNotSelected(); - } - else if (line.startsWith("Error:") && fConsoleErrorOutputStream != null) //$NON-NLS-1$ + if (line.startsWith("Error:") && fConsoleErrorOutputStream != null) //$NON-NLS-1$ { fConsoleErrorOutputStream.write((line + System.lineSeparator()).getBytes()); fConsoleErrorOutputStream.flush(); @@ -172,33 +163,6 @@ else if (fConsoleOutputStream != null) }); } - /** - * Replaces the raw OpenOCD {@code invalid command name "program_esp_bins"} error with a short, actionable message - * explaining that a board has to be selected for the debug target, followed by a clickable link that opens the - * launch target editor. The message is reported only once per launch so the console is not flooded by the repeated - * low-level TCL errors. - * - * @throws IOException if writing to the console streams fails - */ - private void reportBoardNotSelected() throws IOException - { - if (fBoardNotSelectedReported || fConsoleErrorOutputStream == null) - { - return; - } - fBoardNotSelectedReported = true; - fConsoleErrorOutputStream - .write((Messages.OpenOCDConsole_BoardNotSelectedMessage + System.lineSeparator()).getBytes()); - fConsoleErrorOutputStream.flush(); - - // The link text is turned into a clickable hyperlink by IdfProcessConsole, which opens the launch target editor. - if (fConsoleOutputStream != null) - { - fConsoleOutputStream.write((Messages.OpenOCDConsole_EditTargetLink + System.lineSeparator()).getBytes()); - fConsoleOutputStream.flush(); - } - } - public void closeStreams() { synchronized (fErrorStreamMonitor) diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/BoardNotSelectedStatusHandler.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/BoardNotSelectedStatusHandler.java new file mode 100644 index 000000000..d08b5fffe --- /dev/null +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/BoardNotSelectedStatusHandler.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright 2026 Espressif Systems (Shanghai) PTE LTD. All rights reserved. + * Use is subject to license terms. + *******************************************************************************/ +package com.espressif.idf.debug.gdbjtag.openocd.ui; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.debug.core.IStatusHandler; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.launchbar.core.ILaunchBarManager; +import org.eclipse.launchbar.core.target.ILaunchTarget; +import org.eclipse.launchbar.ui.target.ILaunchTargetUIManager; +import org.eclipse.swt.widgets.Display; + +import com.espressif.idf.core.logging.Logger; +import com.espressif.idf.debug.gdbjtag.openocd.Activator; + +/** + * Shows a clear, actionable dialog when a debug session is started without a board selected for the active launch + * target. Confirming the dialog opens the launch target editor so the user can select a board. + */ +public class BoardNotSelectedStatusHandler implements IStatusHandler +{ + @Override + public Object handleStatus(IStatus status, Object source) throws CoreException + { + Display.getDefault().asyncExec(() -> { + boolean isYes = MessageDialog.openConfirm(Display.getDefault().getActiveShell(), + Messages.BoardNotSelectedDialog_title, Messages.BoardNotSelectedDialog_message); + if (isYes) + { + editActiveLaunchTarget(); + } + }); + return null; + } + + private void editActiveLaunchTarget() + { + try + { + ILaunchBarManager launchBarManager = Activator.getService(ILaunchBarManager.class); + ILaunchTargetUIManager targetUIManager = Activator.getService(ILaunchTargetUIManager.class); + if (launchBarManager == null || targetUIManager == null) + { + return; + } + ILaunchTarget target = launchBarManager.getActiveLaunchTarget(); + if (target != null) + { + targetUIManager.editLaunchTarget(target); + } + } + catch (Exception e) + { + Logger.log(e); + } + } +} diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java index af7d8671e..d56236f54 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/Messages.java @@ -63,8 +63,6 @@ public class Messages public static String TabDebugger_SettingTargetJob; public static String OpenOCDConsole_ErrorGuideMessage; - public static String OpenOCDConsole_BoardNotSelectedMessage; - public static String OpenOCDConsole_EditTargetLink; public static String TabDebugger_noConfigOptions; public static String TabDebugger_noGdbClientExe; @@ -77,6 +75,9 @@ public class Messages public static String ServerTimeoutErrorDialog_message; public static String ServerTimeoutErrorDialog_customAreaMessage; + public static String BoardNotSelectedDialog_title; + public static String BoardNotSelectedDialog_message; + // ------------------------------------------------------------------------ static diff --git a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties index 0556c5c30..5264a1290 100644 --- a/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties +++ b/bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/messages.properties @@ -414,10 +414,12 @@ TabMain_Launch_Config=Launch Configuration: ## Console Messages ## OpenOCDConsole_ErrorGuideMessage=Please refer to the troubleshooting guide below to identify the problem. -OpenOCDConsole_BoardNotSelectedMessage=No board is selected for the debug target. -OpenOCDConsole_EditTargetLink=Click here to edit the target and select a board. ## Timeout Exception Dialog ## ServerTimeoutErrorDialog_customAreaMessage=To increase timeout time visit the Espressif Preference Page. ServerTimeoutErrorDialog_message=Starting OpenOCD timed out. Try to increase the `GDB server launch timeout` ServerTimeoutErrorDialog_title=Problem Occurred + +## Board Not Selected Dialog ## +BoardNotSelectedDialog_title=No board selected +BoardNotSelectedDialog_message=No board is selected. Edit the launch target to select a board? diff --git a/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/Messages.java b/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/Messages.java index d1c1ea8e4..5352d071b 100644 --- a/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/Messages.java +++ b/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/Messages.java @@ -24,6 +24,8 @@ public class Messages extends NLS public static String SerialFlashLaunch_Resume; public static String SerialPortNotFoundTitle; public static String SerialPortNotFoundMsg; + public static String BoardNotSelectedTitle; + public static String BoardNotSelectedMsg; static { // initialize resource bundle diff --git a/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/SerialFlashLaunchConfigDelegate.java b/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/SerialFlashLaunchConfigDelegate.java index a9b2fa6b1..be4b8c0e2 100644 --- a/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/SerialFlashLaunchConfigDelegate.java +++ b/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/SerialFlashLaunchConfigDelegate.java @@ -63,6 +63,7 @@ import com.espressif.idf.core.util.IDFUtil; import com.espressif.idf.core.util.RecheckConfigsHelper; import com.espressif.idf.core.util.StringUtil; +import com.espressif.idf.core.variable.JtagVariableResolver; import com.espressif.idf.launch.serial.util.ESPFlashUtil; import com.espressif.idf.terminal.connector.serial.connector.SerialSettings; import com.espressif.idf.terminal.connector.serial.launcher.SerialLauncherDelegate; @@ -105,6 +106,13 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun } if (ESPFlashUtil.isJtag()) { + // Abort early with a clear message when no board is selected. Otherwise OpenOCD would run without a board + // configuration and fail with the cryptic "invalid command name \"program_esp_bins\"" error. + if (!isBoardConfigured(configuration)) + { + showBoardNotSelectedMessage(configuration); + return; + } ESPFlashUtil.flashOverJtag(configuration, launch); return; } @@ -271,6 +279,52 @@ private static void showMessage(ILaunchConfiguration configuration) }); } + /** + * Determines whether a board configuration is available for JTAG flashing. A board is considered configured when it + * is resolvable from the active launch target, or when the (possibly manually edited) resolved JTAG flash arguments + * already reference a board configuration file. + * + * @param configuration the launch configuration + * @return {@code true} if a board configuration is available, {@code false} otherwise + */ + private boolean isBoardConfigured(ILaunchConfiguration configuration) + { + if (JtagVariableResolver.isBoardConfigResolvable()) + { + return true; + } + + // Fall back to inspecting the resolved JTAG flash arguments for a manually configured board file. + try + { + String arguments = configuration.getAttribute(IDFLaunchConstants.ATTR_JTAG_FLASH_ARGUMENTS, + StringUtil.EMPTY); + String resolved = VariablesPlugin.getDefault().getStringVariableManager() + .performStringSubstitution(arguments); + return resolved != null && resolved.contains("board/"); //$NON-NLS-1$ + } + catch (CoreException e) + { + Logger.log(e); + } + return false; + } + + private static void showBoardNotSelectedMessage(ILaunchConfiguration configuration) + { + Display.getDefault().asyncExec(() -> { + boolean isYes = MessageDialog.openConfirm(Display.getDefault().getActiveShell(), + com.espressif.idf.launch.serial.internal.Messages.BoardNotSelectedTitle, + com.espressif.idf.launch.serial.internal.Messages.BoardNotSelectedMsg); + if (isYes) + { + ILaunchTargetUIManager targetUIManager = Activator.getService(ILaunchTargetUIManager.class); + ILaunchTargetManager launchTargetManager = Activator.getService(ILaunchTargetManager.class); + targetUIManager.editLaunchTarget(launchTargetManager.getDefaultLaunchTarget(configuration)); + } + }); + } + @Override public boolean buildForLaunch(ILaunchConfiguration configuration, String mode, ILaunchTarget target, IProgressMonitor monitor) throws CoreException diff --git a/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/messages.properties b/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/messages.properties index 304a39100..cb7bfd4ad 100644 --- a/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/messages.properties +++ b/bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/messages.properties @@ -11,4 +11,6 @@ SerialFlashLaunch_Pause=Pausing serial port SerialFlashLaunch_Resume=Resuming serial port SerialPortNotFoundTitle=Serial port not found -SerialPortNotFoundMsg=The serial port was not found. Please select it first and flash the project again. \ No newline at end of file +SerialPortNotFoundMsg=The serial port was not found. Please select it first and flash the project again. +BoardNotSelectedTitle=No board selected +BoardNotSelectedMsg=No board is selected. Edit the launch target to select a board? \ No newline at end of file