or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

completion.mdhistory.mdhooks.mdindex.mdline-editing.mdutilities.md

hooks.mddocs/

0

# Hook Functions

1

2

Callback system for customizing readline behavior at key points in the input process. Hooks allow applications to integrate deeply with readline's event-driven architecture.

3

4

## Capabilities

5

6

### Completion Display Hooks

7

8

Customize how completion matches are displayed to the user.

9

10

```python { .api }

11

def set_completion_display_matches_hook(function):

12

"""

13

Set hook for displaying completion matches.

14

15

Parameters:

16

- function: Hook function(matches: list, num_matches: int, max_length: int)

17

Called when displaying completion matches to customize presentation

18

"""

19

```

20

21

### Usage Examples

22

23

```python

24

import gnureadline

25

26

def custom_completion_display(matches, num_matches, max_length):

27

"""Custom completion display with formatting."""

28

print(f"\n--- {num_matches} completions available ---")

29

30

# Group matches by type

31

files = []

32

dirs = []

33

others = []

34

35

for match in matches:

36

if match.endswith('/'):

37

dirs.append(match)

38

elif '.' in match:

39

files.append(match)

40

else:

41

others.append(match)

42

43

# Display grouped results

44

if dirs:

45

print("Directories:")

46

for d in sorted(dirs):

47

print(f" πŸ“ {d}")

48

49

if files:

50

print("Files:")

51

for f in sorted(files):

52

print(f" πŸ“„ {f}")

53

54

if others:

55

print("Other:")

56

for o in sorted(others):

57

print(f" ⚑ {o}")

58

59

print("---")

60

61

# Set up custom display

62

gnureadline.set_completion_display_matches_hook(custom_completion_display)

63

64

# Example file completer to test with

65

def simple_file_completer(text, state):

66

import glob

67

if state == 0:

68

simple_file_completer.matches = glob.glob(text + '*')

69

try:

70

return simple_file_completer.matches[state]

71

except IndexError:

72

return None

73

74

gnureadline.set_completer(simple_file_completer)

75

gnureadline.parse_and_bind("tab: complete")

76

```

77

78

### Startup Hooks

79

80

Execute custom code when readline is about to start processing input.

81

82

```python { .api }

83

def set_startup_hook(function):

84

"""

85

Set startup hook function called when readline is about to start.

86

87

Parameters:

88

- function: Hook function() -> int (return 0 for success)

89

Called before readline displays the prompt

90

"""

91

```

92

93

### Usage Examples

94

95

```python

96

import gnureadline

97

import time

98

99

def startup_hook():

100

"""Called before each prompt is displayed."""

101

# Update prompt with current time

102

current_time = time.strftime("%H:%M:%S")

103

104

# Could set custom prompt here or update application state

105

print(f"\r[{current_time}] ", end='', flush=True)

106

107

# Return 0 for success

108

return 0

109

110

# Set startup hook

111

gnureadline.set_startup_hook(startup_hook)

112

113

# Example interactive loop

114

def interactive_shell():

115

print("Interactive shell with startup hook")

116

print("Type 'quit' to exit")

117

118

while True:

119

try:

120

line = input(">>> ")

121

if line.strip().lower() == 'quit':

122

break

123

print(f"You entered: {line}")

124

except EOFError:

125

break

126

except KeyboardInterrupt:

127

print("\nUse 'quit' to exit")

128

129

# interactive_shell() # Uncomment to test

130

```

131

132

### Pre-Input Hooks

133

134

Execute custom code after the prompt is displayed but before input begins.

135

136

```python { .api }

137

def set_pre_input_hook(function):

138

"""

139

Set pre-input hook called after prompt is displayed, before input.

140

141

Parameters:

142

- function: Hook function() -> int (return 0 for success)

143

Called after prompt display, before user can type

144

145

Note: Only available if HAVE_RL_PRE_INPUT_HOOK is defined

146

"""

147

```

148

149

### Usage Examples

150

151

```python

152

import gnureadline

153

154

def pre_input_hook():

155

"""Called after prompt, before input starts."""

156

# Insert default text that user can edit

157

gnureadline.insert_text("default_command ")

158

159

# Could also set up context-specific completion here

160

return 0

161

162

# Set pre-input hook

163

try:

164

gnureadline.set_pre_input_hook(pre_input_hook)

165

print("Pre-input hook set successfully")

166

except AttributeError:

167

print("Pre-input hook not available on this system")

168

169

# Test function

170

def test_pre_input():

171

try:

172

line = input("Command: ")

173

print(f"Final input: '{line}'")

174

except EOFError:

175

pass

176

177

# test_pre_input() # Uncomment to test

178

```

