Skip to content

feat: Preference option for configuring eim_idf.json file#1444

Open
kolipakakondal wants to merge 4 commits intomasterfrom
fix_accept_eimidfjson_location
Open

feat: Preference option for configuring eim_idf.json file#1444
kolipakakondal wants to merge 4 commits intomasterfrom
fix_accept_eimidfjson_location

Conversation

@kolipakakondal
Copy link
Copy Markdown
Collaborator

@kolipakakondal kolipakakondal commented Apr 22, 2026

Description

Preference option for configuring eim_idf.json file from the custom location.

Fixes # IEP-1753

Screenshot 2026-04-22 at 4 25 18 PM

Type of change

New feature (non-breaking change which adds functionality)

How has this been tested?

  1. Open ESP-IDF Manager with your existing default setup.
  2. Move eim_idf.json to a custom location, or install the ESP-IDF tools to a different location with EIM so they are not installed in the default path.
  3. Launch EspressIF IDE.
  4. Point the IDE to the eim_idf.json file and confirm the IDE detects it.
  5. Change something in the file, then click Refresh environment and confirm the change is reflected.

Test Configuration:

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

Dependent components impacted by this PR:

  • EIM view

Checklist

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@kolipakakondal has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 58 minutes and 34 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a627f96-0185-4645-89ca-7a6bd2a34ba7

📥 Commits

Reviewing files that changed from the base of the PR and between 2471abb and dbf78f1.

📒 Files selected for processing (16)
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/IDFCorePreferenceConstants.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimIdfConfiguratinParser.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimIdfJsonPathResolver.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonStateChecker.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonWatchService.java
  • bundles/com.espressif.idf.terminal.connector/src/com/espressif/idf/terminal/connector/launcher/IDFTerminalProcessConnector.java
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/EspresssifPreferencesPage.java
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/Messages.java
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/messages.properties
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/messages_zh.properties
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EspressifToolStartup.java
  • docs/en/installation.rst
  • docs/zh_CN/installation.rst
  • tests/com.espressif.idf.core.test/src/com/espressif/idf/core/tools/test/EimIdfJsonPathResolverTest.java
📝 Walkthrough

Walkthrough

This PR introduces a centralized EimIdfJsonPathResolver class to replace scattered OS-dependent path resolution logic throughout the codebase. The new resolver supports user preferences for custom EIM JSON paths, with UI settings to manage them. The watch service is refactored to support restarting when paths change.

Changes

Cohort / File(s) Summary
Core Path Resolution Infrastructure
IDFCorePreferenceConstants.java, EimIdfJsonPathResolver.java
Added new preference constant EIM_IDF_JSON_PATH and created utility class EimIdfJsonPathResolver to centralize path resolution with support for user preferences and platform-specific defaults.
EIM Configuration & Initialization
EimIdfConfiguratinParser.java, ToolInitializer.java, EimJsonStateChecker.java, IDFTerminalProcessConnector.java
Refactored to use the new EimIdfJsonPathResolver instead of direct OS-conditional path selection via Platform and EimConstants.
EIM Watch Service
EimJsonWatchService.java
Replaced singleton Holder pattern with direct static field; added restartAfterEimIdfPathChange() method for service restart; updated constructor to leverage resolver for directory validation; added null-guarding in lifecycle callbacks.
EIM Loader & Startup
EimLoader.java, EspressifToolStartup.java
Added null-checks around EimJsonWatchService.getInstance() calls in job callbacks and listener registration to safely handle potentially unavailable instances.
UI Preferences & Localization
EspresssifPreferencesPage.java, Messages.java, messages.properties, messages_zh.properties
Added EIM settings section to preferences page with custom path field, browse button, and validation; added corresponding message keys in English and Chinese.
Test Coverage
EimIdfJsonPathResolverTest.java
Added JUnit 5 tests covering empty/null preferences, existing custom paths, and fallback behavior for the new resolver.

Sequence Diagram

