1
+ """Internal constants."""
2
+
1
3
from __future__ import annotations
2
4
5
+ import io
6
+ import logging
3
7
import typing as t
4
8
from dataclasses import dataclass , field
5
9
6
10
from libtmux ._internal .dataclasses import SkipDefaultFieldsReprMixin
11
+ from libtmux ._internal .sparse_array import SparseArray , is_sparse_array_list
7
12
8
- TerminalFeatures = dict [str , list [str ]]
13
+ if t .TYPE_CHECKING :
14
+ from typing_extensions import TypeAlias
9
15
10
16
11
17
T = t .TypeVar ("T" )
12
18
19
+ TerminalFeatures = dict [str , list [str ]]
20
+ HookArray : TypeAlias = "dict[str, SparseArray[str]]"
13
21
14
- class TmuxArray (dict [int , T ], t .Generic [T ]):
15
- """Support non-sequential indexes without raising IndexError."""
16
-
17
- def add (self , index : int , value : T ) -> None :
18
- self [index ] = value
19
-
20
- def append (self , value : T ) -> None :
21
- index = max (self .keys ()) + 1
22
- self [index ] = value
23
-
24
- def iter_values (self ) -> t .Iterator [T ]:
25
- for index in sorted (self .keys ()):
26
- yield self [index ]
27
-
28
- def as_list (self ) -> list [T ]:
29
- return [self [index ] for index in sorted (self .keys ())]
22
+ logger = logging .getLogger (__name__ )
30
23
31
24
32
25
@dataclass (repr = False )
@@ -35,7 +28,7 @@ class ServerOptions(
35
28
):
36
29
backspace : str | None = field (default = None )
37
30
buffer_limit : int | None = field (default = None )
38
- command_alias : TmuxArray [str ] = field (default_factory = TmuxArray )
31
+ command_alias : SparseArray [str ] = field (default_factory = SparseArray )
39
32
default_terminal : str | None = field (default = None )
40
33
copy_command : str | None = field (default = None )
41
34
escape_time : int | None = field (default = None )
@@ -49,8 +42,8 @@ class ServerOptions(
49
42
prompt_history_limit : int | None = field (default = None )
50
43
set_clipboard : t .Literal ["on" , "external" , "off" ] | None = field (default = None )
51
44
terminal_features : TerminalFeatures = field (default_factory = dict )
52
- terminal_overrides : TmuxArray [str ] = field (default_factory = TmuxArray )
53
- user_keys : TmuxArray [str ] = field (default_factory = TmuxArray )
45
+ terminal_overrides : SparseArray [str ] = field (default_factory = SparseArray )
46
+ user_keys : SparseArray [str ] = field (default_factory = SparseArray )
54
47
55
48
def __init__ (self , ** kwargs : object ) -> None :
56
49
# Convert hyphenated keys to underscored attribute names and assign values
@@ -121,7 +114,7 @@ class SessionOptions(
121
114
status_right_length : int | None = field (default = None )
122
115
status_right_style : str | None = field (default = None )
123
116
status_style : str | None = field (default = None )
124
- update_environment : list [str ] | None = field (default = None )
117
+ update_environment : SparseArray [str ] = field (default_factory = SparseArray )
125
118
visual_activity : t .Literal ["on" , "off" , "both" ] | None = field (default = None )
126
119
visual_bell : t .Literal ["on" , "off" , "both" ] | None = field (default = None )
127
120
visual_silence : t .Literal ["on" , "off" , "both" ] | None = field (default = None )
@@ -245,3 +238,224 @@ def __init__(self, **kwargs: object) -> None:
245
238
key_underscored = key .replace ("-" , "_" )
246
239
key_asterisk_removed = key_underscored .rstrip ("*" )
247
240
setattr (self , key_asterisk_removed , value )
241
+
242
+
243
+ @dataclass (repr = False )
244
+ class Hooks (
245
+ SkipDefaultFieldsReprMixin ,
246
+ ):
247
+ """tmux hooks data structure."""
248
+
249
+ # --- Tmux normal hooks ---
250
+ # Run when a window has activity. See monitor-activity.
251
+ alert_activity : SparseArray [str ] = field (default_factory = SparseArray )
252
+ # Run when a window has received a bell. See monitor-bell.
253
+ alert_bell : SparseArray [str ] = field (default_factory = SparseArray )
254
+ # Run when a window has been silent. See monitor-silence.
255
+ alert_silence : SparseArray [str ] = field (default_factory = SparseArray )
256
+ # Run when a client becomes the latest active client of its session.
257
+ client_active : SparseArray [str ] = field (default_factory = SparseArray )
258
+ # Run when a client is attached.
259
+ client_attached : SparseArray [str ] = field (default_factory = SparseArray )
260
+ # Run when a client is detached.
261
+ client_detached : SparseArray [str ] = field (default_factory = SparseArray )
262
+ # Run when focus enters a client.
263
+ client_focus_in : SparseArray [str ] = field (default_factory = SparseArray )
264
+ # Run when focus exits a client.
265
+ client_focus_out : SparseArray [str ] = field (default_factory = SparseArray )
266
+ # Run when a client is resized.
267
+ client_resized : SparseArray [str ] = field (default_factory = SparseArray )
268
+ # Run when a client's attached session is changed.
269
+ client_session_changed : SparseArray [str ] = field (default_factory = SparseArray )
270
+ # Run when the program running in a pane exits, but remain-on-exit is on so the pane
271
+ # has not closed.
272
+ pane_died : SparseArray [str ] = field (default_factory = SparseArray )
273
+ # Run when the program running in a pane exits.
274
+ pane_exited : SparseArray [str ] = field (default_factory = SparseArray )
275
+ # Run when the focus enters a pane, if the focus-events option is on.
276
+ pane_focus_in : SparseArray [str ] = field (default_factory = SparseArray )
277
+ # Run when the focus exits a pane, if the focus-events option is on.
278
+ pane_focus_out : SparseArray [str ] = field (default_factory = SparseArray )
279
+ # Run when the terminal clipboard is set using the xterm(1) escape sequence.
280
+ pane_set_clipboard : SparseArray [str ] = field (default_factory = SparseArray )
281
+ # Run when a new session created.
282
+ session_created : SparseArray [str ] = field (default_factory = SparseArray )
283
+ # Run when a session closed.
284
+ session_closed : SparseArray [str ] = field (default_factory = SparseArray )
285
+ # Run when a session is renamed.
286
+ session_renamed : SparseArray [str ] = field (default_factory = SparseArray )
287
+ # Run when a window is linked into a session.
288
+ window_linked : SparseArray [str ] = field (default_factory = SparseArray )
289
+ # Run when a window is renamed.
290
+ window_renamed : SparseArray [str ] = field (default_factory = SparseArray )
291
+ # Run when a window is resized. This may be after the client-resized hook is run.
292
+ window_resized : SparseArray [str ] = field (default_factory = SparseArray )
293
+ # Run when a window is unlinked from a session.
294
+ window_unlinked : SparseArray [str ] = field (default_factory = SparseArray )
295
+
296
+ # --- Tmux control mode hooks ---
297
+ # The client has detached.
298
+ client_detached_control : SparseArray [str ] = field (default_factory = SparseArray )
299
+ # The client is now attached to the session with ID session-id, which is named name.
300
+ client_session_changed_control : SparseArray [str ] = field (
301
+ default_factory = SparseArray ,
302
+ )
303
+ # An error has happened in a configuration file.
304
+ config_error : SparseArray [str ] = field (default_factory = SparseArray )
305
+ # The pane has been continued after being paused (if the pause-after flag is set,
306
+ # see refresh-client -A).
307
+ continue_control : SparseArray [str ] = field (default_factory = SparseArray )
308
+ # The tmux client is exiting immediately, either because it is not attached to any
309
+ # session or an error occurred.
310
+ exit_control : SparseArray [str ] = field (default_factory = SparseArray )
311
+ # New form of %output sent when the pause-after flag is set.
312
+ extended_output : SparseArray [str ] = field (default_factory = SparseArray )
313
+ # The layout of a window with ID window-id changed.
314
+ layout_change : SparseArray [str ] = field (default_factory = SparseArray )
315
+ # A message sent with the display-message command.
316
+ message_control : SparseArray [str ] = field (default_factory = SparseArray )
317
+ # A window pane produced output.
318
+ output : SparseArray [str ] = field (default_factory = SparseArray )
319
+ # The pane with ID pane-id has changed mode.
320
+ pane_mode_changed : SparseArray [str ] = field (default_factory = SparseArray )
321
+ # Paste buffer name has been changed.
322
+ paste_buffer_changed : SparseArray [str ] = field (default_factory = SparseArray )
323
+ # Paste buffer name has been deleted.
324
+ paste_buffer_deleted : SparseArray [str ] = field (default_factory = SparseArray )
325
+ # The pane has been paused (if the pause-after flag is set).
326
+ pause_control : SparseArray [str ] = field (default_factory = SparseArray )
327
+ # The client is now attached to the session with ID session-id, which is named name.
328
+ session_changed_control : SparseArray [str ] = field (default_factory = SparseArray )
329
+ # The current session was renamed to name.
330
+ session_renamed_control : SparseArray [str ] = field (default_factory = SparseArray )
331
+ # The session with ID session-id changed its active window to the window with ID
332
+ # window-id.
333
+ session_window_changed : SparseArray [str ] = field (default_factory = SparseArray )
334
+ # A session was created or destroyed.
335
+ sessions_changed : SparseArray [str ] = field (default_factory = SparseArray )
336
+ # The value of the format associated with subscription name has changed to value.
337
+ subscription_changed : SparseArray [str ] = field (default_factory = SparseArray )
338
+ # The window with ID window-id was created but is not linked to the current session.
339
+ unlinked_window_add : SparseArray [str ] = field (default_factory = SparseArray )
340
+ # The window with ID window-id, which is not linked to the current session, was
341
+ # closed.
342
+ unlinked_window_close : SparseArray [str ] = field (default_factory = SparseArray )
343
+ # The window with ID window-id, which is not linked to the current session, was
344
+ # renamed.
345
+ unlinked_window_renamed : SparseArray [str ] = field (default_factory = SparseArray )
346
+ # The window with ID window-id was linked to the current session.
347
+ window_add : SparseArray [str ] = field (default_factory = SparseArray )
348
+ # The window with ID window-id closed.
349
+ window_close : SparseArray [str ] = field (default_factory = SparseArray )
350
+ # The layout of a window with ID window-id changed. The new layout is window-layout.
351
+ # The window's visible layout is window-visible-layout and the window flags are
352
+ # window-flags.
353
+ window_layout_changed : SparseArray [str ] = field (default_factory = SparseArray )
354
+ # The active pane in the window with ID window-id changed to the pane with ID
355
+ # pane-id.
356
+ window_pane_changed : SparseArray [str ] = field (default_factory = SparseArray )
357
+ # The window with ID window-id was renamed to name.
358
+ window_renamed_control : SparseArray [str ] = field (default_factory = SparseArray )
359
+
360
+ # --- After hooks - Run after specific tmux commands complete ---
361
+ # Runs after 'bind-key' completes
362
+ after_bind_key : SparseArray [str ] = field (default_factory = SparseArray )
363
+ # Runs after 'capture-pane' completes
364
+ after_capture_pane : SparseArray [str ] = field (default_factory = SparseArray )
365
+ # Runs after 'copy-mode' completes
366
+ after_copy_mode : SparseArray [str ] = field (default_factory = SparseArray )
367
+ # Runs after 'display-message' completes
368
+ after_display_message : SparseArray [str ] = field (default_factory = SparseArray )
369
+ # Runs after 'display-panes' completes
370
+ after_display_panes : SparseArray [str ] = field (default_factory = SparseArray )
371
+ # Runs after 'kill-pane' completes
372
+ after_kill_pane : SparseArray [str ] = field (default_factory = SparseArray )
373
+ # Runs after 'list-buffers' completes
374
+ after_list_buffers : SparseArray [str ] = field (default_factory = SparseArray )
375
+ # Runs after 'list-clients' completes
376
+ after_list_clients : SparseArray [str ] = field (default_factory = SparseArray )
377
+ # Runs after 'list-keys' completes
378
+ after_list_keys : SparseArray [str ] = field (default_factory = SparseArray )
379
+ # Runs after 'list-panes' completes
380
+ after_list_panes : SparseArray [str ] = field (default_factory = SparseArray )
381
+ # Runs after 'list-sessions' completes
382
+ after_list_sessions : SparseArray [str ] = field (default_factory = SparseArray )
383
+ # Runs after 'list-windows' completes
384
+ after_list_windows : SparseArray [str ] = field (default_factory = SparseArray )
385
+ # Runs after 'load-buffer' completes
386
+ after_load_buffer : SparseArray [str ] = field (default_factory = SparseArray )
387
+ # Runs after 'lock-server' completes
388
+ after_lock_server : SparseArray [str ] = field (default_factory = SparseArray )
389
+ # Runs after 'new-session' completes
390
+ after_new_session : SparseArray [str ] = field (default_factory = SparseArray )
391
+ # Runs after 'new-window' completes
392
+ after_new_window : SparseArray [str ] = field (default_factory = SparseArray )
393
+ # Runs after 'paste-buffer' completes
394
+ after_paste_buffer : SparseArray [str ] = field (default_factory = SparseArray )
395
+ # Runs after 'pipe-pane' completes
396
+ after_pipe_pane : SparseArray [str ] = field (default_factory = SparseArray )
397
+ # Runs after 'queue' command is processed
398
+ after_queue : SparseArray [str ] = field (default_factory = SparseArray )
399
+ # Runs after 'refresh-client' completes
400
+ after_refresh_client : SparseArray [str ] = field (default_factory = SparseArray )
401
+ # Runs after 'rename-session' completes
402
+ after_rename_session : SparseArray [str ] = field (default_factory = SparseArray )
403
+ # Runs after 'rename-window' completes
404
+ after_rename_window : SparseArray [str ] = field (default_factory = SparseArray )
405
+ # Runs after 'resize-pane' completes
406
+ after_resize_pane : SparseArray [str ] = field (default_factory = SparseArray )
407
+ # Runs after 'resize-window' completes
408
+ after_resize_window : SparseArray [str ] = field (default_factory = SparseArray )
409
+ # Runs after 'save-buffer' completes
410
+ after_save_buffer : SparseArray [str ] = field (default_factory = SparseArray )
411
+ # Runs after 'select-layout' completes
412
+ after_select_layout : SparseArray [str ] = field (default_factory = SparseArray )
413
+ # Runs after 'select-pane' completes
414
+ after_select_pane : SparseArray [str ] = field (default_factory = SparseArray )
415
+ # Runs after 'select-window' completes
416
+ after_select_window : SparseArray [str ] = field (default_factory = SparseArray )
417
+ # Runs after 'send-keys' completes
418
+ after_send_keys : SparseArray [str ] = field (default_factory = SparseArray )
419
+ # Runs after 'set-buffer' completes
420
+ after_set_buffer : SparseArray [str ] = field (default_factory = SparseArray )
421
+ # Runs after 'set-environment' completes
422
+ after_set_environment : SparseArray [str ] = field (default_factory = SparseArray )
423
+ # Runs after 'set-hook' completes
424
+ after_set_hook : SparseArray [str ] = field (default_factory = SparseArray )
425
+ # Runs after 'set-option' completes
426
+ after_set_option : SparseArray [str ] = field (default_factory = SparseArray )
427
+ # Runs after 'show-environment' completes
428
+ after_show_environment : SparseArray [str ] = field (default_factory = SparseArray )
429
+ # Runs after 'show-messages' completes
430
+ after_show_messages : SparseArray [str ] = field (default_factory = SparseArray )
431
+ # Runs after 'show-options' completes
432
+ after_show_options : SparseArray [str ] = field (default_factory = SparseArray )
433
+ # Runs after 'split-window' completes
434
+ after_split_window : SparseArray [str ] = field (default_factory = SparseArray )
435
+ # Runs after 'unbind-key' completes
436
+ after_unbind_key : SparseArray [str ] = field (default_factory = SparseArray )
437
+
438
+ @classmethod
439
+ def from_stdout (cls , value : list [str ]) -> Hooks :
440
+ from libtmux .options import (
441
+ explode_arrays ,
442
+ explode_complex ,
443
+ parse_options_to_dict ,
444
+ )
445
+
446
+ output_exploded = explode_complex (
447
+ explode_arrays (
448
+ parse_options_to_dict (
449
+ io .StringIO ("\n " .join (value )),
450
+ ),
451
+ force_array = True ,
452
+ ),
453
+ )
454
+
455
+ assert is_sparse_array_list (output_exploded )
456
+
457
+ output_renamed : HookArray = {
458
+ k .lstrip ("%" ).replace ("-" , "_" ): v for k , v in output_exploded .items ()
459
+ }
460
+
461
+ return cls (** output_renamed )
0 commit comments