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

227 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:05 +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 Awaitable, 

44 Callable, 

45 Hashable, 

46 Sequence, 

47 Tuple, 

48 TypeVar, 

49 Union, 

50 cast, 

51) 

52 

53from prompt_toolkit.cache import SimpleCache 

54from prompt_toolkit.filters import FilterOrBool, Never, to_filter 

55from prompt_toolkit.keys import KEY_ALIASES, Keys 

56 

57if TYPE_CHECKING: 

58 # Avoid circular imports. 

59 from .key_processor import KeyPressEvent 

60 

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

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

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

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

65 NotImplementedOrNone = object 

66 # Other non-working options are: 

67 # * Optional[Literal[NotImplemented]] 

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

69 # * None 

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

71 # returns `None` to a variable. 

72 # * Any 

73 # --> Works, but too broad. 

74 

75 

76__all__ = [ 

77 "NotImplementedOrNone", 

78 "Binding", 

79 "KeyBindingsBase", 

80 "KeyBindings", 

81 "ConditionalKeyBindings", 

82 "merge_key_bindings", 

83 "DynamicKeyBindings", 

84 "GlobalOnlyKeyBindings", 

85] 

86 

87# Key bindings can be regular functions or coroutines. 

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

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

90# repainting during mouse move events. 

91KeyHandlerCallable = Callable[ 

92 ["KeyPressEvent"], Union["NotImplementedOrNone", Awaitable["NotImplementedOrNone"]] 

93] 

94 

95 

96class Binding: 

97 """ 

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

99 (Immutable binding class.) 

100 

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

102 macro is recorded. 

103 """ 

104 

105 def __init__( 

106 self, 

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

108 handler: KeyHandlerCallable, 

109 filter: FilterOrBool = True, 

110 eager: FilterOrBool = False, 

111 is_global: FilterOrBool = False, 

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

113 record_in_macro: FilterOrBool = True, 

114 ) -> None: 

115 self.keys = keys 

116 self.handler = handler 

117 self.filter = to_filter(filter) 

118 self.eager = to_filter(eager) 

119 self.is_global = to_filter(is_global) 

120 self.save_before = save_before 

121 self.record_in_macro = to_filter(record_in_macro) 

122 

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

124 result = self.handler(event) 

125 

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

127 if isawaitable(result): 

128 awaitable = cast(Awaitable["NotImplementedOrNone"], result) 

129 

130 async def bg_task() -> None: 

131 result = await awaitable 

132 if result != NotImplemented: 

133 event.app.invalidate() 

134 

135 event.app.create_background_task(bg_task()) 

136 

137 elif result != NotImplemented: 

138 event.app.invalidate() 

139 

140 def __repr__(self) -> str: 

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

142 self.__class__.__name__, 

143 self.keys, 

144 self.handler, 

145 ) 

146 

147 

148# Sequence of keys presses. 

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

150 

151 

152class KeyBindingsBase(metaclass=ABCMeta): 

153 """ 

154 Interface for a KeyBindings. 

155 """ 

156 

157 @abstractproperty 

158 def _version(self) -> Hashable: 

159 """ 

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

161 something changes. 

162 """ 

163 return 0 

164 

165 @abstractmethod 

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

167 """ 

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

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

170 called, for checking it.) 

171 

172 :param keys: tuple of keys. 

173 """ 

174 return [] 

175 

176 @abstractmethod 

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

178 """ 

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

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

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

182 inactive bindings.) 

183 

184 :param keys: tuple of keys. 

185 """ 

186 return [] 

187 

188 @abstractproperty 

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

190 """ 

191 List of `Binding` objects. 

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

193 together.) 

194 """ 

195 return [] 

196 

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

198 

199 

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

201 

202 

203class KeyBindings(KeyBindingsBase): 

204 """ 

205 A container for a set of key bindings. 

206 

207 Example usage:: 

208 

209 kb = KeyBindings() 

210 

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

212 def _(event): 

213 print('Control-T pressed') 

214 

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

216 def _(event): 

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

218 

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

220 def _(event): 

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

222 

223 """ 

224 

225 def __init__(self) -> None: 

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

227 self._get_bindings_for_keys_cache: SimpleCache[ 

228 KeysTuple, list[Binding] 

229 ] = SimpleCache(maxsize=10000) 

230 self._get_bindings_starting_with_keys_cache: SimpleCache[ 

231 KeysTuple, list[Binding] 

232 ] = SimpleCache(maxsize=1000) 

233 self.__version = 0 # For cache invalidation. 

234 

