Skip to content

WIP: Update esp manager UI#1400

Open
sigmaaa wants to merge 3 commits intomasterfrom
update_esp_manager_ui
Open

WIP: Update esp manager UI#1400
sigmaaa wants to merge 3 commits intomasterfrom
update_esp_manager_ui

Conversation

@sigmaaa
Copy link
Collaborator

@sigmaaa sigmaaa commented Feb 16, 2026

Description

Please include a summary of the change and which issue is fixed.

Fixes # (IEP-XXX)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How has this been tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Test A
  • Test B

Test Configuration:

  • ESP-IDF Version:
  • OS (Windows,Linux and macOS):

Dependent components impacted by this PR:

  • Component 1
  • Component 2

Checklist

  • PR Self Reviewed
  • Applied Code formatting
  • Added Documentation
  • Added Unit Test
  • Verified on all platforms - Windows,Linux and macOS

Summary by CodeRabbit

  • New Features

    • Redesigned ESP-IDF version manager with a multi-column table displaying Status, Version, Name, and Location for each installation.
    • Added "Activate" and "Update Environment" actions for managing ESP-IDF versions.
    • Asynchronous installation tracking with real-time UI updates.
  • UI/UX Improvements

    • Updated button label to "Manage ESP-IDF Versions" for clarity.
    • Improved guidance text for selecting and activating ESP-IDF versions.

@sigmaaa sigmaaa self-assigned this Feb 16, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

The PR converts ESPIDFMainTablePage from a singleton pattern to direct instantiation via constructor, introduces a multi-column TableViewer with IdfRow data model replacing the previous single-column editor, adds async processing with CompletableFuture, and updates all call sites accordingly. UI messages for guidance text and button labels are updated to reflect the new table-based interaction model.

Changes

Cohort / File(s) Summary
Singleton Pattern Migration
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/ESPIDFManagerEditor.java, bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/watcher/EimJsonUiChangeHandler.java
Replaced singleton accessor ESPIDFMainTablePage.getInstance(eimJson) with direct constructor calls new ESPIDFMainTablePage(eimJson) across instantiation sites.
Main Table Page Refactoring
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java
Transitioned from singleton pattern to public constructor; redesigned UI from single-column editor to multi-column TableViewer with IdfRow data model; introduced async processing via CompletableFuture for data refresh; added sorting via IdViewerComparator; implemented button state management for Activate/Update actions; added public lifecycle methods (createPage, setFocus, refreshEditorUI, setupInitialEspIdf).
UI Messages
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties
Updated IDFGuideLinkLabel_Text guidance text for table-based selection workflow; renamed EIMButtonLaunchText to "Manage ESP-IDF Versions"; added MsgYes and MsgNo properties.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • kolipakakondal
  • AndriiFilippov

Poem

🐰 From singleton's grip, we break free,
New constructors bloom, wild and spry!
Columns dance in the TableViewer's way,
Async futures light up the day,
ESP-IDF management hops on! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "WIP: Update esp manager UI" is vague and generic, using non-descriptive terms that don't convey the specific nature of the changes made to the codebase. Replace with a more specific title that describes the main change, such as: "Refactor ESPIDFMainTablePage to use TableViewer with multi-column layout and remove singleton pattern" or similar descriptive phrasing.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch update_esp_manager_ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java (4)

224-250: Display.getDefault() called repeatedly inside label provider methods.

getForeground() calls Display.getDefault().getSystemColor(...) up to two times per cell render. While not a correctness bug, it's more idiomatic and slightly more efficient to obtain the display from the viewer's control once.

