0.4.1 used em-dashes (--) in three log strings and one docstring line.
IronPython 2.7 (CODESYS scripting host) rejects non-ASCII bytes in a
source file unless an explicit encoding declaration (PEP 263) is present:
Microsoft.Scripting.SyntaxErrorException: Non-ASCII character '\xe2' in
file ...\watcher.py on line 225, but no encoding declared
Replaced em-dashes with ASCII '--' and bumped WATCHER_VERSION to 0.4.2.
File is now ASCII-clean throughout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the user clicks "Click here to CANCEL this operation" in CODESYS,
IronPython injects KeyboardInterrupt into the running script. The watcher's
try/except blocks only caught Exception, which doesn't include
KeyboardInterrupt — so the interrupt propagated to the host and CODESYS
popped the modal "Running script ... caused exception ... KeyboardInterrupt:
Script aborted by user." dialog. The watcher process then died, taking the
MCP IPC channel with it.
Three layers of handling:
1. execute_script(): catch KeyboardInterrupt and convert it into a normal
command failure ("Aborted by user (Cancel pressed in CODESYS)") so the
in-flight command fails gracefully and the loop continues.
2. Main poll loop: catch KeyboardInterrupt around the iteration body, log
it, and continue. New _safe_delay() helper wraps system.delay() with
its own KeyboardInterrupt swallow because the cancel link almost always
hits during the delay (line 231 in 0.4.0).
3. Outer try: explicit KeyboardInterrupt arm so a cancel that fires before
the loop (during scriptengine import or directory setup) still exits
quietly without the modal traceback dialog.
Bumps WATCHER_VERSION to 0.4.1. Verification pending — needs user to
click the Cancel link in CODESYS after a fresh launch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the .NET background-thread + system.execute_on_primary_thread
marshalling design with a single-threaded primary-thread polling loop
that yields to the IDE via system.delay(). The deprecated
execute_on_primary_thread API was removed in CODESYS V3.5 SP21+ — see
docs/MIGRATION-SP21-PLUS.md for full evidence and rationale.
- watcher.py: drop clr / System.Threading imports, drop ManualResetEvent
+ done_event + shared_result cross-thread machinery, keep file-based
IPC and OutputCapture untouched.
- Loop checks for terminate.signal at the top of each iteration, then
serves the message loop via system.delay(POLL_INTERVAL).
- Bumped WATCHER_VERSION to 0.4.0.
Verified against:
- CODESYS 3.5.22.10 (SP22 Patch 1) — open_project + get_application_state
both succeed where they previously failed with the marshal error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.3.0 returning-watcher architecture — background thread polls for commands
and marshals execution onto the CODESYS UI thread, keeping the IDE fully
responsive between operations. File-based IPC with atomic writes, async mutex
command serialization, headless fallback, and 35 passing tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>