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

93 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 __repr__(self) -> str: 

103 fields = [] 

104 if self.min_specified: 

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

106 if self.max_specified: 

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

108 if self.preferred_specified: 

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

110 if self.weight_specified: 

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

112 

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

114 

115 

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

117 """ 

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

119 """ 

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

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

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

123 

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

125 

126 

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

128 """ 

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

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

131 """ 

132 if not len(dimensions): 

133 return Dimension.zero() 

134 

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

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

137 # parent when all children are invisible.) 

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

139 return Dimension.zero() 

140 

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

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

143 

144 if dimensions: 

145 # Take the highest minimum dimension. 

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

147 

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

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

150 # This seems to work best: 

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

152 # shrink other widgets in the split. 

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

154 # explicitly pass dimensions. 

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

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

157 

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

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

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

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

162 if min_ > max_: 

163 max_ = min_ 

164 

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

166 

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

168 else: 

169 return Dimension() 

170 

171 

172# Anything that can be converted to a dimension. 

173AnyDimension = Union[ 

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

175 int, 

176 Dimension, 

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

178 Callable[[], Any], 

179] 

180 

181 

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

183 """ 

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

185 """ 

186 if value is None: 

187 return Dimension() 

188 if isinstance(value, int): 

189 return Dimension.exact(value) 

190 if isinstance(value, Dimension): 

191 return value 

192 if callable(value): 

193 return to_dimension(value()) 

194 

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

196 

197 

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

199 """ 

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

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

202 """ 

203 if value is None: 

204 return True 

205 if callable(value): 

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

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

208 return True 

209 return False 

210 

211 

212# Common alias. 

213D = Dimension 

214 

215# For backward-compatibility. 

216LayoutDimension = Dimension