179

180

## Advanced Hook Examples

181

182

### Context-Aware Completion Display

183

184

```python

185

import gnureadline

186

import os

187

188

class SmartCompletionDisplay:

189

def __init__(self):

190

self.max_display_width = 80

191

self.max_items_per_line = 4

192

193

def display_matches(self, matches, num_matches, max_length):

194

"""Intelligent completion display based on match types and context."""

195

if num_matches == 0:

196

return

197

198

# Don't show if only one match (will be auto-completed)

199

if num_matches == 1:

200

return

201

202

print() # New line before completions

203

204

# Analyze matches

205

file_matches = []

206

dir_matches = []

207

cmd_matches = []

208

209

for match in matches:

210

if match.endswith('/'):

211

dir_matches.append(match)

212

elif os.path.exists(match):

213

file_matches.append(match)

214

else:

215

cmd_matches.append(match)

216

217

# Display by category

218

if cmd_matches:

219

self._display_category("Commands", cmd_matches, "πŸ”§")

220

221

if dir_matches:

222

self._display_category("Directories", dir_matches, "πŸ“")

223

224

if file_matches:

225

self._display_category("Files", file_matches, "πŸ“„")

226

227

# Show summary if many matches

228

if num_matches > 20:

229

print(f"... and {num_matches - len(matches)} more matches")

230

231

def _display_category(self, category, matches, icon):

232

"""Display a category of matches."""

233

if not matches:

234

return

235

236

print(f"{category}:")

237

238

# Sort matches

239

sorted_matches = sorted(matches)

240

241

# Display in columns if fits

242

if len(sorted_matches) <= self.max_items_per_line:

243

for match in sorted_matches:

244

print(f" {icon} {match}")

245

else:

246

# Multi-column display

247

cols = min(self.max_items_per_line, len(sorted_matches))

248

rows = (len(sorted_matches) + cols - 1) // cols

249

250

for row in range(rows):

251

line_items = []

252

for col in range(cols):

253

idx = row + col * rows

254

if idx < len(sorted_matches):

255

item = f"{icon} {sorted_matches[idx]}"

256

line_items.append(item)

257

258

if line_items:

259

# Format with consistent spacing

260

col_width = self.max_display_width // len(line_items)

261

formatted_line = "".join(item.ljust(col_width) for item in line_items)

262

print(f" {formatted_line.rstrip()}")

263

264

# Set up smart display

265

smart_display = SmartCompletionDisplay()

266

gnureadline.set_completion_display_matches_hook(smart_display.display_matches)

267

```

268

269

### Application State Hooks

270

271

```python

272

import gnureadline

273

import json

274

import os

275

276

class ApplicationHooks:

277

def __init__(self, app_name):

278

self.app_name = app_name

279

self.state_file = f".{app_name}_state.json"

280

self.session_commands = 0

281

self.load_state()

282

283

def startup_hook(self):

284

"""Startup hook that manages application state."""

285

self.session_commands += 1

286

287

# Auto-save state periodically

288

if self.session_commands % 10 == 0:

289

self.save_state()

290

291

# Update window title with command count

292

if 'TERM' in os.environ:

293

print(f"\033]0;{self.app_name} - Commands: {self.session_commands}\007", end='')

294

295

return 0

296

297

def pre_input_hook(self):

298

"""Pre-input hook for context setup."""

299

# Insert commonly used prefixes based on history patterns

300

if hasattr(self, 'common_prefixes'):

301

line_buffer = gnureadline.get_line_buffer()

302

if not line_buffer.strip():

303

# Suggest most common command prefix

304

most_common = max(self.common_prefixes.items(),

305

key=lambda x: x[1], default=(None, 0))[0]

306

if most_common and self.common_prefixes[most_common] > 5:

307

# Only suggest if used more than 5 times

308

gnureadline.insert_text(f"{most_common} ")

309

310

return 0

311

312

def load_state(self):

313

"""Load application state from file."""

314

try:

315

with open(self.state_file, 'r') as f:

316

state = json.load(f)

317

self.session_commands = state.get('total_commands', 0)

318

self.common_prefixes = state.get('common_prefixes', {})

319

except (FileNotFoundError, json.JSONDecodeError):

320

self.common_prefixes = {}

321

322

def save_state(self):

323

"""Save application state to file."""

324

# Analyze history for common patterns

325

self.analyze_history()

326

327

state = {

328

'total_commands': self.session_commands,

329

'common_prefixes': self.common_prefixes

330

}

331

332

try:

333

with open(self.state_file, 'w') as f:

334

json.dump(state, f)

335

except OSError:

336

pass # Ignore save errors

337

338

def analyze_history(self):

339

"""Analyze command history for patterns."""

340

length = gnureadline.get_current_history_length()

341

prefixes = {}

342

343

for i in range(max(1, length - 50), length + 1): # Last 50 commands

344

item = gnureadline.get_history_item(i)

345

if item:

346

# Extract first word as prefix

347

prefix = item.split()[0] if item.split() else ''

348

if prefix:

349

prefixes[prefix] = prefixes.get(prefix, 0) + 1

350

351

self.common_prefixes = prefixes

352

353

# Usage example

354

app_hooks = ApplicationHooks("myapp")

355

gnureadline.set_startup_hook(app_hooks.startup_hook)

356

357

try:

358

gnureadline.set_pre_input_hook(app_hooks.pre_input_hook)

359

except AttributeError:

360

print("Pre-input hook not available")

361

362

# Clean up on exit

363

import atexit

364

atexit.register(app_hooks.save_state)

365

```

