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
```