Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/key_binding/key_bindings.py: 35%

227 statements  

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

1""" 

2Key bindings registry. 

3 

4A `KeyBindings` object is a container that holds a list of key bindings. It has a 

5very efficient internal data structure for checking which key bindings apply 

6for a pressed key. 

7 

8Typical usage:: 

9 

10 kb = KeyBindings() 

11 

12 @kb.add(Keys.ControlX, Keys.ControlC, filter=INSERT) 

13 def handler(event): 

14 # Handle ControlX-ControlC key sequence. 

15 pass 

16 

17It is also possible to combine multiple KeyBindings objects. We do this in the 

18default key bindings. There are some KeyBindings objects that contain the Emacs 

19bindings, while others contain the Vi bindings. They are merged together using 

20`merge_key_bindings`. 

21 

22We also have a `ConditionalKeyBindings` object that can enable/disable a group of 

23key bindings at once. 

24 

25 

26It is also possible to add a filter to a function, before a key binding has 

27been assigned, through the `key_binding` decorator.:: 

28 

29 # First define a key handler with the `filter`. 

30 @key_binding(filter=condition) 

31 def my_key_binding(event): 

32 ... 

33 

34 # Later, add it to the key bindings. 

35 kb.add(Keys.A, my_key_binding) 

36""" 

37from __future__ import annotations 

38 

39from abc import ABCMeta, abstractmethod, abstractproperty 

40from inspect import isawaitable 

41from typing import ( 

42 TYPE_CHECKING, 

43 Any, 

44 Callable, 

45 Coroutine, 

46 Hashable, 

47 Sequence, 

48 Tuple, 

49 TypeVar, 

50 Union, 

51 cast, 

52) 

53 

54from prompt_toolkit.cache import SimpleCache 

55from prompt_toolkit.filters import FilterOrBool, Never, to_filter 

56from prompt_toolkit.keys import KEY_ALIASES, Keys 

57 

58if TYPE_CHECKING: 

59 # Avoid circular imports. 

60 from .key_processor import KeyPressEvent 

61 

62 # The only two return values for a mouse handler (and key bindings) are 

63 # `None` and `NotImplemented`. For the type checker it's best to annotate 

64 # this as `object`. (The consumer never expects a more specific instance: 

65 # checking for NotImplemented can be done using `is NotImplemented`.) 

66 NotImplementedOrNone = object 

67 # Other non-working options are: 

68 # * Optional[Literal[NotImplemented]] 

69 # --> Doesn't work, Literal can't take an Any. 

70 # * None 

71 # --> Doesn't work. We can't assign the result of a function that 

72 # returns `None` to a variable. 

73 # * Any 

74 # --> Works, but too broad. 

75 

76 

77__all__ = [ 

78 "NotImplementedOrNone", 

79 "Binding", 

80 "KeyBindingsBase", 

81 "KeyBindings", 

82 "ConditionalKeyBindings", 

83 "merge_key_bindings", 

84 "DynamicKeyBindings", 

85 "GlobalOnlyKeyBindings", 

86] 

87 

88# Key bindings can be regular functions or coroutines. 

89# In both cases, if they return `NotImplemented`, the UI won't be invalidated. 

90# This is mainly used in case of mouse move events, to prevent excessive 

91# repainting during mouse move events. 

92KeyHandlerCallable = Callable[ 

93 ["KeyPressEvent"], 

94 Union["NotImplementedOrNone", Coroutine[Any, Any, "NotImplementedOrNone"]], 

95] 

96 

97 

98class Binding: 

99 """ 

100 Key binding: (key sequence + handler + filter). 

101 (Immutable binding class.) 

102 

103 :param record_in_macro: When True, don't record this key binding when a 

104 macro is recorded. 

105 """ 

106 

107 def __init__( 

108 self, 

109 keys: tuple[Keys | str, ...], 

110 handler: KeyHandlerCallable, 

111 filter: FilterOrBool = True, 

112 eager: FilterOrBool = False, 

113 is_global: FilterOrBool = False, 

114 save_before: Callable[[KeyPressEvent], bool] = (lambda e: True), 

115 record_in_macro: FilterOrBool = True, 

116 ) -> None: 

117 self.keys = keys 

118 self.handler = handler 

119 self.filter = to_filter(filter) 

120 self.eager = to_filter(eager) 

121 self.is_global = to_filter(is_global) 

122 self.save_before = save_before 

