Skip to content

IEP-1701 Improve UI/UX of esp-idf manager view#1400

Open
sigmaaa wants to merge 15 commits intomasterfrom
update_esp_manager_ui
Open

IEP-1701 Improve UI/UX of esp-idf manager view#1400
sigmaaa wants to merge 15 commits intomasterfrom
update_esp_manager_ui

Conversation

@sigmaaa
Copy link
Collaborator

@sigmaaa sigmaaa commented Feb 16, 2026

Description

Updated the UI to resolve dark theme color issues and rendering glitches. The interface is now more closely aligned with Eclipse UI guidelines. Additionally, sorting functionality has been improved.
image

Fixes # (IEP-1701)

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How has this been tested?

Open the ESP-IDF Manager: The default sorting should be by ESP-IDF version.
Verify State Persistence: Change the sort order, then close and reopen the manager; the sorting preference should be saved.
Test Buttons: Verify the activation and refresh buttons. Note that "Refresh" should reactivate the currently selected target.

Test Configuration:

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

Dependent components impacted by this PR:

  • ESP-IDF Manager

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, sortable table and persistent sort state.
    • Asynchronous loading and dynamic row states with Activate Selected, Refresh Environment, and installation tracking.
    • Improved selection and double-click activation flows; UI refreshes for external changes.
  • UI/UX Improvements

    • Button relabeled to "Manage ESP-IDF Versions".
    • Clarified guidance text for selecting/double-clicking to activate.
    • Standardized confirmation labels ("Yes"/"No").

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

coderabbitai bot commented Feb 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaced singleton-based creation of ESPIDFMainTablePage with direct construction; ESPIDFMainTablePage was refactored into a constructor + createPage lifecycle, converting the UI to a multi-column TableViewer with an IdfRow model, async data loading, persistent sort state, and updated editor call sites and messages.

Changes

Cohort / File(s) Summary
Editor call sites
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 ESPIDFMainTablePage.getInstance(eimJson) calls with new ESPIDFMainTablePage(eimJson); no other control flow changes at these sites.
Main table page & UI refactor
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java
Added public constructor ESPIDFMainTablePage(EimJson) and createPage(Composite). Rewrote UI to a multi-column TableViewer with IdfRow model, label providers, IdViewerComparator with persisted sort prefs, asynchronous row loading via CompletableFuture, selection-based Activate/Refresh flows, console/job listener integration, and updated action buttons and rendering.
Localization / messages
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties
Updated guidance text (IDFGuideLinkLabel_Text), changed EIMButtonLaunchText to "Manage ESP-IDF Versions", and added MsgYes / MsgNo entries.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as ESPIDFManagerEditor
    participant Page as ESPIDFMainTablePage
    participant EIM as EimJson (data/watch)
    participant Job as SetupToolsInIdeJob
    participant Listener as SetupToolsJobListener

    Editor->>Page: new ESPIDFMainTablePage(eimJson)
    Editor->>Page: createPage(parent)
    Page->>Page: build TableViewer, columns, actions
    Page->>EIM: request rows (async CompletableFuture)
    EIM-->>Page: return eim data / installed list (async)
    Page->>Page: map to IdfRow, update TableViewer (UI thread)
    Page->>Job: schedule setup job for selected row
    Job->>Listener: register listener
    Job-->>Listener: progress/completion events
    Listener->>Page: trigger refreshEditorUI()
    Page->>Editor: update editor state / buttons / focus
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • AndriiFilippov
  • kolipakakondal
  • alirana01

Poem

🐰 I hopped from singleton to open air,
Rows now gleam with columns fair,
Futures fetch the data bright,
Buttons hum and jobs take flight,
A tiny rabbit cheers the UI tonight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'IEP-1701 Improve UI/UX of esp-idf manager view' directly aligns with the changeset, which refactors the ESP-IDF manager UI with improved layout, interaction patterns, and asynchronous data handling.
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 unit tests (beta)
  • Create PR with unit tests
  • 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.

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 (3)
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java (3)

465-469: prefs local variable shadows the prefs field — use the existing field directly.

Line 469 creates a local var prefs = InstanceScope.INSTANCE.getNode(UIPlugin.PLUGIN_ID) which is identical to the private final IEclipsePreferences prefs declared at Line 69. The field can be used directly.

