Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/prompt_toolkit/layout/margins.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

122 statements  

1""" 

2Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. 

3""" 

4 

5from __future__ import annotations 

6 

7from abc import ABCMeta, abstractmethod 

8from collections.abc import Callable 

9from typing import TYPE_CHECKING 

10 

11from prompt_toolkit.filters import FilterOrBool, to_filter 

12from prompt_toolkit.formatted_text import ( 

13 StyleAndTextTuples, 

14 fragment_list_to_text, 

15 to_formatted_text, 

16) 

17from prompt_toolkit.utils import get_cwidth 

18 

19from .controls import UIContent 

20 

21if TYPE_CHECKING: 

22 from .containers import WindowRenderInfo 

23 

24__all__ = [ 

25 "Margin", 

26 "NumberedMargin", 

27 "ScrollbarMargin", 

28 "ConditionalMargin", 

29 "PromptMargin", 

30] 

31 

32 

33class Margin(metaclass=ABCMeta): 

34 """ 

35 Base interface for a margin. 

36 """ 

37 

38 @abstractmethod 

39 def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: 

40 """ 

41 Return the width that this margin is going to consume. 

42 

43 :param get_ui_content: Callable that asks the user control to create 

44 a :class:`.UIContent` instance. This can be used for instance to 

45 obtain the number of lines. 

46 """ 

47 return 0 

48 

49 @abstractmethod 

50 def create_margin( 

51 self, window_render_info: WindowRenderInfo, width: int, height: int 

52 ) -> StyleAndTextTuples: 

53 """ 

54 Creates a margin. 

55 This should return a list of (style_str, text) tuples. 

56 

57 :param window_render_info: 

58 :class:`~prompt_toolkit.layout.containers.WindowRenderInfo` 

59 instance, generated after rendering and copying the visible part of 

60 the :class:`~prompt_toolkit.layout.controls.UIControl` into the 

61 :class:`~prompt_toolkit.layout.containers.Window`. 

62 :param width: The width that's available for this margin. (As reported 

63 by :meth:`.get_width`.) 

64 :param height: The height that's available for this margin. (The height 

65 of the :class:`~prompt_toolkit.layout.containers.Window`.) 

66 """ 

67 return [] 

68 

69 

70class NumberedMargin(Margin): 

71 """ 

72 Margin that displays the line numbers. 

73 

74 :param relative: Number relative to the cursor position. Similar to the Vi 

75 'relativenumber' option. 

76 :param display_tildes: Display tildes after the end of the document, just 

77 like Vi does. 

78 """ 

79 

80 def __init__( 

81 self, relative: FilterOrBool = False, display_tildes: FilterOrBool = False 

82 ) -> None: 

83 self.relative = to_filter(relative) 

84 self.display_tildes = to_filter(display_tildes) 

85 

86 def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: 

87 line_count = get_ui_content().line_count 

88 return max(3, len(f"{line_count}") + 1) 

89 

90 def create_margin( 

91 self, window_render_info: WindowRenderInfo, width: int, height: int 

92 ) -> StyleAndTextTuples: 

93 relative = self.relative() 

94 

95 style = "class:line-number" 

96 style_current = "class:line-number.current" 

97 

98 # Get current line number. 

99 current_lineno = window_render_info.ui_content.cursor_position.y 

100 

101 # Construct margin. 

102 result: StyleAndTextTuples = [] 

103 last_lineno = None 

104 

105 for y, lineno in enumerate(window_render_info.displayed_lines): 

106 # Only display line number if this line is not a continuation of the previous line. 

107 if lineno != last_lineno: 

108 if lineno is None: 

109 pass 

110 elif lineno == current_lineno: 

111 # Current line. 

112 if relative: 

113 # Left align current number in relative mode. 

114 result.append((style_current, "%i" % (lineno + 1))) 

115 else: 

116 result.append( 

117 (style_current, ("%i " % (lineno + 1)).rjust(width)) 

118 ) 

119 else: 

120 # Other lines. 

121 if relative: 

122 lineno = abs(lineno - current_lineno) - 1 

123 

124 result.append((style, ("%i " % (lineno + 1)).rjust(width))) 

125 

126 last_lineno = lineno 

127 result.append(("", "\n")) 

128 

129 # Fill with tildes. 

130 if self.display_tildes(): 

131 while y < window_render_info.window_height: 

132 result.append(("class:tilde", "~\n")) 

133 y += 1 

134 

135 return result 

136 

137 

138class ConditionalMargin(Margin): 

139 """ 

140 Wrapper around other :class:`.Margin` classes to show/hide them. 

141 """ 

142 

143 def __init__(self, margin: Margin, filter: FilterOrBool) -> None: 

144 self.margin = margin 

145 self.filter = to_filter(filter) 

146 

147 def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: 

148 if self.filter(): 

149 return self.margin.get_width(get_ui_content) 

150 else: 

151 return 0 

152 

153 def create_margin( 

154 self, window_render_info: WindowRenderInfo, width: int, height: int 

155 ) -> StyleAndTextTuples: 

156 if width and self.filter(): 

157 return self.margin.create_margin(window_render_info, width, height) 

158 else: 

