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

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

94 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 collections.abc import Callable 

9from typing import TYPE_CHECKING 

10 

11__all__ = [ 

12 "Dimension", 

13 "D", 

14 "sum_layout_dimensions", 

15 "max_layout_dimensions", 

16 "AnyDimension", 

17 "to_dimension", 

18 "is_dimension", 

19] 

20 

21if TYPE_CHECKING: 

22 from typing import TypeGuard 

23 

24 

25class Dimension: 

26 """ 

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

28 

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

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

31 between min and max. 

32 

33 :param min: Minimum size. 

34 :param max: Maximum size. 

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

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

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

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

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

40 :param preferred: Preferred size. 

41 """ 

42 

43 def __init__( 

44 self, 

45 min: int | None = None, 

46 max: int | None = None, 

47 weight: int | None = None, 

48 preferred: int | None = None, 

49 ) -> None: 

50 if weight is not None: 

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

52 

53 assert min is None or min >= 0 

54 assert max is None or max >= 0 

55 assert preferred is None or preferred >= 0 

56 

57 self.min_specified = min is not None 

58 self.max_specified = max is not None 

59 self.preferred_specified = preferred is not None 

60 self.weight_specified = weight is not None 

61 

62 if min is None: 

63 min = 0 # Smallest possible value. 

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

65 max = 1000**10 # Something huge. 

66 if preferred is None: 

67 preferred = min 

68 if weight is None: 

69 weight = 1 

70 

71 self.min = min 

72 self.max = max 

73 self.preferred = preferred 

74 self.weight = weight 

75 

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

77 if max < min: 

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

79 

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

81 if self.preferred < self.min: 

82 self.preferred = self.min 

83 

84 if self.preferred > self.max: 

85 self.preferred = self.max 

86 

87 @classmethod 

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

89 """ 

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

91 preferred set to ``amount``). 

92 """ 

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

94 

95 @classmethod 

96 def zero(cls) -> Dimension: 

97 """ 

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

99 controls.) 

100 """ 

101 return cls.exact(amount=0) 

102 

103 def __repr__(self) -> str: 

104 fields = [] 

105 if self.min_specified: 

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

107 if self.max_specified: 

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

109 if self.preferred_specified: 

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

111 if self.weight_specified: 

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

113 

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

115 

116 

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

118 """ 

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

120 """ 

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

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

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

124 

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

126 

127 

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

129 """ 

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

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

132 """ 

133 if not len(dimensions): 

134 return Dimension.zero() 

135 

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

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

138 # parent when all children are invisible.) 

139 if all(d.preferred == 0 and d.max == 0 for d in dimensions): 

140 return Dimension.zero() 

141 

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

143 dimensions = [d for d in dimensions if d.preferred != 0 and d.max != 0] 

144 

145 if dimensions: 

146 # Take the highest minimum dimension. 

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

148 

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

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

151 # This seems to work best: 

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

153 # shrink other widgets in the split. 

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

155 # explicitly pass dimensions. 

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

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

158 

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

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

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

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

163 if min_ > max_: 

164 max_ = min_ 

165 

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

167 

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

169 else: 

170 return Dimension() 

171 

172 

173# Anything that can be converted to a dimension 

174AnyDimension = None | int | Dimension | Callable[[], "AnyDimension"] 

175 

176 

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

178 """ 

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

180 """ 

181 if value is None: 

182 return Dimension() 

183 if isinstance(value, int): 

184 return Dimension.exact(value) 

185 if isinstance(value, Dimension): 

186 return value 

187 if callable(value): 

188 return to_dimension(value()) 

189 

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

191 

192 

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

194 """ 

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

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

197 """ 

198 if value is None: 

199 return True 

200 if callable(value): 

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

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

203 return True 

204 return False 

205 

206 

207# Common alias. 

208D = Dimension 

209 

210# For backward-compatibility. 

211LayoutDimension = Dimension