♻️ Proposed fix
 	public void setupInitialEspIdf()
 	{
 		if (tableViewer.getInput() instanceof List<?> list && !list.isEmpty())
 		{
-			var prefs = InstanceScope.INSTANCE.getNode(UIPlugin.PLUGIN_ID);
-			if (!prefs.getBoolean(EimConstants.INSTALL_TOOLS_FLAG, false))
+			if (!this.prefs.getBoolean(EimConstants.INSTALL_TOOLS_FLAG, false))
🤖 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 465 - 469, The local variable prefs declared inside
setupInitialEspIdf shadows the class field prefs; remove the local declaration
and use the existing private final IEclipsePreferences prefs field instead
(replace the local "var prefs =
InstanceScope.INSTANCE.getNode(UIPlugin.PLUGIN_ID)" usage with the field prefs)
so the method uses the class-level prefs consistently.

99-99: LocalResourceManager created but reference immediately discarded — dead code.

LocalResourceManager is designed to manage resources tied to a widget's lifetime; the typical pattern is to store the reference and use it to create Color/Font/Image objects. Here the result is thrown away, and getImage returns null everywhere, so no resources are ever allocated through it. Either store it as a field and use it to produce images/colors, or remove the line entirely.

♻️ Proposed fix
-		new LocalResourceManager(JFaceResources.getResources(), parent);
+		// Remove if no SWT resources are managed, or assign and use:
+		// resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
🤖 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`
at line 99, The LocalResourceManager instance created in ESPIDFMainTablePage is
discarded immediately (new LocalResourceManager(...);) so resources are never
allocated and getImage returns null; either store that LocalResourceManager in a
field (e.g., add a private LocalResourceManager resourceManager) and use it to
create and manage Images/Colors/Fonts used by getImage and other UI methods, or
remove the creation entirely and ensure any existing getImage calls are updated
to use the correct resource provider; update dispose/cleanup logic to dispose
the stored resourceManager when the page is disposed.

131-131: Logger.log(e.toString()) loses the stack trace — prefer Logger.log(e).

All three sites pass e.toString() which invokes the Logger.log(String) overload, logging only the message at INFO severity. The Logger.log(Exception) overload records the full stack trace at ERROR severity, which is the correct choice for caught exceptions.

♻️ Proposed fix (apply to all three sites)
-				Logger.log(ex.toString());    // Line 131
+				Logger.log(ex);

-			Logger.log(e.toString());         // Line 328
+			Logger.log(e);

-				Logger.log(e.toString());     // Line 443
+				Logger.log(e);

For the exceptionally handler (Line 450):

-		Logger.log(ex.getCause() != null ? ex.getCause().toString() : ex.toString());
+		Throwable cause = ex.getCause() != null ? ex.getCause() : ex;
+		Logger.log(cause instanceof Exception ec ? ec : new RuntimeException(cause));

Also applies to: 328-328, 443-444

🤖 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`
at line 131, Replace the three calls that pass the exception as a string to
Logger (the occurrences of Logger.log(ex.toString()) in ESPIDFMainTablePage)
with the overload that accepts the Throwable object (Logger.log(ex)) so the full
stack trace is recorded at ERROR severity; update each exceptionally/catch
handler where Logger.log(e.toString()) is used to call Logger.log(e) instead.
🤖 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 409-413: The bug is that refreshEditorUI() is being invoked on
throwaway ESPIDFMainTablePage instances whose container is null, so file-watcher
notifications are dropped; instead, change callers (EimJsonUiChangeHandler and
wherever a new ESPIDFMainTablePage is created and passed into
SetupToolsJobListener) to resolve and use the actual displayed page/editor
instance (e.g., obtain the live ESPIDFManagerEditor via the editor input or
IWorkbenchPage.findEditor() and call getActivePage()/the existing
ESPIDFMainTablePage) rather than constructing a new page or passing an
uninitialized instance; update EimJsonUiChangeHandler and SetupToolsJobListener
to accept/retrieve the real page reference and only call refreshEditorUI() on
that initialized instance (or expose a getter on ESPIDFManagerEditor to retrieve
the initialized ESPIDFMainTablePage) so container is non-null when
refreshEditorUI() runs.

---

Duplicate comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 138-222: The UI contains hardcoded user-visible strings in
createMainContent (e.g., Group title "Installed IDF Versions", button
labels/tooltips "Activate Selected", "Set this version as the active ESP-IDF",
"Refresh Environment" and its tooltip, plus status texts "Setting up..." and
"\u2713 Active") that bypass the NLS Messages pattern; replace each literal with
the appropriate Messages.<KEY> constant (or add new keys to the Messages
resource bundle and Messages class) and use those message fields where the
literals appear (references: createMainContent method, eimLaunchBtn/button
creation, table double-click handling/status text setters), then update the
Messages properties files and regenerate or update the Messages class so the UI
strings are localized.
- Around line 414-452: The code currently calls configParser.getEimJson(true) in
the background task and then calls configParser.getEimJson(false) again on the
UI thread, risking inconsistent snapshots and UI-thread I/O; fix this by
capturing the EIM JSON object returned by getEimJson(true) inside the
supplyAsync task (e.g. keep the newJson variable) and pass that parsed snapshot
to the UI continuation instead of re-calling configParser.getEimJson(false); set
this.eimJson = capturedNewJson (or its immutable/readonly representation) on the
UI thread, avoid any additional parsing or I/O there, and keep using the
already-built rows list for tableViewer.setInput and subsequent logic
(references: configParser.getEimJson, supplyAsync lambda, thenAcceptAsync
lambda, this.eimJson, tableViewer).
- Around line 520-534: The comparator in compare(Viewer, Object, Object) on
IdfRow uses version(), name(), and path() which may be null and can throw NPEs
during compareToIgnoreCase; make the comparisons null-safe by normalizing nulls
before comparing (e.g., treat null as "" or use explicit null checks) or use a
null-safe comparator equivalent (Comparator.nullsFirst/Last semantics) for the
cases where propertyIndex == 1, 2, 3; update the switch branch logic in
compare(...) so r1.version()/name()/path() and r2.* are checked/normalized
before calling compareToIgnoreCase and preserve the existing direction handling
with (direction == SWT.UP) ? -rc : rc.
- Around line 7-8: The java.awt.Desktop import and its usage (Desktop and
java.net.URI) must be removed and replaced with an Eclipse/SWT-friendly API;
locate the code that calls Desktop.browse(new URI(...)) (the Desktop symbol) and
change it to use org.eclipse.swt.program.Program.launch(url) with a String URL
(or use
PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(URL)
if you want the workbench external browser), then remove the unused
java.awt.Desktop and java.net.URI imports; ensure any exception handling is
adapted for URL/Program.launch usage and update imports accordingly.

---

Nitpick comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 465-469: The local variable prefs declared inside
setupInitialEspIdf shadows the class field prefs; remove the local declaration
and use the existing private final IEclipsePreferences prefs field instead
(replace the local "var prefs =
InstanceScope.INSTANCE.getNode(UIPlugin.PLUGIN_ID)" usage with the field prefs)
so the method uses the class-level prefs consistently.
- Line 99: The LocalResourceManager instance created in ESPIDFMainTablePage is
discarded immediately (new LocalResourceManager(...);) so resources are never
allocated and getImage returns null; either store that LocalResourceManager in a
field (e.g., add a private LocalResourceManager resourceManager) and use it to
create and manage Images/Colors/Fonts used by getImage and other UI methods, or
remove the creation entirely and ensure any existing getImage calls are updated
to use the correct resource provider; update dispose/cleanup logic to dispose
the stored resourceManager when the page is disposed.
- Line 131: Replace the three calls that pass the exception as a string to
Logger (the occurrences of Logger.log(ex.toString()) in ESPIDFMainTablePage)
with the overload that accepts the Throwable object (Logger.log(ex)) so the full
stack trace is recorded at ERROR severity; update each exceptionally/catch
handler where Logger.log(e.toString()) is used to call Logger.log(e) instead.

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: 2

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

99-99: Remove the unused LocalResourceManager allocation.

The LocalResourceManager is constructed but never assigned to a field or local variable. No resources (images, colours) are ever allocated through it, so it only attaches a no-op dispose listener to parent. Either store it in a field if you plan to create managed resources, or remove the line.

♻️ Proposed fix
-		new LocalResourceManager(JFaceResources.getResources(), parent);
-
🤖 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`
at line 99, The code creates a LocalResourceManager via new
LocalResourceManager(JFaceResources.getResources(), parent) inside
ESPIDFMainTablePage but never stores or uses it; remove that unused allocation
line or assign it to a field (e.g., a private LocalResourceManager
resourceManager) and use it for managed resources, ensuring you dispose it in
the class' dispose() method; if you choose removal, simply delete the new
LocalResourceManager(...) statement to avoid the no-op listener.

391-394: Dead IdfInstalled branch in getSelectedIdf() is unreachable.

tableViewer.setInput(rows) always provides a List<IdfRow>; ArrayContentProvider surfaces those as IdfRow elements. The else if (first instanceof IdfInstalled rawInstalled) branch will never execute.

♻️ Proposed fix
-		else if (first instanceof IdfInstalled rawInstalled)
-		{
-			return rawInstalled;
-		}
🤖 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 391 - 394, The else-if branch checking "first instanceof
IdfInstalled rawInstalled" in getSelectedIdf() is unreachable because
tableViewer (with ArrayContentProvider) always exposes IdfRow elements; remove
that branch and simplify getSelectedIdf() to only handle IdfRow instances
(extract the IdfInstalled from the IdfRow or return null if none). Update
getSelectedIdf() to return the IdfInstalled via the existing IdfRow path (or
adjust the IdfRow handling to expose the installed object) and delete the dead
IdfInstalled branch so the method reflects the actual tableViewer input types.
🤖 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 465-477: Add a null-check for the UI viewer and use the viewer's
displayed-first-element instead of the backing list: in setupInitialEspIdf(),
return early if tableViewer is null (same guard pattern used in
refreshEditorUI/createMainContent) to avoid NPE, then check
prefs.getBoolean(EimConstants.INSTALL_TOOLS_FLAG, false) and that tableViewer
has elements, obtain the first displayed element with
tableViewer.getElementAt(0), cast it to IdfRow (as in the current instanceof
check) and call performToolsSetup(selectedRaw.original()) — this preserves the
sorted display order provided by IdViewerComparator and prevents calling
getInput() on a null viewer.
- Around line 446-447: The selection isn't restored because IdfRow's
auto-generated equals() compares the embedded IdfInstalled by reference and
refreshEditorUI rebuilds new IdfInstalled instances, so
setSelection(currentSelection) fails; fix by either (A) implementing equals()
and hashCode() on IdfInstalled to use its unique id field (e.g., override
equals/hashCode to compare getId()), or (B) after tableViewer.setInput(rows)
compute and apply a new selection by finding rows whose IdfInstalled.getId()
matches the previously selected IdfInstalled ids (use IdfRow/IdfInstalled/getId
and tableViewer.setSelection with the matched new IdfRow instances) so selection
is restored reliably.

---

Duplicate comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 138-185: Replace the hardcoded UI strings in createMainContent
with i18n keys: change group.setText("Installed IDF Versions") to use
Messages.<newKeyInstalledVersions>, change btnActivate creation to use
Messages.<newKeyActivateLabel> and its tooltip to
Messages.<newKeyActivateTooltip>, and change btnReinstall creation to use
Messages.<newKeyRefreshLabel> and its tooltip to
Messages.<newKeyRefreshTooltip>; add the corresponding keys and translations to
the Messages properties and update the Messages class accessor if needed, then
rebuild to ensure the calls (group.setText(...), createActionButton(...) for
btnActivate and btnReinstall) reference the new Messages constants instead of
literal strings.
- Around line 7-8: Remove the AWT import and any usage of java.awt.Desktop in
ESPIDFMainTablePage; replace calls that open external URLs with an SWT-safe
mechanism such as org.eclipse.swt.program.Program.launch(String url) or the
Eclipse browser API
(PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(new
URL(url))). Update imports to remove java.awt.Desktop and java.net.URI (if only
used with Desktop) and import org.eclipse.swt.program.Program or
org.eclipse.ui.PlatformUI/BrowserSupport, and ensure URL-opening logic in
methods of ESPIDFMainTablePage uses the chosen SWT-safe call to avoid AWT/SWT
conflicts on macOS.
- Around line 518-532: The compare method in the ESPI DFMainTablePage comparator
can NPE because IdfRow.version(), name(), and path() may be null; update the
comparator (inside the overridden compare in ESPI DFMainTablePage) to perform
null-safe string comparisons for cases 1–3 (e.g., normalize null to "" or use
nullsFirst/nullsLast semantics) before calling compareToIgnoreCase, and ensure
the direction handling with SWT.UP remains the same; reference the
compare(Viewer, Object, Object) method and the IdfRow.version()/name()/path()
accessors when making the change.
- Around line 409-413: The refreshEditorUI() call on throwaway
ESPIDFMainTablePage instances (constructed in EimJsonUiChangeHandler) is a no-op
because container is null when createPage() was never run; instead of new
ESPIDFMainTablePage(eimJson) create/using the real live page instance: locate
the active/registered ESPIDFMainTablePage (via the editor/page manager or the
existing editor that hosts ESPIDFMainTablePage), then call refreshEditorUI() on
that resolved instance (or invoke createPage()/init lifecycle first if you must
construct one) so container is non-null; update EimJsonUiChangeHandler to
resolve the page rather than instantiating a throwaway.
- Around line 414-452: The code double-parses EIM JSON: getEimJson(true) is
called inside the CompletableFuture.supplyAsync and then getEimJson(false) is
called again on the UI thread in thenAcceptAsync, risking inconsistent data and
UI-thread I/O; fix it by returning the parsed EimJson from the supplier along
with the List<IdfRow> (e.g. create a small result record/class like ParsedRows {
EimJson eimJson; List<IdfRow> rows; } or a Pair) so the supplier builds both
rows and the EimJson using configParser.getEimJson(true) and thenAcceptAsync
receives that single result and assigns this.eimJson = result.eimJson (no second
getEimJson call), updates the UI with result.rows, and removes any I/O on the UI
thread.

---

Nitpick comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Line 99: The code creates a LocalResourceManager via new
LocalResourceManager(JFaceResources.getResources(), parent) inside
ESPIDFMainTablePage but never stores or uses it; remove that unused allocation
line or assign it to a field (e.g., a private LocalResourceManager
resourceManager) and use it for managed resources, ensuring you dispose it in
the class' dispose() method; if you choose removal, simply delete the new
LocalResourceManager(...) statement to avoid the no-op listener.
- Around line 391-394: The else-if branch checking "first instanceof
IdfInstalled rawInstalled" in getSelectedIdf() is unreachable because
tableViewer (with ArrayContentProvider) always exposes IdfRow elements; remove
that branch and simplify getSelectedIdf() to only handle IdfRow instances
(extract the IdfInstalled from the IdfRow or return null if none). Update
getSelectedIdf() to return the IdfInstalled via the existing IdfRow path (or
adjust the IdfRow handling to expose the installed object) and delete the dead
IdfInstalled branch so the method reflects the actual tableViewer input types.

Comment on lines +446 to +447
tableViewer.setInput(rows);
tableViewer.setSelection(currentSelection);
Copy link

@coderabbitai coderabbitai bot Feb 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find bundles -name "IdfInstalled.java" -type f

Repository: espressif/idf-eclipse-plugin

Length of output: 156


🏁 Script executed:

#!/bin/bash
# First, find the IdfInstalled.java file
IDF_INSTALLED_FILE=$(find bundles -name "IdfInstalled.java" -type f)

if [ -n "$IDF_INSTALLED_FILE" ]; then
  echo "=== IdfInstalled.java content ==="
  cat -n "$IDF_INSTALLED_FILE"
  echo ""
  echo "=== Checking for equals/hashCode methods ==="
  rg -n "equals|hashCode" "$IDF_INSTALLED_FILE"
fi

Repository: espressif/idf-eclipse-plugin

Length of output: 1803


🏁 Script executed:

# Find IdfRow to verify it's a record
find bundles -name "IdfRow.java" -type f

Repository: espressif/idf-eclipse-plugin

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Check IdfRow definition
IDF_ROW_FILE=$(find bundles -name "IdfRow.java" -type f)

if [ -n "$IDF_ROW_FILE" ]; then
  echo "=== IdfRow.java content ==="
  cat -n "$IDF_ROW_FILE"
fi

Repository: espressif/idf-eclipse-plugin

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Search for IdfRow definition across the entire codebase
rg -n "class IdfRow|record IdfRow" --type java

Repository: espressif/idf-eclipse-plugin

Length of output: 279


🏁 Script executed:

#!/bin/bash
# Also check the file in question for IdfRow and the context around lines 446-447
FILE="bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java"
echo "=== File size check ==="
wc -l "$FILE"
echo ""
echo "=== Context around lines 440-455 ==="
sed -n '440,455p' "$FILE" | cat -n

Repository: espressif/idf-eclipse-plugin

Length of output: 755


🏁 Script executed:

#!/bin/bash
# Search for IdfRow definition in the same directory or file
rg -n "IdfRow" bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ --type java -B 2 -A 10 | head -100

Repository: espressif/idf-eclipse-plugin

Length of output: 12730


🏁 Script executed:

#!/bin/bash
# Search for currentSelection assignment and usage
rg -n "currentSelection\s*=" bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java -B 3 -A 1

Repository: espressif/idf-eclipse-plugin

Length of output: 257


🏁 Script executed:

#!/bin/bash
# Look for the refreshEditorUI method to understand the refresh flow
rg -n "refreshEditorUI|private.*rows" bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java -A 5 | head -80

Repository: espressif/idf-eclipse-plugin

Length of output: 310


🏁 Script executed:

#!/bin/bash
# Get more context around the refresh method to see how rows are constructed
FILE="bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java"
sed -n '420,460p' "$FILE" | cat -n

Repository: espressif/idf-eclipse-plugin

Length of output: 1588


setSelection(currentSelection) silently fails after every refresh — selection is never restored.

IdfRow is a Java record whose auto-generated equals() includes all component fields, including the original (IdfInstalled) field. IdfInstalled is a plain POJO with no equals()/hashCode() override, so it uses reference equality—two objects are equal only if they refer to the same memory location.

When refreshEditorUI() rebuilds the table, it creates new IdfRow instances with fresh IdfInstalled objects parsed from JSON. The currentSelection captured before the refresh contains old IdfRow wrappers pointing to old IdfInstalled references. After setInput(rows) is called with the new rows, the subsequent setSelection(currentSelection) attempt fails because the old IdfInstalled references in the selection don't match the new object instances in the updated list, so the selection is never restored.

Fix options:

  1. Override equals()/hashCode() on IdfInstalled using its id field.
  2. Re-select by matching on IdfInstalled.getId() manually after setInput.
🤖 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 446 - 447, The selection isn't restored because IdfRow's
auto-generated equals() compares the embedded IdfInstalled by reference and
refreshEditorUI rebuilds new IdfInstalled instances, so
setSelection(currentSelection) fails; fix by either (A) implementing equals()
and hashCode() on IdfInstalled to use its unique id field (e.g., override
equals/hashCode to compare getId()), or (B) after tableViewer.setInput(rows)
compute and apply a new selection by finding rows whose IdfInstalled.getId()
matches the previously selected IdfInstalled ids (use IdfRow/IdfInstalled/getId
and tableViewer.setSelection with the matched new IdfRow instances) so selection
is restored reliably.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@sigmaaa I think this makes sense
After setInput(rows), the table content is new IdfRow instances (with new IdfInstalled references). IdfRow’s generated equals() includes original; IdfInstalled does not override equals()/hashCode(), so comparison is by reference. The old selection no longer matches any element in the new list, so setSelection(currentSelection) has no effect. Fix by either: (A) implementing equals()/hashCode() on IdfInstalled using a stable id (e.g. getId()), or (B) before setInput, storing the selected id(s) (e.g. IdfInstalled.getId()), then after setInput finding the new IdfRow(s) with matching id(s) and calling setSelection with those.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties (1)

14-15: ⚠️ Potential issue | 🟡 Minor

Remove unused message properties MsgYes and MsgNo.

These properties defined at lines 14–15 are not referenced anywhere in the codebase and should be deleted from both messages.properties and their corresponding field declarations in Messages.java.

🤖 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/messages.properties`
around lines 14 - 15, Remove the unused message keys MsgYes and MsgNo from
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties
and delete the corresponding static fields (and any generated references) from
Messages.java; search for the identifiers MsgYes and MsgNo to confirm they are
not used, then remove the two lines "MsgYes=Yes" and "MsgNo=No" from
messages.properties and the matching field declarations in the Messages class
(and regenerate or update any resource bundle accessor code if your build
generates Messages.java).
🧹 Nitpick comments (2)
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties (1)

8-8: Rename stale key IDFGuideLinkLabel_Text to better reflect its current purpose.

The PR removes the guide link and adds an info button, but the property key still carries GuideLinkLabel in its name, creating semantic mismatch. The key is now used exclusively for an information label in ESPIDFMainTablePage.java (line 120). Consider renaming it to IDFInfoLabelText or similar to improve code clarity.

✏️ Suggested rename
-IDFGuideLinkLabel_Text=Select the ESP-IDF version you want to use. Double-click a row or select it and click 'Activate Selected' to switch versions.
+IDFInfoLabelText=Select the ESP-IDF version you want to use. Double-click a row or select it and click 'Activate Selected' to switch versions.

Update the reference in ESPIDFMainTablePage.java:120 from Messages.IDFGuideLinkLabel_Text to Messages.IDFInfoLabelText.

🤖 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/messages.properties`
at line 8, Rename the stale property key IDFGuideLinkLabel_Text to a name that
matches its new purpose (e.g., IDFInfoLabelText) in the properties file and
update all references accordingly; specifically change the resource lookup in
ESPIDFMainTablePage.java from Messages.IDFGuideLinkLabel_Text to
Messages.IDFInfoLabelText and keep the existing string value unchanged so the
label text remains the same.
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java (1)

94-97: LocalResourceManager is created but never used — remove or wire it up.

new LocalResourceManager(JFaceResources.getResources(), parent) is discarded immediately. Since no resources (images, colors, fonts) are allocated through it, it adds a dispose listener overhead with no benefit. Either remove it or assign it to a field so it can actually manage resources.

🧹 Proposed cleanup
-		new LocalResourceManager(JFaceResources.getResources(), parent);
-
 		container = new Composite(parent, SWT.NONE);

If resource management is needed in the future, store it:

+		var resourceManager = 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 94 - 97, In ESPIDFMainTablePage.createPage, the
LocalResourceManager instance is constructed and discarded; either remove the
unused new LocalResourceManager(...) call or store it in a field (e.g.,
localResourceManager) so it can be used to allocate UI resources and disposed
properly; if you choose to store it, add a private LocalResourceManager field to
the class, assign it in createPage, and dispose it in the page's dispose/cleanup
method to avoid leaking the dispose listener.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties`:
- Around line 14-15: Remove the unused message keys MsgYes and MsgNo from
bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties
and delete the corresponding static fields (and any generated references) from
Messages.java; search for the identifiers MsgYes and MsgNo to confirm they are
not used, then remove the two lines "MsgYes=Yes" and "MsgNo=No" from
messages.properties and the matching field declarations in the Messages class
(and regenerate or update any resource bundle accessor code if your build
generates Messages.java).

---

Duplicate comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 421-433: The selection is lost because IdfRow's equals() delegates
to IdfInstalled which uses reference equality; after tableViewer.setInput(rows)
the old IdfInstalled instances no longer match, so
setSelection(currentSelection) silently fails—fix by either implementing
equals() and hashCode() on IdfInstalled (based on its id field) so identity
matches across refreshes, or change the refresh flow in ESPIDFMainTablePage to
capture the selected IdfInstalled id(s) before setInput and after setInput
re-select rows by matching IdfInstalled.getId() (use IdfRow/IdfInstalled
identity via getId and call tableViewer.setSelection with the matched new IdfRow
objects).
- Around line 504-518: The compare() implementation in the anonymous Comparator
can throw NullPointerException when IdfRow fields are null (e.g., r1.version(),
r2.name(), r1.path()); update the comparisons in compare() to be null-safe by
coercing values to non-null strings (for example using
Objects.toString(r.version(), "") or similar) before calling
compareToIgnoreCase, or ensure IdfRow constructors normalize nulls to "" so
version(), name(), and path() never return null; modify the branches for
propertyIndex cases 1, 2, and 3 in the compare() method (which uses
IdfRow.version(), IdfRow.name(), IdfRow.path()) to use these null-safe values
and keep the existing direction handling.
- Around line 451-462: setupInitialEspIdf() can NPE because tableViewer may be
null and it uses the unsorted backing list; guard early by returning if
container==null or tableViewer==null, then check there is at least one visible
element (e.g., tableViewer.getItemCount() > 0) and use the visual first element
via tableViewer.getElementAt(0) instead of list.get(0); if that element is an
IdfRow, call performToolsSetup(selectedRaw.original()) as before.
- Line 127: Replace all hardcoded UI strings in ESPIDFMainTablePage with entries
in the Messages resource bundle and use those keys instead of literal strings:
externalize "Installed IDF Versions" (used in group.setText), the button
labels/tooltips for "Activate Selected" and "Refresh Environment" (and its long
tooltip), the column header "Status", the progress label "Setting up..." and the
status text "\u2713 Active" into new Messages keys (e.g.,
Messages.EspIdfManagerInstalledGroup, Messages.EspIdfManagerActivateBtn,
Messages.EspIdfManagerRefreshBtn, Messages.EspIdfManagerRefreshTooltip,
Messages.EspIdfManagerStatusCol, Messages.EspIdfManagerSettingUp,
Messages.EspIdfManagerActiveTick) and replace the hardcoded literals in
ESPIDFMainTablePage with calls to those Messages fields; add corresponding
entries to the Messages properties and update any generated Messages.java if
required so the UI uses the i18n pattern consistently.
- Around line 400-438: The code currently calls configParser.getEimJson(true) on
the background thread to build rows and then calls
configParser.getEimJson(false) again on the UI thread, risking race conditions
and blocking; instead capture the parsed EIM JSON once on the background thread
(keep the local variable newJson returned from the CompletableFuture), convert
it to rows there, and pass that same newJson into the UI continuation so the UI
thread sets this.eimJson = newJson (or an appropriate copy) and avoids calling
configParser.getEimJson again; update the CompletableFuture.supplyAsync to
return a small holder (e.g., a pair/record or map) containing both the parsed
newJson and the rows, then in thenAcceptAsync use that holder to set
this.eimJson, update tableViewer input/selection, and update button state,
removing the configParser.getEimJson(false) call and its exception handling on
the UI thread.
- Around line 395-399: refreshEditorUI() is a no-op when container is null
because EimJsonUiChangeHandler and SetupToolsJobListener create throwaway
ESPIDFMainTablePage instances instead of targeting the live page; fix this by
changing those callers to locate the real, active ESPIDFMainTablePage (do NOT
construct a new instance) via the workbench page/editor lookup APIs and invoke
refreshEditorUI on that instance, or alternatively make refreshEditorUI tolerant
by, when container==null, resolving the live page from the current
IWorkbenchWindow/IWorkbenchPage (or active editor) and delegating the refresh
(so symbols to change are EimJsonUiChangeHandler, SetupToolsJobListener,
refreshEditorUI, createPage, and container).

---

Nitpick comments:
In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java`:
- Around line 94-97: In ESPIDFMainTablePage.createPage, the LocalResourceManager
instance is constructed and discarded; either remove the unused new
LocalResourceManager(...) call or store it in a field (e.g.,
localResourceManager) so it can be used to allocate UI resources and disposed
properly; if you choose to store it, add a private LocalResourceManager field to
the class, assign it in createPage, and dispose it in the page's dispose/cleanup
method to avoid leaking the dispose listener.

In
`@bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties`:
- Line 8: Rename the stale property key IDFGuideLinkLabel_Text to a name that
matches its new purpose (e.g., IDFInfoLabelText) in the properties file and
update all references accordingly; specifically change the resource lookup in
ESPIDFMainTablePage.java from Messages.IDFGuideLinkLabel_Text to
Messages.IDFInfoLabelText and keep the existing string value unchanged so the
label text remains the same.

@sigmaaa sigmaaa changed the title WIP: Update esp manager UI IEP-1701 Improve UI/UX of esp-idf manager view Feb 20, 2026
@kolipakakondal
Copy link
Collaborator

No editor descriptor for id org.eclipse.ui.internal.emptyEditorTab

Screenshot 2026-02-23 at 2 17 50 PM

@kolipakakondal
Copy link
Collaborator

No editor descriptor for id org.eclipse.ui.internal.emptyEditorTab

Screenshot 2026-02-23 at 2 17 50 PM

After restarting, the issue was no longer reproducible. For some reason, the shell was not initialized before tool startup, which caused the issue.

We can ignore this for now, but continue to monitor it.

@sigmaaa
Copy link
Collaborator Author

sigmaaa commented Feb 23, 2026

No editor descriptor for id org.eclipse.ui.internal.emptyEditorTab
Screenshot 2026-02-23 at 2 17 50 PM

After restarting, the issue was no longer reproducible. For some reason, the shell was not initialized before tool startup, which caused the issue.

We can ignore this for now, but continue to monitor it.

Hi @kolipakakondal, as far as I know, this issue only happens in the dev environment when we make changes to the editor code. I think that since you changed the branch, you were able to see it once.

@kolipakakondal kolipakakondal added this to the v4.2.0 milestone Feb 26, 2026
Copy link
Collaborator

@alirana01 alirana01 left a comment

Choose a reason for hiding this comment

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

@sigmaaa just a few small quirks that maybe you can fix else LGTM!

Comment on lines +446 to +447
tableViewer.setInput(rows);
tableViewer.setSelection(currentSelection);
Copy link
Collaborator

Choose a reason for hiding this comment

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

@sigmaaa I think this makes sense
After setInput(rows), the table content is new IdfRow instances (with new IdfInstalled references). IdfRow’s generated equals() includes original; IdfInstalled does not override equals()/hashCode(), so comparison is by reference. The old selection no longer matches any element in the new list, so setSelection(currentSelection) has no effect. Fix by either: (A) implementing equals()/hashCode() on IdfInstalled using a stable id (e.g. getId()), or (B) before setInput, storing the selected id(s) (e.g. IdfInstalled.getId()), then after setInput finding the new IdfRow(s) with matching id(s) and calling setSelection with those.

@sigmaaa
Copy link
Collaborator Author

sigmaaa commented Mar 4, 2026

Hi @alirana01, good catch, thanks for noticing it. Fixed in the last commit. PTAL

@AndriiFilippov
Copy link
Collaborator

@sigmaaa hi !

Tested under:
OS: Windows 11 / Linux Ubuntu / Mac arm64

Everything we discussed earlier is implemented in this PR and works properly ✅
LGTM

Copy link
Collaborator

@alirana01 alirana01 left a comment

Choose a reason for hiding this comment

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

Thanks @sigmaaa LGTM!

@kolipakakondal kolipakakondal force-pushed the update_esp_manager_ui branch from 209275d to 24b131f Compare March 18, 2026 03:42
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.

4 participants