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

121 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 typing import TYPE_CHECKING, Callable 

9 

10from prompt_toolkit.filters import FilterOrBool, to_filter 

11from prompt_toolkit.formatted_text import ( 

12 StyleAndTextTuples, 

13 fragment_list_to_text, 

14 to_formatted_text, 

15) 

16from prompt_toolkit.utils import get_cwidth 

17 

18from .controls import UIContent 

19 

20if TYPE_CHECKING: 

21 from .containers import WindowRenderInfo 

22 

23__all__ = [ 

24 "Margin", 

25 "NumberedMargin", 

26 "ScrollbarMargin", 

27 "ConditionalMargin", 

28 "PromptMargin", 

29] 

30 

31 

32class Margin(metaclass=ABCMeta): 

33 """ 

34 Base interface for a margin. 

35 """ 

36 

37 @abstractmethod 

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

39 """ 

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

41 

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

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

44 obtain the number of lines. 

45 """ 

46 return 0 

47 

48 @abstractmethod 

49 def create_margin( 

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

51 ) -> StyleAndTextTuples: 

52 """ 

53 Creates a margin. 

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

55 

56 :param window_render_info: 

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

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

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

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

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

62 by :meth:`.get_width`.) 

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

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

65 """ 

66 return [] 

67 

68 

69class NumberedMargin(Margin): 

70 """ 

71 Margin that displays the line numbers. 

72 

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

74 'relativenumber' option. 

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

76 like Vi does. 

77 """ 

78 

79 def __init__( 

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

81 ) -> None: 

82 self.relative = to_filter(relative) 

83 self.display_tildes = to_filter(display_tildes) 

84 

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

86 line_count = get_ui_content().line_count 

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

88 

89 def create_margin( 

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

91 ) -> StyleAndTextTuples: 

92 relative = self.relative() 

93 

94 style = "class:line-number" 

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

96 

97 # Get current line number. 

98 current_lineno = window_render_info.ui_content.cursor_position.y 

99 

100 # Construct margin. 

101 result: StyleAndTextTuples = [] 

102 last_lineno = None 

103 

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

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

106 if lineno != last_lineno: 

107 if lineno is None: 

108 pass 

109 elif lineno == current_lineno: 

110 # Current line. 

111 if relative: 

112 # Left align current number in relative mode. 

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

114 else: 

115 result.append( 

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

117 ) 

118 else: 

119 # Other lines. 

120 if relative: 

121 lineno = abs(lineno - current_lineno) - 1 

122 

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

124 

125 last_lineno = lineno 

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

127 

128 # Fill with tildes. 

129 if self.display_tildes(): 

130 while y < window_render_info.window_height: 

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

132 y += 1 

133 

134 return result 

135 

136 

137class ConditionalMargin(Margin): 

138 """ 

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

140 """ 

141 

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

143 self.margin = margin 

144 self.filter = to_filter(filter) 

145 

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

147 if self.filter(): 

148 return self.margin.get_width(get_ui_content) 

149 else: 

150 return 0 

151 

152 def create_margin( 

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

154 ) -> StyleAndTextTuples: 

155 if width and self.filter(): 

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

157 else: 

158 return [] 

159 

160 

161class ScrollbarMargin(Margin): 

162 """ 

163 Margin displaying a scrollbar. 

164 

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

166 """ 

167 

168 def __init__( 

169 self, 

170 display_arrows: FilterOrBool = False, 

171 up_arrow_symbol: str = "^", 

172 down_arrow_symbol: str = "v", 

173 ) -> None: 

174 self.display_arrows = to_filter(display_arrows) 

175 self.up_arrow_symbol = up_arrow_symbol 

176 self.down_arrow_symbol = down_arrow_symbol 

177 

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

179 return 1 

180 

181 def create_margin( 

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

183 ) -> StyleAndTextTuples: 

184 content_height = window_render_info.content_height 

185 window_height = window_render_info.window_height 

186 display_arrows = self.display_arrows() 

187 

188 if display_arrows: 

189 window_height -= 2 

190 

191 try: 

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

193 content_height 

194 ) 

195 fraction_above = window_render_info.vertical_scroll / float(content_height) 

196 

197 scrollbar_height = int( 

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

199 ) 

200 scrollbar_top = int(window_height * fraction_above) 

201 except ZeroDivisionError: 

202 return [] 

203 else: 

204 

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

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

207 return scrollbar_top <= row <= scrollbar_top + scrollbar_height 

208 

209 # Up arrow. 

210 result: StyleAndTextTuples = [] 

211 if display_arrows: 

212 result.extend( 

213 [ 

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

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

216 ] 

217 ) 

218 

219 # Scrollbar body. 

220 scrollbar_background = "class:scrollbar.background" 

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

222 scrollbar_button = "class:scrollbar.button" 

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

224 

225 for i in range(window_height): 

226 if is_scroll_button(i): 

227 if not is_scroll_button(i + 1): 

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

229 # want to underline this. 

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

231 else: 

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

233 else: 

234 if is_scroll_button(i + 1): 

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

236 else: 

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

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

239 

240 # Down arrow 

241 if display_arrows: 

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

243 

244 return result 

245 

246 

247class PromptMargin(Margin): 

248 """ 

249 [Deprecated] 

250 

251 Create margin that displays a prompt. 

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

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

254 

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

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

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

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

259 continuations. 

260 

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

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

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

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

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

266 input. 

267 """ 

268 

269 def __init__( 

270 self, 

271 get_prompt: Callable[[], StyleAndTextTuples], 

272 get_continuation: None 

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

274 ) -> None: 

275 self.get_prompt = get_prompt 

276 self.get_continuation = get_continuation 

277 

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

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

280 # Take the width from the first line. 

281 text = fragment_list_to_text(self.get_prompt()) 

282 return get_cwidth(text) 

283 

284 def create_margin( 

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

286 ) -> StyleAndTextTuples: 

287 get_continuation = self.get_continuation 

288 result: StyleAndTextTuples = [] 

289 

290 # First line. 

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

292 

293 # Next lines. 

294 if get_continuation: 

295 last_y = None 

296 

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

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

299 result.extend( 

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

301 ) 

302 last_y = y 

303 

304 return result