sequenceDiagram
    participant User
    participant PreferencesPage as Preferences UI
    participant PrefStore as Preference Store
    participant PathResolver as EimIdfJsonPathResolver
    participant WatchService as EimJsonWatchService
    
    User->>PreferencesPage: Set custom EIM JSON path
    PreferencesPage->>PreferencesPage: Validate filename
    PreferencesPage->>PrefStore: Save path to EIM_IDF_JSON_PATH
    PreferencesPage->>WatchService: restartAfterEimIdfPathChange()
    WatchService->>WatchService: Stop current instance
    WatchService->>PathResolver: resolveEimIdfJsonFile()
    PathResolver->>PrefStore: Read EIM_IDF_JSON_PATH preference
    PathResolver-->>WatchService: Return resolved path
    WatchService->>WatchService: Create new instance with path
    WatchService->>WatchService: Re-attach prior listeners
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • AndriiFilippov
  • alirana01
  • sigmaaa

Poem

🐰 A rabbit hops through pathways tangled,
Where OS checks once left code mangled,
But now a Resolver shows the way,
Users choose their paths—hip hip hooray!
Watch services restart, listeners play,
One central truth saves the day! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Preference option for configuring eim_idf.json file' accurately summarizes the main change: adding a preference option to configure the EIM IDF JSON file location from a custom location, which is the core feature of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
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 fix_accept_eimidfjson_location

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.

Copy link
Copy Markdown

@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: 4

Caution

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

⚠️ Outside diff range comments (2)
bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java (1)

138-174: ⚠️ Potential issue | 🟠 Major

Keep pause/unpause balanced on the same watcher instance.

The code pauses one getInstance() result and later unpauses another. If the watcher is restarted/recreated while EIM is open, or if callback.run() throws, listeners can remain paused on the original watcher.