Sketch
 			`@Override`
 			public Color getForeground(Object element)
 			{
 				var row = (IdfRow) element;
+				var display = Display.getCurrent();
 				if (row.original().equals(currentInstallingNode))
-					return Display.getDefault().getSystemColor(SWT.COLOR_DARK_YELLOW);
-				return row.isActive() ? Display.getDefault().getSystemColor(SWT.COLOR_DARK_GREEN)
-						: Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
+					return display.getSystemColor(SWT.COLOR_DARK_YELLOW);
+				return row.isActive() ? display.getSystemColor(SWT.COLOR_DARK_GREEN)
+						: display.getSystemColor(SWT.COLOR_GRAY);
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`
around lines 224 - 250, The label provider is repeatedly calling
Display.getDefault() in getForeground; instead capture the Display once from the
viewer (e.g. final Display display = viewer.getControl().getDisplay()) before
calling createCol and use display.getSystemColor(...) inside the anonymous
ColumnLabelProvider methods; update the references in getForeground (and
anywhere else in this provider) to use that cached display and avoid repeated
Display.getDefault() calls, keeping the rest of the logic (IdfRow,
currentInstallingNode, isActive) unchanged.

341-356: Dead code: else if (first instanceof IdfInstalled) branch is unreachable.

The table input is always List<IdfRow> (set at line 406), so selection.getFirstElement() will always be an IdfRow, never a raw IdfInstalled. This branch adds confusion with no benefit.

Proposed fix
 	private IdfInstalled getSelectedIdf()
 	{
 		var selection = (IStructuredSelection) tableViewer.getSelection();
 		if (selection.isEmpty())
 			return null;
 		Object first = selection.getFirstElement();
 		if (first instanceof IdfRow firstRow)
 		{
 			return firstRow.original();
 		}
-		else if (first instanceof IdfInstalled rawInstalled)
-		{
-			return rawInstalled;
-		}
 		return null;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`
around lines 341 - 356, The getSelectedIdf method contains an unreachable
else-if branch checking "first instanceof IdfInstalled"; since the table input
is always List<IdfRow>, remove the unreachable branch and simplify the method to
only handle IdfRow cases: cast/unwrap the first selection to IdfRow (using
IdfRow firstRow) and return firstRow.original(), keeping the existing
empty-selection/null handling and preserving use of tableViewer and
IStructuredSelection.

85-101: Unused LocalResourceManager — created but never stored or used.

Line 87 creates a LocalResourceManager whose reference is immediately discarded. If no managed resources (images, colors, fonts) are allocated through it, the allocation is pointless. If managed resources are planned, store the reference in a field so it can be used.

Proposed fix: remove unused allocation
 	public Composite createPage(Composite parent)
 	{
-		new LocalResourceManager(JFaceResources.getResources(), parent);
-
 		container = new Composite(parent, SWT.NONE);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`
around lines 85 - 101, The LocalResourceManager instance created in createPage
(new LocalResourceManager(JFaceResources.getResources(), parent)) is never
stored or used; either remove that unused allocation or assign it to a field
(e.g., a private LocalResourceManager resourceManager) and use it for any
managed resources and dispose it in the page's lifecycle (e.g., on dispose) to
avoid leaks; update createPage to set resourceManager = new
LocalResourceManager(...) and ensure disposeResourceManager is called when the
page/container is disposed.

374-391: Unchecked RuntimeException wrapping loses the original exception's context in logs.

Line 390 wraps the caught exception in a RuntimeException, and the exceptionally handler at line 410 logs only ex.getCause().toString(). The stack trace is lost. Consider logging with Logger.log(Logger.ERROR, ...) or similar that preserves the stack, or at minimum log ex.getCause() with its stack trace.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`
around lines 374 - 391, The supplyAsync block in ESPIDFMainTablePage catches
exceptions and throws new RuntimeException(e), which loses stacktrace clarity
and pairs poorly with the existing exceptionally handler that logs
ex.getCause().toString(); replace the throw with a CompletionException(e) (or
simply let the exception propagate) so CompletableFuture preserves the original
cause, and update the exceptionally handler that currently logs
ex.getCause().toString() to log the full throwable (e.g.,
Logger.log(Logger.ERROR, "Failed to load IDF rows", ex.getCause()) or
logger.error("...", ex.getCause())) so the stacktrace is preserved; look for
CompletableFuture.supplyAsync(...) and the matching exceptionally(...) block to
apply these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 130-198: Replace all hardcoded user-visible strings in
createMainContent and associated UI code with entries from the Messages resource
bundle: change group.setText("Installed IDF Versions") to
group.setText(Messages.EspIdfManagerInstalledVersions), change the
createActionButton label/tooltips ("Activate", "Set this version as the active
ESP-IDF", "Update Environment", the long tooltip) to Messages keys (e.g.,
Messages.EspIdfManagerActivate, Messages.EspIdfManagerActivateTooltip,
Messages.EspIdfManagerUpdateEnvironment,
Messages.EspIdfManagerUpdateEnvironmentTooltip), and replace the
column/header/status strings ("Status", "Setting up...", "\u2713 Active") used
in createColumns or wherever status text is emitted with
Messages.EspIdfManagerStatus, Messages.EspIdfManagerSettingUp,
Messages.EspIdfManagerActive (or similar keys). Add the corresponding keys to
the Messages properties and Java Messages accessor class following the existing
pattern used for Messages.EspIdfManagerVersionCol so that all UI text is
externalized for i18n.

---

Duplicate comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 469-483: The comparator in compare(Viewer, Object, Object) casts
to IdfRow and calls compareToIgnoreCase on r1.version()/name()/path(), which can
NPE if those fields are null; modify the switch in compare to perform null-safe
comparisons (e.g., normalize to empty string or use explicit null checks and
decide null ordering) for cases 1, 2, 3 so compareToIgnoreCase is never invoked
on null, and keep the existing Boolean.compare for case 0 and the direction flip
using (direction == SWT.UP) ? -rc : rc unchanged; locate this logic in the
compare method of ESPIDFMainTablePage.java where propertyIndex is switched and
update those branches to handle nulls consistently.
- Around line 7-8: The code currently imports and uses AWT's java.awt.Desktop
(and java.net.URI) in ESPIDFMainTablePage; replace any
Desktop.getDesktop().browse(...) or Desktop.open(...) calls with the SWT-native
Program.launch(...) API: import org.eclipse.swt.program.Program, convert the URI
to a String (uri.toString() or the original URL string) and call
Program.launch(urlString); remove the java.awt.Desktop import and the direct
java.net.URI use if not needed (or keep URI only to build the string), and
adjust exception handling accordingly since Program.launch returns boolean
rather than throwing IOExceptions.
- Around line 374-412: The code calls configParser.getEimJson(true) on the
background thread but then re-calls configParser.getEimJson(false) on the UI
thread (in ESPIDFMainTablePage’s CompletableFuture.thenAcceptAsync block),
risking inconsistent state and UI blocking; fix by computing and capturing the
parsed EIM JSON on the background thread (the CompletableFuture.supplyAsync that
creates rows) and pass or store that result so the UI thread reuses it instead
of calling getEimJson(false). Concretely, modify the supplyAsync work (the
lambda that calls configParser.getEimJson(true) and builds IdfRow list) to also
return or set the EIM JSON (e.g., return a small holder object or set
this.eimJson inside the background thread safely) and remove the call to
configParser.getEimJson(false) from the thenAcceptAsync block so
updateLaunchButtonState(), tableViewer.setInput(...), and updateButtonState()
use the already-parsed EIM JSON and the computed rows.
- Around line 59-61: IdfRow's components (version, name, path) can be null
causing NPEs in label providers and the comparator; add a compact constructor to
the record IdfRow that normalizes null Strings to "" (empty string) for the
version, name and path fields so callers (where IdfRow is constructed from
ToolsUtility.getIdfVersion(...), idf.getName(), idf.getPath()) never get null
values; update the compact constructor inside IdfRow to coerce null -> "" for
each String parameter and leave original and isActive unchanged so existing
label provider code and the comparator continue to work without NPEs.

---

Nitpick comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 224-250: The label provider is repeatedly calling
Display.getDefault() in getForeground; instead capture the Display once from the
viewer (e.g. final Display display = viewer.getControl().getDisplay()) before
calling createCol and use display.getSystemColor(...) inside the anonymous
ColumnLabelProvider methods; update the references in getForeground (and
anywhere else in this provider) to use that cached display and avoid repeated
Display.getDefault() calls, keeping the rest of the logic (IdfRow,
currentInstallingNode, isActive) unchanged.
- Around line 341-356: The getSelectedIdf method contains an unreachable else-if
branch checking "first instanceof IdfInstalled"; since the table input is always
List<IdfRow>, remove the unreachable branch and simplify the method to only
handle IdfRow cases: cast/unwrap the first selection to IdfRow (using IdfRow
firstRow) and return firstRow.original(), keeping the existing
empty-selection/null handling and preserving use of tableViewer and
IStructuredSelection.
- Around line 85-101: The LocalResourceManager instance created in createPage
(new LocalResourceManager(JFaceResources.getResources(), parent)) is never
stored or used; either remove that unused allocation or assign it to a field
(e.g., a private LocalResourceManager resourceManager) and use it for any
managed resources and dispose it in the page's lifecycle (e.g., on dispose) to
avoid leaks; update createPage to set resourceManager = new
LocalResourceManager(...) and ensure disposeResourceManager is called when the
page/container is disposed.
- Around line 374-391: The supplyAsync block in ESPIDFMainTablePage catches
exceptions and throws new RuntimeException(e), which loses stacktrace clarity
and pairs poorly with the existing exceptionally handler that logs
ex.getCause().toString(); replace the throw with a CompletionException(e) (or
simply let the exception propagate) so CompletableFuture preserves the original
cause, and update the exceptionally handler that currently logs
ex.getCause().toString() to log the full throwable (e.g.,
Logger.log(Logger.ERROR, "Failed to load IDF rows", ex.getCause()) or
logger.error("...", ex.getCause())) so the stacktrace is preserved; look for
CompletableFuture.supplyAsync(...) and the matching exceptionally(...) block to
apply these changes.

Comment on lines +130 to +198
private void createMainContent(Composite parent)
{
if (idfInstalledList != null && idfInstalledList.size() == 1)
var group = new Group(parent, SWT.SHADOW_ETCHED_IN);
group.setText("Installed IDF Versions");
group.setLayout(new GridLayout(2, false));
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

// --- Table ---
var tableComp = new Composite(group, SWT.NONE);
var tableLayout = new TableColumnLayout();
tableComp.setLayout(tableLayout);
tableComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

tableViewer = new TableViewer(tableComp, SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL);
var table = tableViewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
table.addListener(SWT.MeasureItem, e -> e.height = 28);

tableViewer.setContentProvider(ArrayContentProvider.getInstance());
tableViewer.setComparator(comparator);

createColumns(tableViewer, tableLayout);

// --- Buttons ---
var buttonComp = new Composite(group, SWT.NONE);
buttonComp.setLayout(new GridLayout(1, true));
buttonComp.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));

btnActivate = createActionButton(buttonComp, "Activate", "Set this version as the active ESP-IDF");
btnReinstall = createActionButton(buttonComp, "Update Environment",
"Refresh toolchains, Python virtual environment, and IDE settings for the CURRENTLY ACTIVE version");

// --- Listeners ---
tableViewer.addSelectionChangedListener(event -> updateButtonState());

tableViewer.addDoubleClickListener(event -> {
var idf = getSelectedIdf();
if (idf != null && !idf.equals(currentInstallingNode) && !ToolsUtility.isIdfInstalledActive(idf))
{
performToolsSetup(idf);
}

});

SelectionAdapter btnListener = new SelectionAdapter()
{
// activate the only available esp-idf first check if its not already active
Preferences scopedPreferenceStore = InstanceScope.INSTANCE.getNode(UIPlugin.PLUGIN_ID);
if (!scopedPreferenceStore.getBoolean(EimConstants.INSTALL_TOOLS_FLAG, false))
@Override
public void widgetSelected(SelectionEvent e)
{
SetupToolsInIde setupToolsInIde = new SetupToolsInIde(idfInstalledList.get(0), eimJson,
getConsoleStream(true), getConsoleStream(false));
SetupToolsJobListener toolsActivationJobListener = new SetupToolsJobListener(ESPIDFMainTablePage.this,
setupToolsInIde);
setupToolsInIde.addJobChangeListener(toolsActivationJobListener);
setupToolsInIde.schedule();
if (e.widget == btnActivate)
{
// Activate depends on SELECTION
var idf = getSelectedIdf();
if (idf != null)
performToolsSetup(idf);
}
else if (e.widget == btnReinstall)
{
// Update Environment depends on ACTIVE STATUS (ignores selection)
performUpdateOnActiveIdf();
}
}
};
btnActivate.addSelectionListener(btnListener);
btnReinstall.addSelectionListener(btnListener);

updateButtonState();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded UI strings should be externalized to Messages.

Lines 133 ("Installed IDF Versions"), 159 ("Activate", "Set this version as the active ESP-IDF"), 160-161 ("Update Environment", tooltip text), 224 ("Status"), 231 ("Setting up..."), and 232 ("\u2713 Active") are all user-visible strings that bypass the project's Messages externalization pattern already used elsewhere in this file (e.g., Messages.EspIdfManagerVersionCol). This breaks i18n/l10n support.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`
around lines 130 - 198, Replace all hardcoded user-visible strings in
createMainContent and associated UI code with entries from the Messages resource
bundle: change group.setText("Installed IDF Versions") to
group.setText(Messages.EspIdfManagerInstalledVersions), change the
createActionButton label/tooltips ("Activate", "Set this version as the active
ESP-IDF", "Update Environment", the long tooltip) to Messages keys (e.g.,
Messages.EspIdfManagerActivate, Messages.EspIdfManagerActivateTooltip,
Messages.EspIdfManagerUpdateEnvironment,
Messages.EspIdfManagerUpdateEnvironmentTooltip), and replace the
column/header/status strings ("Status", "Setting up...", "\u2713 Active") used
in createColumns or wherever status text is emitted with
Messages.EspIdfManagerStatus, Messages.EspIdfManagerSettingUp,
Messages.EspIdfManagerActive (or similar keys). Add the corresponding keys to
the Messages properties and Java Messages accessor class following the existing
pattern used for Messages.EspIdfManagerVersionCol so that all UI text is
externalized for i18n.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant