66
77from __future__ import annotations
88
9+ import time
910import typing as t
1011from dataclasses import dataclass
11- from typing import Callable , TypeVar
12+ from typing import (
13+ TypeVar ,
14+ )
1215
13- from libtmux .test .retry import retry_until
16+ from libtmux .exc import LibTmuxException
17+ from libtmux .test .retry import WaitTimeout , retry_until
1418
1519if t .TYPE_CHECKING :
1620 from libtmux .pane import Pane
1721
1822T = TypeVar ("T" )
1923
2024
25+ class WaiterError (LibTmuxException ):
26+ """Base exception for waiter errors."""
27+
28+
29+ class WaiterTimeoutError (WaiterError ):
30+ """Exception raised when waiting for content times out."""
31+
32+
33+ class WaiterContentError (WaiterError ):
34+ """Exception raised when there's an error getting or checking content."""
35+
36+
2137@dataclass
2238class WaitResult (t .Generic [T ]):
2339 """Result of a wait operation."""
@@ -43,112 +59,101 @@ def __init__(self, pane: Pane, timeout: float = 2.0) -> None:
4359 self .pane = pane
4460 self .timeout = timeout
4561
46- def wait_for_content (
62+ def _check_content (
4763 self ,
48- predicate : Callable [[str ], bool ],
49- * ,
50- timeout : float | None = None ,
51- error_message : str | None = None ,
52- ) -> WaitResult [str ]:
53- """Wait for pane content to match predicate.
64+ predicate : t .Callable [[str ], bool ],
65+ result : WaitResult ,
66+ ) -> bool :
67+ """Check pane content against predicate.
5468
5569 Parameters
5670 ----------
5771 predicate : Callable[[str], bool]
5872 Function that takes pane content as string and returns bool
59- timeout : float | None, optional
60- Timeout in seconds, by default None (uses instance timeout)
61- error_message : str | None, optional
62- Custom error message if timeout occurs, by default None
73+ result : WaitResult
74+ Result object to store content if predicate matches
6375
6476 Returns
6577 -------
66- WaitResult[str]
67- Result containing success status and pane content if successful
78+ bool
79+ True if predicate matches, False otherwise
80+
81+ Raises
82+ ------
83+ WaiterContentError
84+ If there's an error capturing pane content
6885 """
69- timeout = timeout or self .timeout
70- result = WaitResult [str ](success = False )
71-
72- def check_content () -> bool :
73- try :
74- content = "\n " .join (self .pane .capture_pane ())
75- if predicate (content ):
76- result .success = True
77- result .value = content
78- return True
79- else :
80- return False
81- except Exception as e :
82- result .error = e
83- return False
86+ try :
87+ content = "\n " .join (self .pane .capture_pane ())
88+ if predicate (content ):
89+ result .value = content
90+ return True
91+ return False
92+ except Exception as e :
93+ error = WaiterContentError ("Error capturing pane content" )
94+ error .__cause__ = e
95+ raise error from e
8496
97+ def wait_for_content (
98+ self ,
99+ predicate : t .Callable [[str ], bool ],
100+ timeout_seconds : float | None = None ,
101+ interval_seconds : float | None = None ,
102+ error_message : str | None = None ,
103+ ) -> WaitResult :
104+ """Wait for content in the pane to match a predicate."""
105+ result = WaitResult (success = False , value = None , error = None )
85106 try :
86- success = retry_until (check_content , timeout , raises = False )
107+ # Give the shell a moment to be ready
108+ time .sleep (0.1 )
109+ success = retry_until (
110+ lambda : self ._check_content (predicate , result ),
111+ seconds = timeout_seconds or self .timeout ,
112+ interval = interval_seconds ,
113+ raises = True ,
114+ )
115+ result .success = success
87116 if not success :
88- result .error = Exception (
117+ result .error = WaiterTimeoutError (
89118 error_message or "Timed out waiting for content" ,
90119 )
91- except Exception as e :
120+ except WaitTimeout as e :
121+ result .error = WaiterTimeoutError (error_message or str (e ))
122+ result .success = False
123+ except WaiterContentError as e :
92124 result .error = e
93- if error_message :
94- result .error = Exception (error_message )
95-
125+ result .success = False
126+ except Exception as e :
127+ if isinstance (e , (WaiterTimeoutError , WaiterContentError )):
128+ result .error = e
129+ else :
130+ result .error = WaiterContentError ("Error capturing pane content" )
131+ result .error .__cause__ = e
132+ result .success = False
96133 return result
97134
98135 def wait_for_prompt (
99136 self ,
100137 prompt : str ,
101- * ,
102- timeout : float | None = None ,
138+ timeout_seconds : float | None = None ,
103139 error_message : str | None = None ,
104- ) -> WaitResult [str ]:
105- """Wait for specific prompt to appear in pane.
106-
107- Parameters
108- ----------
109- prompt : str
110- The prompt text to wait for
111- timeout : float | None, optional
112- Timeout in seconds, by default None (uses instance timeout)
113- error_message : str | None, optional
114- Custom error message if timeout occurs, by default None
115-
116- Returns
117- -------
118- WaitResult[str]
119- Result containing success status and pane content if successful
120- """
140+ ) -> WaitResult :
141+ """Wait for a specific prompt to appear in the pane."""
121142 return self .wait_for_content (
122143 lambda content : prompt in content and len (content .strip ()) > 0 ,
123- timeout = timeout ,
144+ timeout_seconds = timeout_seconds ,
124145 error_message = error_message or f"Prompt '{ prompt } ' not found in pane" ,
125146 )
126147
127148 def wait_for_text (
128149 self ,
129150 text : str ,
130- * ,
131- timeout : float | None = None ,
151+ timeout_seconds : float | None = None ,
132152 error_message : str | None = None ,
133- ) -> WaitResult [str ]:
134- """Wait for specific text to appear in pane.
135-
136- Parameters
137- ----------
138- text : str
139- The text to wait for
140- timeout : float | None, optional
141- Timeout in seconds, by default None (uses instance timeout)
142- error_message : str | None, optional
143- Custom error message if timeout occurs, by default None
144-
145- Returns
146- -------
147- WaitResult[str]
148- Result containing success status and pane content if successful
149- """
153+ ) -> WaitResult :
154+ """Wait for specific text to appear in the pane."""
150155 return self .wait_for_content (
151156 lambda content : text in content ,
152- timeout = timeout ,
157+ timeout_seconds = timeout_seconds ,
153158 error_message = error_message or f"Text '{ text } ' not found in pane" ,
154159 )
0 commit comments