123 self.record_in_macro = to_filter(record_in_macro) 

124 

125 def call(self, event: KeyPressEvent) -> None: 

126 result = self.handler(event) 

127 

128 # If the handler is a coroutine, create an asyncio task. 

129 if isawaitable(result): 

130 awaitable = cast(Coroutine[Any, Any, "NotImplementedOrNone"], result) 

131 

132 async def bg_task() -> None: 

133 result = await awaitable 

134 if result != NotImplemented: 

135 event.app.invalidate() 

136 

137 event.app.create_background_task(bg_task()) 

138 

139 elif result != NotImplemented: 

140 event.app.invalidate() 

141 

142 def __repr__(self) -> str: 

143 return "{}(keys={!r}, handler={!r})".format( 

144 self.__class__.__name__, 

145 self.keys, 

146 self.handler, 

147 ) 

148 

149 

150# Sequence of keys presses. 

151KeysTuple = Tuple[Union[Keys, str], ...] 

152 

153 

154class KeyBindingsBase(metaclass=ABCMeta): 

155 """ 

156 Interface for a KeyBindings. 

157 """ 

158 

159 @abstractproperty 

160 def _version(self) -> Hashable: 

161 """ 

162 For cache invalidation. - This should increase every time that 

163 something changes. 

164 """ 

165 return 0 

166 

167 @abstractmethod 

168 def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]: 

169 """ 

170 Return a list of key bindings that can handle these keys. 

171 (This return also inactive bindings, so the `filter` still has to be 

172 called, for checking it.) 

173 

174 :param keys: tuple of keys. 

175 """ 

176 return [] 

177 

178 @abstractmethod 

179 def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]: 

180 """ 

181 Return a list of key bindings that handle a key sequence starting with 

182 `keys`. (It does only return bindings for which the sequences are 

183 longer than `keys`. And like `get_bindings_for_keys`, it also includes 

184 inactive bindings.) 

185 

186 :param keys: tuple of keys. 

187 """ 

188 return [] 

189 

190 @abstractproperty 

191 def bindings(self) -> list[Binding]: 

192 """ 

193 List of `Binding` objects. 

194 (These need to be exposed, so that `KeyBindings` objects can be merged 

195 together.) 

196 """ 

197 return [] 

198 

199 # `add` and `remove` don't have to be part of this interface. 

200 

201 

202T = TypeVar("T", bound=Union[KeyHandlerCallable, Binding]) 

203 

204 

205class KeyBindings(KeyBindingsBase): 

206 """ 

207 A container for a set of key bindings. 

208 

209 Example usage:: 

210 

211 kb = KeyBindings() 

212 

213 @kb.add('c-t') 

214 def _(event): 

215 print('Control-T pressed') 

216 

217 @kb.add('c-a', 'c-b') 

218 def _(event): 

219 print('Control-A pressed, followed by Control-B') 

220 

221 @kb.add('c-x', filter=is_searching) 

222 def _(event): 

223 print('Control-X pressed') # Works only if we are searching. 

224 

225 """ 

226 

227 def __init__(self) -> None: 

228 self._bindings: list[Binding] = [] 

229 self._get_bindings_for_keys_cache: SimpleCache[ 

230 KeysTuple, list[Binding] 

231 ] = SimpleCache(maxsize=10000) 

232 self._get_bindings_starting_with_keys_cache: SimpleCache[ 

233 KeysTuple, list[Binding] 

234 ] = SimpleCache(maxsize=1000) 

235 self.__version = 0 # For cache invalidation. 

236 

237 def _clear_cache(self) -> None: 

238 self.__version += 1 

239 self._get_bindings_for_keys_cache.clear() 

240 self._get_bindings_starting_with_keys_cache.clear() 

241 

242 @property 

243 def bindings(self) -> list[Binding]: 

244 return self._bindings 

245 

246 @property 

247 def _version(self) -> Hashable: 

248 return self.__version 

249 

250 def add( 

251 self, 

252 *keys: Keys | str, 

253 filter: FilterOrBool = True, 

254 eager: FilterOrBool = False, 

255 is_global: FilterOrBool = False, 

256 save_before: Callable[[KeyPressEvent], bool] = (lambda e: True), 

257 record_in_macro: FilterOrBool = True, 

258 ) -> Callable[[T], T]: 