235 def _clear_cache(self) -> None: 

236 self.__version += 1 

237 self._get_bindings_for_keys_cache.clear() 

238 self._get_bindings_starting_with_keys_cache.clear() 

239 

240 @property 

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

242 return self._bindings 

243 

244 @property 

245 def _version(self) -> Hashable: 

246 return self.__version 

247 

248 def add( 

249 self, 

250 *keys: Keys | str, 

251 filter: FilterOrBool = True, 

252 eager: FilterOrBool = False, 

253 is_global: FilterOrBool = False, 

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

255 record_in_macro: FilterOrBool = True, 

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

257 """ 

258 Decorator for adding a key bindings. 

259 

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

261 when this key binding is active. 

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

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

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

265 execute the handler immediately and ignore the key binding for 

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

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

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

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

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

271 (That's the default.) 

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

273 being recorded. (True by default.) 

274 """ 

275 assert keys 

276 

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

278 

279 if isinstance(filter, Never): 

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

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

282 # down every key press otherwise. 

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

284 return func 

285 

286 else: 

287 

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

289 if isinstance(func, Binding): 

290 # We're adding an existing Binding object. 

291 self.bindings.append( 

292 Binding( 

293 keys, 

294 func.handler, 

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

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

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

298 save_before=func.save_before, 

299 record_in_macro=func.record_in_macro, 

300 ) 

301 ) 

302 else: 

303 self.bindings.append( 

304 Binding( 

305 keys, 

306 cast(KeyHandlerCallable, func), 

307 filter=filter, 

308 eager=eager, 

309 is_global=is_global, 

310 save_before=save_before, 

311 record_in_macro=record_in_macro, 

312 ) 

313 ) 

314 self._clear_cache() 

315 

316 return func 

317 

318 return decorator 

319 

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

321 """ 

322 Remove a key binding. 

323 

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

325 parameter or a sequence of key bindings. 

326 

327 Raises `ValueError` when no bindings was found. 

328 

329 Usage:: 

330 

331 remove(handler) # Pass handler. 

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

333 """ 

334 found = False 

335 

336 if callable(args[0]): 

337 assert len(args) == 1 

338 function = args[0] 

339 

340 # Remove the given function. 

341 for b in self.bindings: 

342 if b.handler == function: 

343 self.bindings.remove(b) 

344 found = True 

345 

346 else: 

347 assert len(args) > 0 

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

349 

350 # Remove this sequence of key bindings. 

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

352 

353 for b in self.bindings: 

354 if b.keys == keys: 

355 self.bindings.remove(b) 

356 found = True 

357 

358 if found: 

359 self._clear_cache() 

360 else: 

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

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

363 

364 # For backwards-compatibility. 

365 add_binding = add 

366 remove_binding = remove 

367 

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

369 """ 

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

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

372 called, for checking it.) 

373 

374 :param keys: tuple of keys. 

375 """ 

376 

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

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

379 

380 for b in self.bindings: 

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

382 match = True 

383 any_count = 0 

384 

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

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

387 match = False 

388 break 

389 

390 if i == Keys.Any: 

391 any_count += 1 

392 

393 if match: 

394 result.append((any_count, b)) 

395 

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

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

398 

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

400 

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

402 

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

404 """ 

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

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

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

408 inactive bindings.) 

409 

410 :param keys: tuple of keys. 

411 """ 

412 

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

414 result = [] 

415 for b in self.bindings: 

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

417 match = True 

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

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

420 match = False 

421 break 

422 if match: 

423 result.append(b) 

424 return result 

425 

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

427 

428 

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

430 """ 

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

432 """ 

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

434 if isinstance(key, Keys): 

435 return key 

436 

437 # Lookup aliases. 

438 key = KEY_ALIASES.get(key, key) 

439 

440 # Replace 'space' by ' ' 

441 if key == "space": 

442 key = " " 

443 

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

445 try: 

446 return Keys(key) 

447 except ValueError: 

448 pass 

449 

450 # Final validation. 

451 if len(key) != 1: 

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

453 

454 return key 

455 

456 

457def key_binding( 

458 filter: FilterOrBool = True, 

459 eager: FilterOrBool = False, 

460 is_global: FilterOrBool = False, 

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

462 record_in_macro: FilterOrBool = True, 

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

464 """ 

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

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

467 """ 

468 assert save_before is None or callable(save_before) 

469 

470 filter = to_filter(filter) 

471 eager = to_filter(eager) 

472 is_global = to_filter(is_global) 

473 save_before = save_before 

474 record_in_macro = to_filter(record_in_macro) 

475 keys = () 

476 

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

478 return Binding( 

479 keys, 

480 function, 

481 filter=filter, 

482 eager=eager, 

483 is_global=is_global, 

484 save_before=save_before, 

485 record_in_macro=record_in_macro, 

486 ) 

487 

488 return decorator 

489 

490 

491class _Proxy(KeyBindingsBase): 

492 """ 

493 Common part for ConditionalKeyBindings and _MergedKeyBindings. 

494 """ 

495 

496 def __init__(self) -> None: 

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

498 self._bindings2: KeyBindingsBase = KeyBindings() 

499 self._last_version: Hashable = () 

500 

501 def _update_cache(self) -> None: 

502 """ 

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

504 the version and `self._bindings2`. 

505 """ 

506 raise NotImplementedError 

507 

508 # Proxy methods to self._bindings2. 

509 

510 @property 

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

512 self._update_cache() 

513 return self._bindings2.bindings 

514 

515 @property 

516 def _version(self) -> Hashable: 

517 self._update_cache() 

518 return self._last_version 

519 

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

521 self._update_cache() 

522 return self._bindings2.get_bindings_for_keys(keys) 

523 

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

525 self._update_cache() 

526 return self._bindings2.get_bindings_starting_with_keys(keys) 

527 

528 

529class ConditionalKeyBindings(_Proxy): 

530 """ 

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

532 the given (additional) filter.:: 

533 

534 @Condition 

535 def setting_is_true(): 

536 return True # or False 

537 

538 registry = ConditionalKeyBindings(key_bindings, setting_is_true) 

539 

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

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

542 

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

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

545 """ 

546 

547 def __init__( 

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

549 ) -> None: 

550 _Proxy.__init__(self) 

551 

552 self.key_bindings = key_bindings 

553 self.filter = to_filter(filter) 

554 

555 def _update_cache(self) -> None: 

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

557 expected_version = self.key_bindings._version 

558 

559 if self._last_version != expected_version: 

560 bindings2 = KeyBindings() 

561 

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

563 for b in self.key_bindings.bindings: 

564 bindings2.bindings.append( 

565 Binding( 

566 keys=b.keys, 

567 handler=b.handler, 

568 filter=self.filter & b.filter, 

569 eager=b.eager, 

570 is_global=b.is_global, 

571 save_before=b.save_before, 

572 record_in_macro=b.record_in_macro, 

573 ) 

574 ) 

575 

576 self._bindings2 = bindings2 

577 self._last_version = expected_version 

578 

579 

580class _MergedKeyBindings(_Proxy): 

581 """ 

582 Merge multiple registries of key bindings into one. 

583 

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

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

586 

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

588 """ 

589 

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

591 _Proxy.__init__(self) 

592 self.registries = registries 

593 

594 def _update_cache(self) -> None: 

595 """ 

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

597 version. 

598 """ 

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

600 

601 if self._last_version != expected_version: 

602 bindings2 = KeyBindings() 

603 

604 for reg in self.registries: 

605 bindings2.bindings.extend(reg.bindings) 

606 

607 self._bindings2 = bindings2 

608 self._last_version = expected_version 

609 

610 

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

612 """ 

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

614 

615 Usage:: 

616 

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

618 """ 

619 return _MergedKeyBindings(bindings) 

620 

621 

622class DynamicKeyBindings(_Proxy): 

623 """ 

624 KeyBindings class that can dynamically returns any KeyBindings. 

625 

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

627 """ 

628 

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

630 self.get_key_bindings = get_key_bindings 

631 self.__version = 0 

632 self._last_child_version = None 

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

634 

635 def _update_cache(self) -> None: 

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

637 assert isinstance(key_bindings, KeyBindingsBase) 

638 version = id(key_bindings), key_bindings._version 

639 

640 self._bindings2 = key_bindings 

641 self._last_version = version 

642 

643 

644class GlobalOnlyKeyBindings(_Proxy): 

645 """ 

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

647 key bindings. 

648 """ 

649 

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

651 _Proxy.__init__(self) 

652 self.key_bindings = key_bindings 

653 

654 def _update_cache(self) -> None: 

655 """ 

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

657 version. 

658 """ 

659 expected_version = self.key_bindings._version 

660 

661 if self._last_version != expected_version: 

662 bindings2 = KeyBindings() 

663 

664 for b in self.key_bindings.bindings: 

665 if b.is_global(): 

666 bindings2.bindings.append(b) 

667 

668 self._bindings2 = bindings2 

669 self._last_version = expected_version