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
« 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
7from typing import TYPE_CHECKING, Any, Callable, Union
9__all__ = [
10 "Dimension",
11 "D",
12 "sum_layout_dimensions",
13 "max_layout_dimensions",
14 "AnyDimension",
15 "to_dimension",
16 "is_dimension",
17]
19if TYPE_CHECKING:
20 from typing_extensions import TypeGuard
23class Dimension:
24 """
25 Specified dimension (width/height) of a user control or window.
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.
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 """
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.
51 assert min is None or min >= 0
52 assert max is None or max >= 0
53 assert preferred is None or preferred >= 0
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
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
69 self.min = min
70 self.max = max
71 self.preferred = preferred
72 self.weight = weight
74 # Don't allow situations where max < min. (This would be a bug.)
75 if max < min:
76 raise ValueError("Invalid Dimension: max < min.")
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
82 if self.preferred > self.max:
83 self.preferred = self.max
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)
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)
101 def is_zero(self) -> bool:
102 "True if this `Dimension` represents a zero size."
103 return self.preferred == 0 or self.max == 0
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)
116 return "Dimension(%s)" % ", ".join(fields)
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)
127 return Dimension(min=min, max=max, preferred=preferred)
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()
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]
144 # Ignore empty dimensions. (They should not reduce the size of others.)
145 dimensions = [d for d in dimensions if not d.is_zero()]
147 if dimensions:
148 # Take the highest minimum dimension.
149 min_ = max(d.min for d in dimensions)
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))
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_
168 preferred = max(d.preferred for d in dimensions)
170 return Dimension(min=min_, max=max_, preferred=preferred)
171 else:
172 return Dimension()
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]
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())
198 raise ValueError("Not an integer or Dimension object.")
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
215# Common alias.
216D = Dimension
218# For backward-compatibility.
219LayoutDimension = Dimension