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

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

95 statements  

1""" 

2Layout dimensions are used to give the minimum, maximum and preferred 

3dimensions for containers and controls. 

4""" 

5 

6from __future__ import annotations 

7 

8from typing import TYPE_CHECKING, Any, Callable, Union 

9 

10__all__ = [ 

11 "Dimension", 

12 "D", 

13 "sum_layout_dimensions", 

14 "max_layout_dimensions", 

15 "AnyDimension", 

16 "to_dimension", 

17 "is_dimension", 

18] 

19 

20if TYPE_CHECKING: 

21 from typing_extensions import TypeGuard 

22 

23 

24class Dimension: 

25 """ 

26 Specified dimension (width/height) of a user control or window. 

27 

28 The layout engine tries to honor the preferred size. If that is not 

29 possible, because the terminal is larger or smaller, it tries to keep in 

30 between min and max. 

31 

32 :param min: Minimum size. 

33 :param max: Maximum size. 

34 :param weight: For a VSplit/HSplit, the actual size will be determined 

35 by taking the proportion of weights from all the children. 

36 E.g. When there are two children, one with a weight of 1, 

37 and the other with a weight of 2, the second will always be 

38 twice as big as the first, if the min/max values allow it. 

39 :param preferred: Preferred size. 

40 """ 

41 

42 def __init__( 

43 self, 

44 min: int | None = None, 

45 max: int | None = None, 

46 weight: int | None = None, 

47 preferred: int | None = None, 

48 ) -> None: 

49 if weight is not None: 

50 assert weight >= 0 # Also cannot be a float. 

51 

52 assert min is None or min >= 0 

53 assert max is None or max >= 0 

54 assert preferred is None or preferred >= 0 

55 

56 self.min_specified = min is not None 

57 self.max_specified = max is not None 

58 self.preferred_specified = preferred is not None 

59 self.weight_specified = weight is not None 

60 

61 if min is None: 

62 min = 0 # Smallest possible value. 

63 if max is None: # 0-values are allowed, so use "is None" 

64 max = 1000**10 # Something huge. 

65 if preferred is None: 

66 preferred = min 

67 if weight is None: 

68 weight = 1 

69 

70 self.min = min 

71 self.max = max 

72 self.preferred = preferred 

73 self.weight = weight 

74 

75 # Don't allow situations where max < min. (This would be a bug.) 

76 if max < min: 

77 raise ValueError("Invalid Dimension: max < min.") 

78 

79 # Make sure that the 'preferred' size is always in the min..max range. 

80 if self.preferred < self.min: 

81 self.preferred = self.min 

82 

83 if self.preferred > self.max: 

84 self.preferred = self.max 

85 

86 @classmethod 

87 def exact(cls, amount: int) -> Dimension: 

88 """ 

89 Return a :class:`.Dimension` with an exact size. (min, max and 

90 preferred set to ``amount``). 

91 """ 

92 return cls(min=amount, max=amount, preferred=amount) 

93 

94 @classmethod 

95 def zero(cls) -> Dimension: 

96 """ 

97 Create a dimension that represents a zero size. (Used for 'invisible' 

98 controls.) 

99 """ 

100 return cls.exact(amount=0) 

101 

102 def is_zero(self) -> bool: 

103 "True if this `Dimension` represents a zero size." 

104 return self.preferred == 0 or self.max == 0 

105 

106 def __repr__(self) -> str: 

107 fields = [] 

108 if self.min_specified: 

109 fields.append(f"min={self.min!r}") 

110 if self.max_specified: 

111 fields.append(f"max={self.max!r}") 

112 if self.preferred_specified: 

113 fields.append(f"preferred={self.preferred!r}") 

114 if self.weight_specified: 

115 fields.append(f"weight={self.weight!r}") 

116 

117 return "Dimension({})".format(", ".join(fields)) 

118 

119 

120def sum_layout_dimensions(dimensions: list[Dimension]) -> Dimension: 

121 """ 

122 Sum a list of :class:`.Dimension` instances. 

123 """ 

124 min = sum(d.min for d in dimensions) 

125 max = sum(d.max for d in dimensions) 

126 preferred = sum(d.preferred for d in dimensions) 

127 

128 return Dimension(min=min, max=max, preferred=preferred) 

129 

130 

131def max_layout_dimensions(dimensions: list[Dimension]) -> Dimension: 

132 """ 

133 Take the maximum of a list of :class:`.Dimension` instances. 

134 Used when we have a HSplit/VSplit, and we want to get the best width/height.) 

135 """ 

136 if not len(dimensions): 

137 return Dimension.zero() 

138 

139 # If all dimensions are size zero. Return zero. 

140 # (This is important for HSplit/VSplit, to report the right values to their 

141 # parent when all children are invisible.) 

142 if all(d.is_zero() for d in dimensions): 

143 return dimensions[0] 

144 

145 # Ignore empty dimensions. (They should not reduce the size of others.) 

146 dimensions = [d for d in dimensions if not d.is_zero()] 

147 

148 if dimensions: 

149 # Take the highest minimum dimension. 

150 min_ = max(d.min for d in dimensions) 

151 

152 # For the maximum, we would prefer not to go larger than then smallest 

153 # 'max' value, unless other dimensions have a bigger preferred value. 

154 # This seems to work best: 

155 # - We don't want that a widget with a small height in a VSplit would 

156 # shrink other widgets in the split. 

157 # If it doesn't work well enough, then it's up to the UI designer to 

158 # explicitly pass dimensions. 

159 max_ = min(d.max for d in dimensions) 

160 max_ = max(max_, max(d.preferred for d in dimensions)) 

161 

162 # Make sure that min>=max. In some scenarios, when certain min..max 

163 # ranges don't have any overlap, we can end up in such an impossible 

164 # situation. In that case, give priority to the max value. 

165 # E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8). 

166 if min_ > max_: 

167 max_ = min_ 

168 

169 preferred = max(d.preferred for d in dimensions) 

170 

171 return Dimension(min=min_, max=max_, preferred=preferred) 

172 else: 

173 return Dimension() 

174 

175 

176# Anything that can be converted to a dimension. 

177AnyDimension = Union[ 

178 None, # None is a valid dimension that will fit anything. 

179 int, 

180 Dimension, 

181 # Callable[[], 'AnyDimension'] # Recursive definition not supported by mypy. 

182 Callable[[], Any], 

183] 

184 

185 

186def to_dimension(value: AnyDimension) -> Dimension: 

187 """ 

188 Turn the given object into a `Dimension` object. 

189 """ 

190 if value is None: 

191 return Dimension() 

192 if isinstance(value, int): 

193 return Dimension.exact(value) 

194 if isinstance(value, Dimension): 

195 return value 

196 if callable(value): 

197 return to_dimension(value()) 

198 

199 raise ValueError("Not an integer or Dimension object.") 

200 

201 

202def is_dimension(value: object) -> TypeGuard[AnyDimension]: 

203 """ 

204 Test whether the given value could be a valid dimension. 

205 (For usage in an assertion. It's not guaranteed in case of a callable.) 

206 """ 

207 if value is None: 

208 return True 

209 if callable(value): 

210 return True # Assume it's a callable that doesn't take arguments. 

211 if isinstance(value, (int, Dimension)): 

212 return True 

213 return False 

214 

215 

216# Common alias. 

217D = Dimension 

218 

219# For backward-compatibility. 

220LayoutDimension = Dimension