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

120 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1""" 

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

3""" 

4from __future__ import annotations 

5 

6from abc import ABCMeta, abstractmethod 

7from typing import TYPE_CHECKING, Callable 

8 

9from prompt_toolkit.filters import FilterOrBool, to_filter 

10from prompt_toolkit.formatted_text import ( 

11 StyleAndTextTuples, 

12 fragment_list_to_text, 

13 to_formatted_text, 

14) 

15from prompt_toolkit.utils import get_cwidth 

16 

17from .controls import UIContent 

18 

19if TYPE_CHECKING: 

20 from .containers import WindowRenderInfo 

21 

22__all__ = [ 

23 "Margin", 

24 "NumberedMargin", 

25 "ScrollbarMargin", 

26 "ConditionalMargin", 

27 "PromptMargin", 

28] 

29 

30 

31class Margin(metaclass=ABCMeta): 

32 """ 

33 Base interface for a margin. 

34 """ 

35 

36 @abstractmethod 

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

38 """ 

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

40 

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

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

43 obtain the number of lines. 

44 """ 

45 return 0 

46 

47 @abstractmethod 

48 def create_margin( 

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

50 ) -> StyleAndTextTuples: 

51 """ 

52 Creates a margin. 

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

54 

55 :param window_render_info: 

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

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

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

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

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

61 by :meth:`.get_width`.) 

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

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

64 """ 

65 return [] 

66 

67 

68class NumberedMargin(Margin): 

69 """ 

70 Margin that displays the line numbers. 

71 

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

73 'relativenumber' option. 

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

75 like Vi does. 

76 """ 

77 

78 def __init__( 

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

80 ) -> None: 

81 self.relative = to_filter(relative) 

82 self.display_tildes = to_filter(display_tildes) 

83 

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

85 line_count = get_ui_content().line_count 

86 return max(3, len("%s" % line_count) + 1) 

87 

88 def create_margin( 

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

90 ) -> StyleAndTextTuples: 

91 relative = self.relative() 

92 

93 style = "class:line-number" 

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

95 

96 # Get current line number. 

97 current_lineno = window_render_info.ui_content.cursor_position.y 

98 

99 # Construct margin. 

100 result: StyleAndTextTuples = [] 

101 last_lineno = None 

102 

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

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

105 if lineno != last_lineno: 

106 if lineno is None: 

107 pass 

108 elif lineno == current_lineno: 

109 # Current line. 

110 if relative: 

111 # Left align current number in relative mode. 

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

113 else: 

114 result.append( 

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

116 ) 

117 else: 

118 # Other lines. 

119 if relative: 

120 lineno = abs(lineno - current_lineno) - 1 

121 

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

123 

124 last_lineno = lineno 

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

126 

127 # Fill with tildes. 

128 if self.display_tildes(): 

129 while y < window_render_info.window_height: 

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

131 y += 1 

132 

133 return result 

134 

135 

136class ConditionalMargin(Margin): 

137 """ 

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

139 """ 

140 

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

142 self.margin = margin 

143 self.filter = to_filter(filter) 

144 

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

146 if self.filter(): 

147 return self.margin.get_width(get_ui_content) 

148 else: 

149 return 0 

150 

151 def create_margin( 

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

153 ) -> StyleAndTextTuples: 

154 if width and self.filter(): 

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

156 else: 

157 return [] 

158 

159 

160class ScrollbarMargin(Margin): 

161 """ 

162 Margin displaying a scrollbar. 

163 

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

165 """ 

166 

167 def __init__( 

168 self, 

169 display_arrows: FilterOrBool = False, 

170 up_arrow_symbol: str = "^", 

171 down_arrow_symbol: str = "v", 

172 ) -> None: 

173 self.display_arrows = to_filter(display_arrows) 

174 self.up_arrow_symbol = up_arrow_symbol 

175 self.down_arrow_symbol = down_arrow_symbol 

176 

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

178 return 1 

179 

180 def create_margin( 

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

182 ) -> StyleAndTextTuples: 

183 content_height = window_render_info.content_height 

184 window_height = window_render_info.window_height 

185 display_arrows = self.display_arrows() 

186 

187 if display_arrows: 

188 window_height -= 2 

189 

190 try: 

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

192 content_height 

193 ) 

194 fraction_above = window_render_info.vertical_scroll / float(content_height) 

195 

196 scrollbar_height = int( 

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

198 ) 

199 scrollbar_top = int(window_height * fraction_above) 

200 except ZeroDivisionError: 

201 return [] 

202 else: 

203 

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

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

206 return scrollbar_top <= row <= scrollbar_top + scrollbar_height 

207 

208 # Up arrow. 

209 result: StyleAndTextTuples = [] 

210 if display_arrows: 

211 result.extend( 

212 [ 

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

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

215 ] 

216 ) 

217 

218 # Scrollbar body. 

219 scrollbar_background = "class:scrollbar.background" 

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

221 scrollbar_button = "class:scrollbar.button" 

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

223 

224 for i in range(window_height): 

225 if is_scroll_button(i): 

226 if not is_scroll_button(i + 1): 

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

228 # want to underline this. 

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

230 else: 

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

232 else: 

233 if is_scroll_button(i + 1): 

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

235 else: 

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

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

238 

239 # Down arrow 

240 if display_arrows: 

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

242 

243 return result 

244 

245 

246class PromptMargin(Margin): 

247 """ 

248 [Deprecated] 

249 

250 Create margin that displays a prompt. 

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

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

253 

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

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

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

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

258 continuations. 

259 

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

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

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

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

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

265 input. 

266 """ 

267 

268 def __init__( 

269 self, 

270 get_prompt: Callable[[], StyleAndTextTuples], 

271 get_continuation: None 

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

273 ) -> None: 

274 self.get_prompt = get_prompt 

275 self.get_continuation = get_continuation 

276 

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

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

279 # Take the width from the first line. 

280 text = fragment_list_to_text(self.get_prompt()) 

281 return get_cwidth(text) 

282 

283 def create_margin( 

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

285 ) -> StyleAndTextTuples: 

286 get_continuation = self.get_continuation 

287 result: StyleAndTextTuples = [] 

288 

289 # First line. 

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

291 

292 # Next lines. 

293 if get_continuation: 

294 last_y = None 

295 

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

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

298 result.extend( 

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

300 ) 

301 last_y = y 

302 

303 return result