366

367

### Interactive Tutorial Hook

368

369

```python

370

import gnureadline

371

372

class TutorialHooks:

373

def __init__(self):

374

self.step = 0

375

self.tutorial_steps = [

376

"Try typing 'help' and press tab for completion",

377

"Use 'history' to see command history",

378

"Press Ctrl-R to search history",

379

"Type 'complete test' and press tab",

380

"Tutorial completed!"

381

]

382

383

def startup_hook(self):

384

"""Display tutorial hints."""

385

if self.step < len(self.tutorial_steps):

386

hint = self.tutorial_steps[self.step]

387

print(f"\nπŸ’‘ Tutorial step {self.step + 1}: {hint}")

388

self.step += 1

389

390

return 0

391

392

def completion_display(self, matches, num_matches, max_length):

393

"""Tutorial-aware completion display."""

394

print(f"\n🎯 Found {num_matches} completions:")

395

396

for i, match in enumerate(matches[:10]): # Show max 10

397

print(f" {i+1}. {match}")

398

399

if num_matches > 10:

400

print(f" ... and {num_matches - 10} more")

401

402

print("πŸ’‘ Press tab again to cycle through options")

403

404

# Set up tutorial

405

tutorial = TutorialHooks()

406

gnureadline.set_startup_hook(tutorial.startup_hook)

407

gnureadline.set_completion_display_matches_hook(tutorial.completion_display)

408

409

# Simple completer for tutorial

410

def tutorial_completer(text, state):

411

commands = ['help', 'history', 'complete', 'test', 'tutorial', 'quit']

412

if state == 0:

413

tutorial_completer.matches = [cmd for cmd in commands if cmd.startswith(text)]

414

try:

415

return tutorial_completer.matches[state]

416

except (IndexError, AttributeError):

417

return None

418

419

gnureadline.set_completer(tutorial_completer)

420

gnureadline.parse_and_bind("tab: complete")

421

```

422

423

## Hook Error Handling

424

425

Hooks should handle errors gracefully and return appropriate values:

426

427

```python

428

import gnureadline

429

import traceback

430

431

def safe_startup_hook():

432

"""Startup hook with error handling."""

433

try:

434

# Your startup logic here

435

print("Startup hook executed")

436

return 0 # Success

437

except Exception as e:

438

print(f"Startup hook error: {e}")

439

traceback.print_exc()

440

return -1 # Error

441

442

def safe_completion_display(matches, num_matches, max_length):

443

"""Completion display with error handling."""

444

try:

445

# Your display logic here

446

for match in matches[:10]:

447

print(f" {match}")

448

except Exception as e:

449

print(f"Completion display error: {e}")

450

# Fallback to default display

451

for match in matches:

452

print(match)

453

454

gnureadline.set_startup_hook(safe_startup_hook)

455

gnureadline.set_completion_display_matches_hook(safe_completion_display)

456

```

457

458

## Platform Availability

459

460

Note that `set_pre_input_hook` is only available if the underlying GNU Readline library was compiled with `HAVE_RL_PRE_INPUT_HOOK` defined. You can test availability:

461

462

```python

463

import gnureadline

464

465

# Check if pre-input hook is available

466

try:

467

gnureadline.set_pre_input_hook(None) # Clear any existing hook

468

print("Pre-input hook is available")

469

except AttributeError:

470

print("Pre-input hook is not available on this system")

471

```