259 """ 

260 Decorator for adding a key bindings. 

261 

262 :param filter: :class:`~prompt_toolkit.filters.Filter` to determine 

263 when this key binding is active. 

264 :param eager: :class:`~prompt_toolkit.filters.Filter` or `bool`. 

265 When True, ignore potential longer matches when this key binding is 

266 hit. E.g. when there is an active eager key binding for Ctrl-X, 

267 execute the handler immediately and ignore the key binding for 

268 Ctrl-X Ctrl-E of which it is a prefix. 

269 :param is_global: When this key bindings is added to a `Container` or 

270 `Control`, make it a global (always active) binding. 

271 :param save_before: Callable that takes an `Event` and returns True if 

272 we should save the current buffer, before handling the event. 

273 (That's the default.) 

274 :param record_in_macro: Record these key bindings when a macro is 

275 being recorded. (True by default.) 

276 """ 

277 assert keys 

278 

279 keys = tuple(_parse_key(k) for k in keys) 

280 

281 if isinstance(filter, Never): 

282 # When a filter is Never, it will always stay disabled, so in that 

283 # case don't bother putting it in the key bindings. It will slow 

284 # down every key press otherwise. 

285 def decorator(func: T) -> T: 

286 return func 

287 

288 else: 

289 

290 def decorator(func: T) -> T: 

291 if isinstance(func, Binding): 

292 # We're adding an existing Binding object. 

293 self.bindings.append( 

294 Binding( 

295 keys, 

296 func.handler, 

297 filter=func.filter & to_filter(filter), 

298 eager=to_filter(eager) | func.eager, 

299 is_global=to_filter(is_global) | func.is_global, 

300 save_before=func.save_before, 

301 record_in_macro=func.record_in_macro, 

302 ) 

303 ) 

304 else: 

305 self.bindings.append( 

306 Binding( 

307 keys, 

308 cast(KeyHandlerCallable, func), 

309 filter=filter, 

310 eager=eager, 

311 is_global=is_global, 

312 save_before=save_before, 

313 record_in_macro=record_in_macro, 

314 ) 

315 ) 

316 self._clear_cache() 

317 

318 return func 

319 

320 return decorator 

321 

322 def remove(self, *args: Keys | str | KeyHandlerCallable) -> None: 

323 """ 

324 Remove a key binding. 

325 

326 This expects either a function that was given to `add` method as 

327 parameter or a sequence of key bindings. 

328 

329 Raises `ValueError` when no bindings was found. 

330 

331 Usage:: 

332 

333 remove(handler) # Pass handler. 

334 remove('c-x', 'c-a') # Or pass the key bindings. 

335 """ 

336 found = False 

337 

338 if callable(args[0]): 

339 assert len(args) == 1 

340 function = args[0] 

341 

342 # Remove the given function. 

343 for b in self.bindings: 

344 if b.handler == function: 

345 self.bindings.remove(b) 

346 found = True 

347 

348 else: 

349 assert len(args) > 0 

350 args = cast(Tuple[Union[Keys, str]], args) 

351 

352 # Remove this sequence of key bindings. 

353 keys = tuple(_parse_key(k) for k in args) 

354 

355 for b in self.bindings: 

356 if b.keys == keys: 

357 self.bindings.remove(b) 

358 found = True 

359 

360 if found: 

361 self._clear_cache() 

362 else: 

363 # No key binding found for this function. Raise ValueError. 

364 raise ValueError(f"Binding not found: {function!r}") 

365 

366 # For backwards-compatibility. 

367 add_binding = add 

368 remove_binding = remove 

369 

370 def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]: 

371 """ 

372 Return a list of key bindings that can handle this key. 

373 (This return also inactive bindings, so the `filter` still has to be 

374 called, for checking it.) 

375 

376 :param keys: tuple of keys. 

377 """ 

378 

379 def get() -> list[Binding]: 

380 result: list[tuple[int, Binding]] = [] 

381 

382 for b in self.bindings: 

383 if len(keys) == len(b.keys): 

384 match = True 

385 any_count = 0 

386 

387 for i, j in zip(b.keys, keys): 

388 if i != j and i != Keys.Any: 

389 match = False 

390 break 

391 

392 if i == Keys.Any: 

393 any_count += 1 

394 

395 if match: 

396 result.append((any_count, b)) 

397 

398 # Place bindings that have more 'Any' occurrences in them at the end. 

399 result = sorted(result, key=lambda item: -item[0]) 

400 

401 return [item[1] for item in result] 

