0
# Events
1
2
## Overview
3
4
Xonsh provides a comprehensive event system that enables customization and extension of shell behavior through event handlers. Events are fired at key points during command execution, shell lifecycle, and environment changes, allowing for powerful customization and automation.
5
6
## Event System Core
7
8
### Event Classes
9
10
```python { .api }
11
from xonsh.events import AbstractEvent, Event, LoadEvent, EventManager
12
13
class AbstractEvent(collections.abc.MutableSet):
14
"""Abstract base class for xonsh events."""
15
16
def fire(self, **kwargs) -> None:
17
"""Fire event with keyword arguments.
18
19
Parameters
20
----------
21
**kwargs
22
Event-specific arguments passed to handlers
23
"""
24
25
def add(self, func: callable) -> None:
26
"""Add event handler function.
27
28
Parameters
29
----------
30
func : callable
31
Handler function to add
32
"""
33
34
def discard(self, func: callable) -> None:
35
"""Remove event handler function.
36
37
Parameters
38
----------
39
func : callable
40
Handler function to remove
41
"""
42
43
class Event(AbstractEvent):
44
"""Standard event implementation."""
45
46
def __init__(self, name: str = None, doc: str = None,
47
prio: int = 0, traceback: bool = True):
48
"""Create event.
49
50
Parameters
51
----------
52
name : str, optional
53
Event name
54
doc : str, optional
55
Event documentation
56
prio : int, default 0
57
Event priority for handler ordering
58
traceback : bool, default True
59
Whether to show tracebacks on handler errors
60
"""
61
62
class LoadEvent(AbstractEvent):
63
"""Event that loads handlers from configuration."""
64
65
def __init__(self, name: str = None, doc: str = None,
66
prio: int = 0, traceback: bool = True):
67
"""Create load event with configuration loading."""
68
69
class EventManager:
70
"""Central event manager for xonsh."""
71
72
def __init__(self):
73
"""Initialize event manager."""
74
75
def __getattr__(self, name: str) -> Event:
76
"""Get or create event by name.
77
78
Parameters
79
----------
80
name : str
81
Event name
82
83
Returns
84
-------
85
Event
86
Event instance
87
"""
88
```
89
90
### Global Event Manager
91
92
```python { .api }
93
from xonsh.events import events
94
95
# Global event manager instance
96
events: EventManager # Main event manager
97
98
# Event registration patterns
99
@events.on_precommand
100
def my_precommand_handler(cmd: str) -> None:
101
"""Handler for precommand event."""
102
pass
103
104
# Alternative registration
105
def my_handler(**kwargs):
106
"""Generic event handler."""
107
pass
108
109
events.on_postcommand.add(my_handler)
110
```
111
112
## Core Shell Events
113
114
### Command Lifecycle Events
115
116
```python { .api }
117
from xonsh.events import events
118
119
@events.on_transform_command
120
def transform_command_handler(cmd: str, **kwargs) -> str:
121
"""Transform command before parsing.
122
123
Parameters
124
----------
125
cmd : str
126
Original command string
127
**kwargs
128
Additional context
129
130
Returns
131
-------
132
str
133
Transformed command string
134
"""
135
# Example: expand abbreviations
136
if cmd.startswith('g '):
137
return 'git ' + cmd[2:]
138
return cmd
139
140
@events.on_precommand
141
def precommand_handler(cmd: str, **kwargs) -> None:
142
"""Called before command execution.
143
144
Parameters
145
----------
146
cmd : str
147
Command to be executed
148
**kwargs
149
Execution context
150
"""
151
print(f"About to execute: {cmd}")
152
153
@events.on_postcommand
154
def postcommand_handler(cmd: str, rtn: int, out: str = None,
155
ts: list = None, **kwargs) -> None:
156
"""Called after command execution.
157
158
Parameters
159
----------
160
cmd : str
161
Executed command
162
rtn : int
163
Return code (0 for success)
164
out : str, optional
165
Command output if captured
166
ts : list, optional
167
Timestamps [start_time, end_time]
168
**kwargs
169
Additional context
170
"""
171
if rtn != 0:
172
print(f"Command failed with return code {rtn}")
173
174
@events.on_command_not_found
175
def command_not_found_handler(cmd: list[str], **kwargs) -> None:
176
"""Called when command is not found.
177
178
Parameters
179
----------
180
cmd : list[str]
181
Command arguments that were not found
182
**kwargs
183
Context information
184
"""
185
cmd_name = cmd[0] if cmd else "unknown"
186
print(f"Command '{cmd_name}' not found. Did you mean something else?")
187
```
188
189
### Prompt Events
190
191
```python { .api }
192
@events.on_pre_prompt_format
193
def pre_prompt_format_handler(**kwargs) -> None:
194
"""Called before prompt formatting.
195
196
Parameters
197
----------
198
**kwargs
199
Prompt context
200
"""
201
# Prepare prompt context
202
pass
203
204
@events.on_pre_prompt
205
def pre_prompt_handler(**kwargs) -> None:
206
"""Called just before showing prompt.
207
208
Parameters
209
----------
210
**kwargs
211
Prompt state
212
"""
213
# Last-minute prompt customization
214
pass
215
216
@events.on_post_prompt
217
def post_prompt_handler(**kwargs) -> None:
218
"""Called after prompt input is received.
219
220
Parameters
221
----------
222
**kwargs
223
Input context
224
"""
225
# Process prompt input
226
pass
227
```
228
229
## Environment and Configuration Events
230
231
### Directory Change Events
232
233
```python { .api }
234
@events.on_chdir
235
def chdir_handler(olddir: str, newdir: str, **kwargs) -> None:
236
"""Called when current directory changes.
237
238
Parameters
239
----------
240
olddir : str
241
Previous directory path
242
newdir : str
243
New directory path
244
**kwargs
245
Change context
246
"""
247
print(f"Directory changed: {olddir} -> {newdir}")
248
249
# Auto-activate virtual environments
250
import os
251
venv_activate = os.path.join(newdir, 'venv', 'bin', 'activate')
252
if os.path.exists(venv_activate):
253
print("Virtual environment detected")
254
```
255
256
### Environment Variable Events
257
258
```python { .api }
259
@events.on_envvar_new
260
def envvar_new_handler(name: str, value: str, **kwargs) -> None:
261
"""Called when new environment variable is set.
262
263
Parameters
264
----------
265
name : str
266
Variable name
267
value : str
268
Variable value
269
**kwargs
270
Context information
271
"""
272
if name.startswith('PROJECT_'):
273
print(f"Project variable set: {name}={value}")
274
275
@events.on_envvar_change
276
def envvar_change_handler(name: str, oldvalue: str, newvalue: str,
277
**kwargs) -> None:
278
"""Called when environment variable changes.
279
280
Parameters
281
----------
282
name : str
283
Variable name
284
oldvalue : str
285
Previous value
286
newvalue : str
287
New value
288
**kwargs
289
Context information
290
"""
291
if name == 'PATH':
292
print("PATH was modified")
293
```
294
295
## Advanced Event Patterns
296
297
### Event Filtering and Validation
298
299
```python { .api }
300
def filtered_event_handler(cmd: str, **kwargs) -> None:
301
"""Event handler with filtering logic."""
302
# Only handle git commands
303
if not cmd.startswith('git '):
304
return
305
306
# Extract git subcommand
307
parts = cmd.split()
308
if len(parts) > 1:
309
subcmd = parts[1]
310
print(f"Git subcommand: {subcmd}")
311
312
@events.on_precommand
313
def validate_command_handler(cmd: str, **kwargs) -> None:
314
"""Validate commands before execution."""
315
dangerous_commands = ['rm -rf /', 'dd if=/dev/zero']
316
317
for dangerous in dangerous_commands:
318
if dangerous in cmd:
319
print(f"WARNING: Potentially dangerous command: {cmd}")
320
response = input("Are you sure? (y/N): ")
321
if response.lower() != 'y':
322
raise KeyboardInterrupt("Command cancelled by user")
323
```
324
325
### Conditional Event Handlers
326
327
```python { .api }
328
from xonsh.built_ins import XSH
329
330
def development_mode_handler(cmd: str, **kwargs) -> None:
331
"""Handler that only runs in development mode."""
332
env = XSH.env
333
334
if not env.get('DEVELOPMENT_MODE'):
335
return
336
337
# Development-specific logging
338
with open('/tmp/dev_commands.log', 'a') as f:
339
import datetime
340
timestamp = datetime.datetime.now().isoformat()
341
f.write(f"{timestamp}: {cmd}\n")
342
343
events.on_precommand.add(development_mode_handler)
344
```
345
346
### Event Handler Composition
347
348
```python { .api }
349
def create_logging_handler(logfile: str):
350
"""Factory function for logging handlers."""
351
def logging_handler(cmd: str, **kwargs):
352
with open(logfile, 'a') as f:
353
import datetime
354
timestamp = datetime.datetime.now().isoformat()
355
f.write(f"{timestamp}: {cmd}\n")
356
return logging_handler
357
358
# Create specialized loggers
359
git_logger = create_logging_handler('/tmp/git_commands.log')
360
system_logger = create_logging_handler('/tmp/system_commands.log')
361
362
@events.on_precommand
363
def dispatch_logging(cmd: str, **kwargs):
364
"""Dispatch to appropriate logger based on command."""
365
if cmd.startswith('git '):
366
git_logger(cmd, **kwargs)
367
elif cmd.startswith(('ls', 'cd', 'pwd')):
368
system_logger(cmd, **kwargs)
369
```
370
371
## Event Handler Management
372
373
### Dynamic Handler Registration
374
375
```python { .api }
376
def register_project_handlers():
377
"""Register project-specific event handlers."""
378
379
@events.on_chdir
380
def project_chdir_handler(olddir, newdir, **kwargs):
381
# Check for project files
382
import os
383
if os.path.exists(os.path.join(newdir, 'pyproject.toml')):
384
print("Entered Python project directory")
385
elif os.path.exists(os.path.join(newdir, 'package.json')):
386
print("Entered Node.js project directory")
387
388
return project_chdir_handler
389
390
# Register conditionally
391
if XSH.env.get('ENABLE_PROJECT_DETECTION'):
392
handler = register_project_handlers()
393
```
394
395
### Handler Removal and Cleanup
396
397
```python { .api }
398
# Store handler references for later removal
399
active_handlers = []
400
401
def temporary_debug_handler(cmd: str, **kwargs):
402
"""Temporary debugging handler."""
403
print(f"DEBUG: {cmd}")
404
405
# Add handler and store reference
406
events.on_precommand.add(temporary_debug_handler)
407
active_handlers.append(temporary_debug_handler)
408
409
# Remove handler later
410
def cleanup_handlers():
411
"""Remove temporary handlers."""
412
for handler in active_handlers:
413
events.on_precommand.discard(handler)
414
active_handlers.clear()
415
```
416
417
## Integration Examples
418
419
### With Xontribs (Extensions)
420
421
```python { .api }
422
def load_xontrib_with_events():
423
"""Load xontrib and register its event handlers."""
424
from xonsh.xontribs import xontribs_load
425
426
# Load xontrib
427
xontribs_load(['my_extension'])
428
429
# Register extension-specific handlers
430
@events.on_postcommand
431
def extension_postcommand(cmd, rtn, **kwargs):
432
# Extension-specific post-command processing
433
pass
434
```
435
436
### With Configuration
437
438
```python { .api }
439
def configure_event_system():
440
"""Configure event system based on user preferences."""
441
env = XSH.env
442
443
# Command timing
444
if env.get('ENABLE_COMMAND_TIMING'):
445
@events.on_precommand
446
def start_timer(**kwargs):
447
import time
448
XSH._command_start_time = time.time()
449
450
@events.on_postcommand
451
def end_timer(cmd, **kwargs):
452
if hasattr(XSH, '_command_start_time'):
453
duration = time.time() - XSH._command_start_time
454
if duration > 1.0: # Only show for slow commands
455
print(f"Command took {duration:.2f} seconds")
456
457
# Command history enhancement
458
if env.get('ENHANCED_HISTORY'):
459
@events.on_postcommand
460
def enhanced_history(cmd, rtn, **kwargs):
461
if rtn == 0: # Only successful commands
462
# Add to enhanced history with metadata
463
pass
464
```
465
466
## Error Handling in Events
467
468
### Event Handler Exceptions
469
470
```python { .api }
471
def robust_event_handler(cmd: str, **kwargs) -> None:
472
"""Event handler with proper error handling."""
473
try:
474
# Handler logic - example processing
475
print(f"Processing command: {cmd}")
476
except Exception as e:
477
# Log error but don't break event chain
478
import logging
479
logging.error(f"Event handler error: {e}")
480
# Optionally re-raise for critical errors
481
# raise
482
483
# Event system handles exceptions gracefully
484
events.on_precommand.add(robust_event_handler)
485
```
486
487
The event system provides powerful hooks into xonsh's execution flow, enabling sophisticated customization, automation, and extension of shell behavior through a clean, composable interface.