Suggested fix
 		waitJob.addJobChangeListener(new JobChangeAdapter()
 		{
+			private EimJsonWatchService pausedWatchService;
+
 			`@Override`
 			public void aboutToRun(IJobChangeEvent event)
 			{
-				EimJsonWatchService ws = EimJsonWatchService.getInstance();
-				if (ws != null)
+				pausedWatchService = EimJsonWatchService.getInstance();
+				if (pausedWatchService != null)
 				{
-					ws.pauseListeners();
+					pausedWatchService.pauseListeners();
 				}
 			}
@@
 			public void done(IJobChangeEvent event)
 			{
-				Display.getDefault().asyncExec(() -> {
-					try
-					{
-						standardConsoleStream.write("EIM has been closed.\n"); //$NON-NLS-1$
-					}
-					catch (IOException e)
-					{
-						Logger.log(e);
-					}
-				});
-
-				if (callback != null)
+				try
 				{
-					callback.run();
+					Display.getDefault().asyncExec(() -> {
+						try
+						{
+							standardConsoleStream.write("EIM has been closed.\n"); //$NON-NLS-1$
+						}
+						catch (IOException e)
+						{
+							Logger.log(e);
+						}
+					});
+
+					if (callback != null)
+					{
+						callback.run();
+					}
 				}
-
-				EimJsonWatchService ws2 = EimJsonWatchService.getInstance();
-				if (ws2 != null)
+				finally
 				{
-					ws2.unpauseListeners();
+					if (pausedWatchService != null)
+					{
+						pausedWatchService.unpauseListeners();
+						pausedWatchService = null;
+					}
 				}
 			}
 		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java`
around lines 138 - 174, The aboutToRun/done listener currently pauses
EimJsonWatchService.getInstance() into local ws and later calls getInstance()
again (ws2) and runs callback, risking an unmatched unpause if the watcher is
recreated or callback throws; capture the watcher instance once (e.g., a final
variable like pausedWatcher) when aboutToRun runs or before adding the
JobChangeAdapter and use that same reference in done, and ensure done wraps
callback.run() in try/finally so pausedWatcher.unpauseListeners() is always
invoked on the same instance even if callback throws.
bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonStateChecker.java (1)

23-64: ⚠️ Potential issue | 🟠 Major

Track the resolved path with the stored timestamp.

Line 64 makes the watched file configurable, but LAST_MODIFIED_PREF_KEY still stores a timestamp without the path it belongs to. After switching locations, a newly selected eim_idf.json can be treated as unchanged if its mtime is older than the previous file’s timestamp.

Suggested fix
 	private static final String LAST_MODIFIED_PREF_KEY = "lastEimJsonModified"; //$NON-NLS-1$
+	private static final String LAST_PATH_PREF_KEY = "lastEimJsonPath"; //$NON-NLS-1$
@@
 	public boolean wasModifiedSinceLastRun()
 	{
-		File jsonFile = new File(getEimJsonPath());
+		String eimJsonPath = getEimJsonPath();
+		File jsonFile = new File(eimJsonPath);
 		if (!jsonFile.exists())
 		{
 			return false;
 		}
 
 		long lastModified = jsonFile.lastModified();
 		long lastSeen = preferences.getLong(LAST_MODIFIED_PREF_KEY, 0L);
@@
 			return false;
 		}
 
+		String lastPath = preferences.get(LAST_PATH_PREF_KEY, eimJsonPath);
+		if (!eimJsonPath.equals(lastPath))
+		{
+			return true;
+		}
+
 		return lastModified > lastSeen;
 	}
@@
 	public void updateLastSeenTimestamp()
 	{
-		File jsonFile = new File(getEimJsonPath());
+		String eimJsonPath = getEimJsonPath();
+		File jsonFile = new File(eimJsonPath);
 		if (jsonFile.exists())
 		{
 			preferences.putLong(LAST_MODIFIED_PREF_KEY, jsonFile.lastModified());
+			preferences.put(LAST_PATH_PREF_KEY, eimJsonPath);
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonStateChecker.java`
around lines 23 - 64, The stored timestamp under LAST_MODIFIED_PREF_KEY in
EimJsonStateChecker doesn't account for the resolved file path from
getEimJsonPath(), so switching to a different eim_idf.json can be misdetected;
update wasModifiedSinceLastRun and updateLastSeenTimestamp to also persist and
compare the resolved path (e.g., store a companion preference like
lastEimJsonPath or include the path in the pref key) before using the stored
timestamp—if the saved path differs from the current getEimJsonPath() result
treat it as a first-seen file (or reset the timestamp) and then save the current
path together with the file's lastModified value so future calls to
wasModifiedSinceLastRun check both path equality and mtime.
🤖 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.core/src/com/espressif/idf/core/tools/EimIdfJsonPathResolver.java`:
- Around line 30-40: The resolver currently returns any existing path; update
resolveEimIdfJsonFileFromPreferenceString to validate that the provided custom
path has the expected basename (compare
Paths.get(custom).getFileName().toString() to EimConstants.EIM_JSON) and that it
is a regular file (use Files.isRegularFile) before returning it; otherwise fall
back to getDefaultEimIdfJsonFile(). Keep the existing null/empty check and use
the same Path p variable and Files.exists check logic but replace the
exists-only check with the basename + isRegularFile guards so the watcher/UI
contract is honored.

In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java`:
- Around line 63-66: The current isEimIdfJsonPresent() uses Files.exists(...)
which can return true for directories or non-regular paths; change the check to
verify the resolved path from new
EimIdfJsonPathResolver().resolveEimIdfJsonFile() is a regular readable file (use
Files.isRegularFile(path) and/or Files.isReadable(path) instead of Files.exists)
so the method only returns true for an actual file that can be read.

In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonWatchService.java`:
- Around line 92-114: restartAfterEimIdfPathChange currently stops the old
watcher first which can leave INSTANCE null if constructing the new
EimJsonWatchService fails; change the order in restartAfterEimIdfPathChange so
you first create the new EimJsonWatchService instance (new
EimJsonWatchService()), register the copied listeners on that fresh instance via
fresh.addEimJsonChangeListener(...), then atomically assign INSTANCE = fresh,
and only after the swap call old.requestStop(); keep the try/catch around the
construction so if construction throws you log the IOException and leave the
original INSTANCE running (do not set INSTANCE to null).

In
`@tests/com.espressif.idf.core.test/src/com/espressif/idf/core/tools/test/EimIdfJsonPathResolverTest.java`:
- Around line 50-55: The test nonexistent_custom_falls_back_to_default assumes a
literal filesystem path that might exist on the host; make it deterministic by
using JUnit's `@TempDir`. Change the test to accept a `@TempDir` Path (e.g. Path
tempDir) and pass a guaranteed-missing child like
tempDir.resolve("missing").resolve("eim_idf.json").toString() into
EimIdfJsonPathResolver.resolveEimIdfJsonFileFromPreferenceString, keeping the
assertion against r.getDefaultEimIdfJsonFile(); reference the test method name
and the EimIdfJsonPathResolver class to locate where to update the parameter and
call.

---

Outside diff comments:
In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java`:
- Around line 138-174: The aboutToRun/done listener currently pauses
EimJsonWatchService.getInstance() into local ws and later calls getInstance()
again (ws2) and runs callback, risking an unmatched unpause if the watcher is
recreated or callback throws; capture the watcher instance once (e.g., a final
variable like pausedWatcher) when aboutToRun runs or before adding the
JobChangeAdapter and use that same reference in done, and ensure done wraps
callback.run() in try/finally so pausedWatcher.unpauseListeners() is always
invoked on the same instance even if callback throws.

In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonStateChecker.java`:
- Around line 23-64: The stored timestamp under LAST_MODIFIED_PREF_KEY in
EimJsonStateChecker doesn't account for the resolved file path from
getEimJsonPath(), so switching to a different eim_idf.json can be misdetected;
update wasModifiedSinceLastRun and updateLastSeenTimestamp to also persist and
compare the resolved path (e.g., store a companion preference like
lastEimJsonPath or include the path in the pref key) before using the stored
timestamp—if the saved path differs from the current getEimJsonPath() result
treat it as a first-seen file (or reset the timestamp) and then save the current
path together with the file's lastModified value so future calls to
wasModifiedSinceLastRun check both path equality and mtime.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c910487-d1c4-4aae-9ef8-a3cb4e454c42

📥 Commits

Reviewing files that changed from the base of the PR and between 6a67e13 and 2471abb.

📒 Files selected for processing (14)
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/IDFCorePreferenceConstants.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimIdfConfiguratinParser.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimIdfJsonPathResolver.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonStateChecker.java
  • bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonWatchService.java
  • bundles/com.espressif.idf.terminal.connector/src/com/espressif/idf/terminal/connector/launcher/IDFTerminalProcessConnector.java
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/EspresssifPreferencesPage.java
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/Messages.java
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/messages.properties
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/messages_zh.properties
  • bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EspressifToolStartup.java
  • tests/com.espressif.idf.core.test/src/com/espressif/idf/core/tools/test/EimIdfJsonPathResolverTest.java

Copy link
Copy Markdown
Collaborator

@sigmaaa sigmaaa left a comment

Choose a reason for hiding this comment

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

LGTM. Just a one small nit about small UX improvement

eimGroup.setLayout(new GridLayout(3, false));

Label eimPathLabel = new Label(eimGroup, SWT.None);
eimPathLabel.setText(Messages.EspresssifPreferencesPage_EimIdfJsonPathLabel);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NIT: We can also use eimIdfJsonPathText.setMessage("Leave empty to use the default value."); to make users aware that a default value exists.

Image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed it.

@kolipakakondal kolipakakondal added this to the v4.3.0 milestone Apr 28, 2026
@kolipakakondal kolipakakondal force-pushed the fix_accept_eimidfjson_location branch from 9c43397 to dbf78f1 Compare April 28, 2026 06:45
@AndriiFilippov
Copy link
Copy Markdown
Collaborator

@kolipakakondal hi !

Tested under:
OS: Mac arm64 / Windows 11

Windows 11:
if you call EIM after updating eim_idf.json path in Preferences, the IDE does not read EIM path from eim_idf.json (I guess) and execute new EIM downloading to the same standard location: C:\Users\andri\.espressif\eim_gui

Mac arm64:
LGTM

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.

3 participants