402 

403 return self._get_bindings_for_keys_cache.get(keys, get) 

404 

405 def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]: 

406 """ 

407 Return a list of key bindings that handle a key sequence starting with 

408 `keys`. (It does only return bindings for which the sequences are 

409 longer than `keys`. And like `get_bindings_for_keys`, it also includes 

410 inactive bindings.) 

411 

412 :param keys: tuple of keys. 

413 """ 

414 

415 def get() -> list[Binding]: 

416 result = [] 

417 for b in self.bindings: 

418 if len(keys) < len(b.keys): 

419 match = True 

420 for i, j in zip(b.keys, keys): 

421 if i != j and i != Keys.Any: 

422 match = False 

423 break 

424 if match: 

425 result.append(b) 

426 return result 

427 

428 return self._get_bindings_starting_with_keys_cache.get(keys, get) 

429 

430 

431def _parse_key(key: Keys | str) -> str | Keys: 

432 """ 

433 Replace key by alias and verify whether it's a valid one. 

434 """ 

435 # Already a parse key? -> Return it. 

436 if isinstance(key, Keys): 

437 return key 

438 

439 # Lookup aliases. 

440 key = KEY_ALIASES.get(key, key) 

441 

442 # Replace 'space' by ' ' 

443 if key == "space": 

444 key = " " 

445 

446 # Return as `Key` object when it's a special key. 

447 try: 

448 return Keys(key) 

449 except ValueError: 

450 pass 

451 

452 # Final validation. 

453 if len(key) != 1: 

454 raise ValueError(f"Invalid key: {key}") 

455 

456 return key 

457 

458 

459def key_binding( 

460 filter: FilterOrBool = True, 

461 eager: FilterOrBool = False, 

462 is_global: FilterOrBool = False, 

463 save_before: Callable[[KeyPressEvent], bool] = (lambda event: True), 

464 record_in_macro: FilterOrBool = True, 

465) -> Callable[[KeyHandlerCallable], Binding]: 

466 """ 

467 Decorator that turn a function into a `Binding` object. This can be added 

468 to a `KeyBindings` object when a key binding is assigned. 

469 """ 

470 assert save_before is None or callable(save_before) 

471 

472 filter = to_filter(filter) 

473 eager = to_filter(eager) 

474 is_global = to_filter(is_global) 

475 save_before = save_before 

476 record_in_macro = to_filter(record_in_macro) 

477 keys = () 

478 

479 def decorator(function: KeyHandlerCallable) -> Binding: 

480 return Binding( 

481 keys, 

482 function, 

483 filter=filter, 

484 eager=eager, 

485 is_global=is_global, 

486 save_before=save_before, 

487 record_in_macro=record_in_macro, 

488 ) 

489 

490 return decorator 

491 

492 

493class _Proxy(KeyBindingsBase): 

494 """ 

495 Common part for ConditionalKeyBindings and _MergedKeyBindings. 

496 """ 

497 

498 def __init__(self) -> None: 

499 # `KeyBindings` to be synchronized with all the others. 

500 self._bindings2: KeyBindingsBase = KeyBindings() 

501 self._last_version: Hashable = () 

502 

503 def _update_cache(self) -> None: 

504 """ 

505 If `self._last_version` is outdated, then this should update 

506 the version and `self._bindings2`. 

507 """ 

508 raise NotImplementedError 

509 

510 # Proxy methods to self._bindings2. 

511 

512 @property 

513 def bindings(self) -> list[Binding]: 

514 self._update_cache() 

515 return self._bindings2.bindings 

516 

517 @property 

518 def _version(self) -> Hashable: 

519 self._update_cache() 

520 return self._last_version 

521 

522 def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]: 

523 self._update_cache() 

524 return self._bindings2.get_bindings_for_keys(keys) 

525 

526 def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]: 

527 self._update_cache() 

528 return self._bindings2.get_bindings_starting_with_keys(keys) 

529 

530 

531class ConditionalKeyBindings(_Proxy): 

532 """ 

533 Wraps around a `KeyBindings`. Disable/enable all the key bindings according to 

534 the given (additional) filter.:: 

535 

536 @Condition 

537 def setting_is_true(): 

538 return True # or False 

539 

540 registry = ConditionalKeyBindings(key_bindings, setting_is_true) 

541 

542 When new key bindings are added to this object. They are also 

543 enable/disabled according to the given `filter`. 

544 

545 :param registries: List of :class:`.KeyBindings` objects. 

546 :param filter: :class:`~prompt_toolkit.filters.Filter` object. 

547 """ 

