Successfully implemented a robust GHCi REPL manager for the Haskell-Run VS Code extension that replaces the old terminal-based approach with a prompt-driven, queue-based system.
Purpose: Core REPL management with deterministic prompt-based output handling
Key Features:
- ✅ Prompt-based Detection: Uses regex
/>\s*$/to detect GHCi prompt instead of hard-coded delays - ✅ Command Queue System: Serializes commands to prevent race conditions
- ✅ Automatic Error Detection: Regex patterns detect compilation errors, missing modules, parse errors
- ✅ State Safety: Module state updated ONLY on successful operations
- ✅ Defensive Timeout: 10-second failsafe timeout (not for synchronization)
- ✅ Event-Based Sync: No
setTimeout()for command synchronization - purely event-driven
Core Components:
-
Interface: GhciCommand
interface GhciCommand { command: string; resolve: (output: string) => void; reject: (error: string) => void; }
-
Class Methods:
constructor(outputChannel)- Initialize with output loggingrunCommand(command)- Execute command, return PromiseloadModule(filePath, moduleName, loadedModules)- Load .hs file with state safetyreloadModule(moduleName, loadedModules)- Reload with state safetyevaluate(expression)- Evaluate Haskell expressiondispose()- Clean termination of GHCi processisAlive()- Check if REPL is running
Changes:
a) Import new manager (Line 7)
import { GhciReplManager } from './ghci/GhciReplManager';b) Initialize in activate() (Lines 24-28)
const ghciRepl = new GhciReplManager(outputChannel);
const loadedModules = new Set<string>();
context.subscriptions.push({
dispose: () => ghciRepl.dispose()
});c) Update runSelectedFunction command (Lines 291-310)
- Removed terminal visibility requirement
- Removed
setTimeout()delays - Replaced old
replManager.loadModule()calls withghciRepl.loadModule() - Replaced
replManager.evaluateInRepl()withghciRepl.evaluate() - Added proper result output to debug channel
d) Update REPL management commands (Lines 320-330)
restartRepl- Disposes and reinitializes GHCiclearRepl- Clears module tracking and reinits REPL
| Problem | Fixed By |
|---|---|
| Hard-coded 500ms/1000ms delays | Prompt-based detection regex |
| Silent failures without error context | ERROR_REGEX pattern matching |
| Wrong internal state on failure | State updates only after success |
| Race conditions in command execution | Serialized command queue |
| Terminal visibility dependency | Event-based output handling |
| Cross-platform timing issues | Deterministic prompt detection |
| No error information in UI | Detailed error messages to user |
activate()
├─ Create GhciReplManager instance
├─ Initialize command handlers
│ ├─ runSelectedFunction
│ │ ├─ Load module via ghciRepl.loadModule()
│ │ └─ Evaluate via ghciRepl.evaluate()
│ ├─ restartRepl
│ └─ clearRepl
└─ Dispose cleanup on deactivate()
GhciReplManager
├─ spawn('ghci') → ChildProcess
├─ Event listeners
│ ├─ stdout.on('data') → onData()
│ ├─ stderr.on('data') → onData()
│ ├─ on('exit') → error handling
│ └─ on('error') → error handling
├─ Command Queue
│ ├─ FIFO serialization
│ └─ Promise-based API
└─ Output Parsing
├─ PROMPT_REGEX: />\s*$/
└─ ERROR_REGEX: (error:|Failed,|Could not find module|parse error|Variable not in scope)
User selects function
↓
runSelectedFunction handler
↓
ghciRepl.loadModule() → Load file
↓
ghciRepl.evaluate() → Run expression
↓
Promise resolves with output
↓
Show result in output channel + notification
- Automatic error detection via regex
- Promise rejection on error match
- User notification + output channel logging
- State unchanged on failure
- Single GHCi process per session
- Automatic cleanup on dispose
- Subscription-based disposal
- ✅ Build succeeds (
npm run watch:tsc) - ✅ No TypeScript errors
- ✅ Extension activation works
- ✅ GhciReplManager initializes on startup
- ✅ Output channel logs all operations
- ⏳ Manual testing: Load and evaluate Haskell functions
- ⏳ Manual testing: Error detection and recovery
- ⏳ Manual testing: Restart/clear commands
- Add unit tests for prompt detection and error parsing
- Support Windows line endings if needed (
\r\nvariant for PROMPT_REGEX) - Add timeout configuration to package.json settings
- Monitor for GHCi version compatibility (prompt format may vary)
- Old
ReplManagerstill imported but not used by new code - Can be removed in future cleanup PR
- Existing CodeLens and TreeView providers unaffected
- All existing commands still functional
Implementation Date: January 9, 2026
Status: ✅ Complete and buildable