or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aliases.mdapi-package.mdbuiltins-api.mdcompletion.mdconfiguration.mddirectory-management.mdevents.mdindex.mdscripting.mdshell-interface.md

events.mddocs/

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.