548 

549 def __init__( 

550 self, key_bindings: KeyBindingsBase, filter: FilterOrBool = True 

551 ) -> None: 

552 _Proxy.__init__(self) 

553 

554 self.key_bindings = key_bindings 

555 self.filter = to_filter(filter) 

556 

557 def _update_cache(self) -> None: 

558 "If the original key bindings was changed. Update our copy version." 

559 expected_version = self.key_bindings._version 

560 

561 if self._last_version != expected_version: 

562 bindings2 = KeyBindings() 

563 

564 # Copy all bindings from `self.key_bindings`, adding our condition. 

565 for b in self.key_bindings.bindings: 

566 bindings2.bindings.append( 

567 Binding( 

568 keys=b.keys, 

569 handler=b.handler, 

570 filter=self.filter & b.filter, 

571 eager=b.eager, 

572 is_global=b.is_global, 

573 save_before=b.save_before, 

574 record_in_macro=b.record_in_macro, 

575 ) 

576 ) 

577 

578 self._bindings2 = bindings2 

579 self._last_version = expected_version 

580 

581 

582class _MergedKeyBindings(_Proxy): 

583 """ 

584 Merge multiple registries of key bindings into one. 

585 

586 This class acts as a proxy to multiple :class:`.KeyBindings` objects, but 

587 behaves as if this is just one bigger :class:`.KeyBindings`. 

588 

589 :param registries: List of :class:`.KeyBindings` objects. 

590 """ 

591 

592 def __init__(self, registries: Sequence[KeyBindingsBase]) -> None: 

593 _Proxy.__init__(self) 

594 self.registries = registries 

595 

596 def _update_cache(self) -> None: 

597 """ 

598 If one of the original registries was changed. Update our merged 

599 version. 

600 """ 

601 expected_version = tuple(r._version for r in self.registries) 

602 

603 if self._last_version != expected_version: 

604 bindings2 = KeyBindings() 

605 

606 for reg in self.registries: 

607 bindings2.bindings.extend(reg.bindings) 

608 

609 self._bindings2 = bindings2 

610 self._last_version = expected_version 

611 

612 

613def merge_key_bindings(bindings: Sequence[KeyBindingsBase]) -> _MergedKeyBindings: 

614 """ 

615 Merge multiple :class:`.Keybinding` objects together. 

616 

617 Usage:: 

618 

619 bindings = merge_key_bindings([bindings1, bindings2, ...]) 

620 """ 

621 return _MergedKeyBindings(bindings) 

622 

623 

624class DynamicKeyBindings(_Proxy): 

625 """ 

626 KeyBindings class that can dynamically returns any KeyBindings. 

627 

628 :param get_key_bindings: Callable that returns a :class:`.KeyBindings` instance. 

629 """ 

630 

631 def __init__(self, get_key_bindings: Callable[[], KeyBindingsBase | None]) -> None: 

632 self.get_key_bindings = get_key_bindings 

633 self.__version = 0 

634 self._last_child_version = None 

635 self._dummy = KeyBindings() # Empty key bindings. 

636 

637 def _update_cache(self) -> None: 

638 key_bindings = self.get_key_bindings() or self._dummy 

639 assert isinstance(key_bindings, KeyBindingsBase) 

640 version = id(key_bindings), key_bindings._version 

641 

642 self._bindings2 = key_bindings 

643 self._last_version = version 

644 

645 

646class GlobalOnlyKeyBindings(_Proxy): 

647 """ 

648 Wrapper around a :class:`.KeyBindings` object that only exposes the global 

649 key bindings. 

650 """ 

651 

652 def __init__(self, key_bindings: KeyBindingsBase) -> None: 

653 _Proxy.__init__(self) 

654 self.key_bindings = key_bindings 

655 

656 def _update_cache(self) -> None: 

657 """ 

658 If one of the original registries was changed. Update our merged 

659 version. 

660 """ 

661 expected_version = self.key_bindings._version 

662 

663 if self._last_version != expected_version: 

664 bindings2 = KeyBindings() 

665 

666 for b in self.key_bindings.bindings: 

667 if b.is_global(): 

668 bindings2.bindings.append(b) 

669 

670 self._bindings2 = bindings2 

671 self._last_version = expected_version