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

94 statements  

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

1""" 

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

3dimensions for containers and controls. 

4""" 

5from __future__ import annotations 

6 

7from typing import TYPE_CHECKING, Any, Callable, Union 

8 

9__all__ = [ 

10 "Dimension", 

11 "D", 

12 "sum_layout_dimensions", 

13 "max_layout_dimensions", 

14 "AnyDimension", 

15 "to_dimension", 

16 "is_dimension", 

17] 

18 

19if TYPE_CHECKING: 

20 from typing_extensions import TypeGuard 

21 

22 

23class Dimension: 

24 """ 

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

26 

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

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

29 between min and max. 

30 

31 :param min: Minimum size. 

32 :param max: Maximum size. 

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

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

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

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

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

38 :param preferred: Preferred size. 

39 """ 

40 

41 def __init__( 

42 self, 

43 min: int | None = None, 

44 max: int | None = None, 

45 weight: int | None = None, 

46 preferred: int | None = None, 

47 ) -> None: 

48 if weight is not None: 

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

50 

51 assert min is None or min >= 0 

52 assert max is None or max >= 0 

53 assert preferred is None or preferred >= 0 

54 

55 self.min_specified = min is not None 

56 self.max_specified = max is not None 

57 self.preferred_specified = preferred is not None 

58 self.weight_specified = weight is not None 

59 

60 if min is None: 

61 min = 0 # Smallest possible value. 

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

63 max = 1000**10 # Something huge. 

64 if preferred is None: 

65 preferred = min 

66 if weight is None: 

67 weight = 1 

68 

69 self.min = min 

70 self.max = max 

71 self.preferred = preferred 

72 self.weight = weight 

73 

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

75 if max < min: 

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

77 

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

79 if self.preferred < self.min: 

80 self.preferred = self.min 

81 

82 if self.preferred > self.max: 

83 self.preferred = self.max 

84 

85 @classmethod 

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

87 """ 

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

89 preferred set to ``amount``). 

90 """ 

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

92 

93 @classmethod 

94 def zero(cls) -> Dimension: 

95 """ 

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

97 controls.) 

98 """ 

99 return cls.exact(amount=0) 

100 

101 def is_zero(self) -> bool: 

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

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

104 

105 def __repr__(self) -> str: 

106 fields = [] 

107 if self.min_specified: 

108 fields.append("min=%r" % self.min) 

109 if self.max_specified: 

110 fields.append("max=%r" % self.max) 

111 if self.preferred_specified: 

112 fields.append("preferred=%r" % self.preferred) 

113 if self.weight_specified: 

114 fields.append("weight=%r" % self.weight) 

115 

116 return "Dimension(%s)" % ", ".join(fields) 

117 

118 

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

120 """ 

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

122 """ 

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

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

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

126 

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

128 

129 

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

131 """ 

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

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

134 """ 

135 if not len(dimensions): 

136 return Dimension.zero() 

137 

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

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

140 # parent when all children are invisible.) 

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

142 return dimensions[0] 

143 

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

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

146 

147 if dimensions: 

148 # Take the highest minimum dimension. 

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

150 

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

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

153 # This seems to work best: 

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

155 # shrink other widgets in the split. 

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

157 # explicitly pass dimensions. 

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

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

160 

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

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

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

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

165 if min_ > max_: 

166 max_ = min_ 

167 

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

169 

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

171 else: 

172 return Dimension() 

173 

174 

175# Anything that can be converted to a dimension. 

176AnyDimension = Union[ 

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

178 int, 

179 Dimension, 

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

181 Callable[[], Any], 

182] 

183 

184 

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

186 """ 

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

188 """ 

189 if value is None: 

190 return Dimension() 

191 if isinstance(value, int): 

192 return Dimension.exact(value) 

193 if isinstance(value, Dimension): 

194 return value 

195 if callable(value): 

196 return to_dimension(value()) 

197 

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

199 

200 

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

202 """ 

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

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

205 """ 

206 if value is None: 

207 return True 

208 if callable(value): 

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

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

211 return True 

212 return False 

213 

214 

215# Common alias. 

216D = Dimension 

217 

218# For backward-compatibility. 

219LayoutDimension = Dimension