159 return [] 

160 

161 

162class ScrollbarMargin(Margin): 

163 """ 

164 Margin displaying a scrollbar. 

165 

166 :param display_arrows: Display scroll up/down arrows. 

167 """ 

168 

169 def __init__( 

170 self, 

171 display_arrows: FilterOrBool = False, 

172 up_arrow_symbol: str = "^", 

173 down_arrow_symbol: str = "v", 

174 ) -> None: 

175 self.display_arrows = to_filter(display_arrows) 

176 self.up_arrow_symbol = up_arrow_symbol 

177 self.down_arrow_symbol = down_arrow_symbol 

178 

179 def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: 

180 return 1 

181 

182 def create_margin( 

183 self, window_render_info: WindowRenderInfo, width: int, height: int 

184 ) -> StyleAndTextTuples: 

185 content_height = window_render_info.content_height 

186 window_height = window_render_info.window_height 

187 display_arrows = self.display_arrows() 

188 

189 if display_arrows: 

190 window_height -= 2 

191 

192 try: 

193 fraction_visible = len(window_render_info.displayed_lines) / float( 

194 content_height 

195 ) 

196 fraction_above = window_render_info.vertical_scroll / float(content_height) 

197 

198 scrollbar_height = int( 

199 min(window_height, max(1, window_height * fraction_visible)) 

200 ) 

201 scrollbar_top = int(window_height * fraction_above) 

202 except ZeroDivisionError: 

203 return [] 

204 else: 

205 

206 def is_scroll_button(row: int) -> bool: 

207 "True if we should display a button on this row." 

208 return scrollbar_top <= row <= scrollbar_top + scrollbar_height 

209 

210 # Up arrow. 

211 result: StyleAndTextTuples = [] 

212 if display_arrows: 

213 result.extend( 

214 [ 

215 ("class:scrollbar.arrow", self.up_arrow_symbol), 

216 ("class:scrollbar", "\n"), 

217 ] 

218 ) 

219 

220 # Scrollbar body. 

221 scrollbar_background = "class:scrollbar.background" 

222 scrollbar_background_start = "class:scrollbar.background,scrollbar.start" 

223 scrollbar_button = "class:scrollbar.button" 

224 scrollbar_button_end = "class:scrollbar.button,scrollbar.end" 

225 

226 for i in range(window_height): 

227 if is_scroll_button(i): 

228 if not is_scroll_button(i + 1): 

229 # Give the last cell a different style, because we 

230 # want to underline this. 

231 result.append((scrollbar_button_end, " ")) 

232 else: 

233 result.append((scrollbar_button, " ")) 

234 else: 

235 if is_scroll_button(i + 1): 

236 result.append((scrollbar_background_start, " ")) 

237 else: 

238 result.append((scrollbar_background, " ")) 

239 result.append(("", "\n")) 

240 

241 # Down arrow 

242 if display_arrows: 

243 result.append(("class:scrollbar.arrow", self.down_arrow_symbol)) 

244 

245 return result 

246 

247 

248class PromptMargin(Margin): 

249 """ 

250 [Deprecated] 

251 

252 Create margin that displays a prompt. 

253 This can display one prompt at the first line, and a continuation prompt 

254 (e.g, just dots) on all the following lines. 

255 

256 This `PromptMargin` implementation has been largely superseded in favor of 

257 the `get_line_prefix` attribute of `Window`. The reason is that a margin is 

258 always a fixed width, while `get_line_prefix` can return a variable width 

259 prefix in front of every line, making it more powerful, especially for line 

260 continuations. 

261 

262 :param get_prompt: Callable returns formatted text or a list of 

263 `(style_str, type)` tuples to be shown as the prompt at the first line. 

264 :param get_continuation: Callable that takes three inputs. The width (int), 

265 line_number (int), and is_soft_wrap (bool). It should return formatted 

266 text or a list of `(style_str, type)` tuples for the next lines of the 

267 input. 

268 """ 

269 

270 def __init__( 

271 self, 

272 get_prompt: Callable[[], StyleAndTextTuples], 

273 get_continuation: None 

274 | (Callable[[int, int, bool], StyleAndTextTuples]) = None, 

275 ) -> None: 

276 self.get_prompt = get_prompt 

277 self.get_continuation = get_continuation 

278 

279 def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: 

280 "Width to report to the `Window`." 

281 # Take the width from the first line. 

282 text = fragment_list_to_text(self.get_prompt()) 

283 return get_cwidth(text) 

284 

285 def create_margin( 

286 self, window_render_info: WindowRenderInfo, width: int, height: int 

287 ) -> StyleAndTextTuples: 

288 get_continuation = self.get_continuation 

289 result: StyleAndTextTuples = [] 

290 

291 # First line. 

292 result.extend(to_formatted_text(self.get_prompt())) 

293 

294 # Next lines. 

295 if get_continuation: 

296 last_y = None 

297 

298 for y in window_render_info.displayed_lines[1:]: 

299 result.append(("", "\n")) 

300 result.extend( 

301 to_formatted_text(get_continuation(width, y, y == last_y)) 

302 ) 

303 last_y = y 

304 

305 return result