Coverage for src/prosemark/freewriting/ports/tui_adapter.py: 100%

81 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-09-24 18:08 +0000

1"""TUI adapter port interfaces for freewriting interface. 

2 

3This module defines the port interfaces for the terminal user interface 

4components of the freewriting feature. 

5""" 

6 

7from __future__ import annotations 

8 

9from abc import ABC, abstractmethod 

10from dataclasses import dataclass 

11from typing import TYPE_CHECKING, Any 

12 

13if TYPE_CHECKING: # pragma: no cover 

14 from collections.abc import Callable 

15 

16 from prosemark.freewriting.domain.models import FreewriteSession, SessionConfig 

17 from prosemark.freewriting.ports.freewrite_service import FreewriteServicePort 

18 

19 

20@dataclass(frozen=True) 

21class UIState: 

22 """Current state of the TUI interface.""" 

23 

24 session: FreewriteSession | None 

25 input_text: str 

26 display_lines: list[str] 

27 word_count: int 

28 elapsed_time: int 

29 time_remaining: int | None 

30 progress_percent: float | None 

31 error_message: str | None 

32 is_paused: bool 

33 

34 

35@dataclass(frozen=True) 

36class TUIConfig: 

37 """Configuration for TUI appearance and behavior.""" 

38 

39 theme: str 

40 content_height_percent: int = 80 

41 input_height_percent: int = 20 

42 show_word_count: bool = True 

43 show_timer: bool = True 

44 auto_scroll: bool = True 

45 max_display_lines: int = 1000 

46 

47 

48class TUIAdapterPort(ABC): 

49 """Port interface for TUI operations. 

50 

51 This port defines the contract for main TUI operations such as 

52 session management and content handling. 

53 """ 

54 

55 @property 

56 @abstractmethod 

57 def freewrite_service(self) -> FreewriteServicePort: 

58 """Freewrite service instance for session operations. 

59 

60 Returns: 

61 The freewrite service instance used by this TUI adapter. 

62 

63 """ 

64 

65 @abstractmethod 

66 def initialize_session(self, config: SessionConfig) -> FreewriteSession: 

67 """Initialize a new freewriting session. 

68 

69 Args: 

70 config: Session configuration from CLI. 

71 

72 Returns: 

73 Created session object. 

74 

75 Raises: 

76 ValidationError: If configuration is invalid. 

77 

78 """ 

79 

80 @abstractmethod 

81 def handle_input_submission(self, session: FreewriteSession, input_text: str) -> FreewriteSession: 

82 """Handle user pressing ENTER in input box. 

83 

84 Args: 

85 session: Current session state. 

86 input_text: Text from input box. 

87 

88 Returns: 

89 Updated session after content is appended. 

90 

91 Raises: 

92 FileSystemError: If save operation fails. 

93 

94 """ 

95 

96 @abstractmethod 

97 def get_display_content(self, session: FreewriteSession, max_lines: int) -> list[str]: 

98 """Get content lines to display in content area. 

99 

100 Args: 

101 session: Current session. 

102 max_lines: Maximum lines to return (for bottom of file view). 

103 

104 Returns: 

105 List of content lines for display. 

106 

107 """ 

108 

109 @abstractmethod 

110 def calculate_progress(self, session: FreewriteSession) -> dict[str, Any]: 

111 """Calculate session progress metrics. 

112 

113 Args: 

114 session: Current session. 

115 

116 Returns: 

117 Dictionary with progress information: 

118 - word_count: int 

119 - elapsed_time: int 

120 - time_remaining: Optional[int] 

121 - progress_percent: Optional[float] 

122 - goals_met: Dict[str, bool] 

123 

124 """ 

125 

126 @abstractmethod 

127 def handle_error(self, error: Exception, session: FreewriteSession) -> UIState: 

128 """Handle errors during session operations. 

129 

130 Args: 

131 error: The exception that occurred. 

132 session: Current session state. 

133 

134 Returns: 

135 Updated UI state with error information. 

136 

137 """ 

138 

139 

140class TUIEventPort(ABC): 

141 """Port interface for TUI event handling. 

142 

143 This port defines the contract for handling various TUI events 

144 such as user input and session state changes. 

145 """ 

146 

147 @abstractmethod 

148 def on_input_change(self, callback: Callable[[str], None]) -> None: 

149 """Register callback for input text changes. 

150 

151 Args: 

152 callback: Function to call when input changes. 

153 

154 """ 

155 

156 @abstractmethod 

157 def on_input_submit(self, callback: Callable[[str], None]) -> None: 

158 """Register callback for input submission (ENTER key). 

159 

160 Args: 

161 callback: Function to call when input is submitted. 

162 

163 """ 

164 

165 @abstractmethod 

166 def on_session_pause(self, callback: Callable[[], None]) -> None: 

167 """Register callback for session pause events. 

168 

169 Args: 

170 callback: Function to call when session is paused. 

171 

172 """ 

173 

174 @abstractmethod 

175 def on_session_resume(self, callback: Callable[[], None]) -> None: 

176 """Register callback for session resume events. 

177 

178 Args: 

179 callback: Function to call when session is resumed. 

180 

181 """ 

182 

183 @abstractmethod 

184 def on_session_exit(self, callback: Callable[[], None]) -> None: 

185 """Register callback for session exit events. 

186 

187 Args: 

188 callback: Function to call when session exits. 

189 

190 """ 

191 

192 

193class TUIDisplayPort(ABC): 

194 """Port interface for TUI display operations. 

195 

196 This port defines the contract for updating the TUI display 

197 components such as content area and status display. 

198 """ 

199 

200 @abstractmethod 

201 def update_content_area(self, lines: list[str]) -> None: 

202 """Update the main content display area. 

203 

204 Args: 

205 lines: Content lines to display. 

206 

207 """ 

208 

209 @abstractmethod 

210 def update_stats_display(self, stats: dict[str, Any]) -> None: 

211 """Update statistics display (word count, timer, etc.). 

212 

213 Args: 

214 stats: Statistics to display. 

215 

216 """ 

217 

218 @abstractmethod 

219 def clear_input_area(self) -> None: 

220 """Clear the input text box.""" 

221 

222 @abstractmethod 

223 def show_error_message(self, message: str) -> None: 

224 """Display error message to user. 

225 

226 Args: 

227 message: Error message to show. 

228 

229 """ 

230 

231 @abstractmethod 

232 def hide_error_message(self) -> None: 

233 """Hide any currently displayed error message.""" 

234 

235 @abstractmethod 

236 def set_theme(self, theme_name: str) -> None: 

237 """Apply UI theme. 

238 

239 Args: 

240 theme_name: Name of theme to apply. 

241 

242 """ 

243 

244 

245# UI Event Data Classes 

246@dataclass(frozen=True) 

247class InputSubmittedEvent: 

248 """Event fired when user submits input.""" 

249 

250 content: str 

251 timestamp: str 

252 

253 

254@dataclass(frozen=True) 

255class SessionProgressEvent: 

256 """Event fired when session progress updates.""" 

257 

258 word_count: int 

259 elapsed_time: int 

260 progress_percent: float | None 

261 

262 

263@dataclass(frozen=True) 

264class ErrorEvent: 

265 """Event fired when an error occurs.""" 

266 

267 error_type: str 

268 message: str 

269 recoverable: bool 

270 

271 

272@dataclass(frozen=True) 

273class SessionCompletedEvent: 

274 """Event fired when session completes.""" 

275 

276 final_word_count: int 

277 total_time: int 

278 output_file: str