xulbux 1.7.2__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xulbux might be problematic. Click here for more details.
- xulbux/__init__.py +17 -19
- xulbux/{xx_code.py → code.py} +3 -3
- xulbux/{xx_color.py → color.py} +1 -1
- xulbux/{xx_console.py → console.py} +352 -200
- xulbux/{xx_data.py → data.py} +14 -14
- xulbux/{xx_env_path.py → env_path.py} +1 -1
- xulbux/{xx_file.py → file.py} +1 -1
- xulbux/{xx_format_codes.py → format_codes.py} +24 -22
- xulbux/{xx_json.py → json.py} +3 -3
- xulbux/{xx_system.py → system.py} +11 -11
- xulbux-1.8.0.dist-info/METADATA +190 -0
- xulbux-1.8.0.dist-info/RECORD +18 -0
- xulbux-1.8.0.dist-info/entry_points.txt +2 -0
- xulbux/_cli_.py +0 -46
- xulbux/_consts_.py +0 -180
- xulbux-1.7.2.dist-info/METADATA +0 -170
- xulbux-1.7.2.dist-info/RECORD +0 -21
- xulbux-1.7.2.dist-info/entry_points.txt +0 -3
- xulbux-1.7.2.dist-info/licenses/LICENSE +0 -21
- /xulbux/{xx_path.py → path.py} +0 -0
- /xulbux/{xx_regex.py → regex.py} +0 -0
- /xulbux/{xx_string.py → string.py} +0 -0
- {xulbux-1.7.2.dist-info → xulbux-1.8.0.dist-info}/WHEEL +0 -0
- {xulbux-1.7.2.dist-info → xulbux-1.8.0.dist-info}/top_level.txt +0 -0
|
@@ -2,22 +2,23 @@
|
|
|
2
2
|
Functions for logging and other small actions within the console.\n
|
|
3
3
|
----------------------------------------------------------------------------------------------------------
|
|
4
4
|
You can also use special formatting codes directly inside the log message to change their appearance.
|
|
5
|
-
For more detailed information about formatting codes, see the the `
|
|
5
|
+
For more detailed information about formatting codes, see the the `format_codes` module documentation.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
11
|
-
from .
|
|
12
|
-
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
from .base.consts import COLOR, CHARS
|
|
9
|
+
from .format_codes import FormatCodes, _COMPILED
|
|
10
|
+
from .string import String
|
|
11
|
+
from .color import Color, Rgba, Hexa
|
|
12
|
+
|
|
13
|
+
from typing import Callable, Optional, Literal, Mapping, Any, cast
|
|
14
|
+
from prompt_toolkit.key_binding import KeyPressEvent, KeyBindings
|
|
15
|
+
from prompt_toolkit.validation import ValidationError, Validator
|
|
16
|
+
from prompt_toolkit.styles import Style
|
|
17
|
+
from prompt_toolkit.keys import Keys
|
|
18
|
+
import prompt_toolkit as _pt
|
|
17
19
|
import keyboard as _keyboard
|
|
18
20
|
import getpass as _getpass
|
|
19
21
|
import shutil as _shutil
|
|
20
|
-
import mouse as _mouse
|
|
21
22
|
import sys as _sys
|
|
22
23
|
import os as _os
|
|
23
24
|
|
|
@@ -63,7 +64,7 @@ class ArgResult:
|
|
|
63
64
|
--------------------------------------------------------------------------------------------------------
|
|
64
65
|
When the `ArgResult` instance is accessed as a boolean it will correspond to the `exists` attribute."""
|
|
65
66
|
|
|
66
|
-
def __init__(self, exists: bool, value: Any):
|
|
67
|
+
def __init__(self, exists: bool, value: Any | list[Any]):
|
|
67
68
|
self.exists: bool = exists
|
|
68
69
|
self.value: Any = value
|
|
69
70
|
|
|
@@ -76,12 +77,11 @@ class Args:
|
|
|
76
77
|
For example, if an argument `foo` was parsed, it can be accessed via `args.foo`.
|
|
77
78
|
Each such attribute (e.g. `args.foo`) is an instance of `ArgResult`."""
|
|
78
79
|
|
|
79
|
-
def __init__(self, **kwargs: dict[str, Any]):
|
|
80
|
+
def __init__(self, **kwargs: dict[str, Any | list[Any]]):
|
|
80
81
|
for alias_name, data_dict in kwargs.items():
|
|
81
82
|
if not alias_name.isidentifier():
|
|
82
83
|
raise TypeError(f"Argument alias '{alias_name}' is invalid. It must be a valid Python variable name.")
|
|
83
|
-
|
|
84
|
-
setattr(self, alias_name, arg_result_instance)
|
|
84
|
+
setattr(self, alias_name, ArgResult(exists=cast(bool, data_dict["exists"]), value=data_dict["value"]))
|
|
85
85
|
|
|
86
86
|
def __len__(self):
|
|
87
87
|
return len(vars(self))
|
|
@@ -132,7 +132,8 @@ class Console:
|
|
|
132
132
|
|
|
133
133
|
@staticmethod
|
|
134
134
|
def get_args(
|
|
135
|
-
find_args: Mapping[str, list[str] | tuple[str, ...] | dict[str, list[str] | tuple[str, ...] | Any]
|
|
135
|
+
find_args: Mapping[str, list[str] | tuple[str, ...] | dict[str, list[str] | tuple[str, ...] | Any]
|
|
136
|
+
| Literal["before", "after"]],
|
|
136
137
|
allow_spaces: bool = False
|
|
137
138
|
) -> Args:
|
|
138
139
|
"""Will search for the specified arguments in the command line
|
|
@@ -150,24 +151,31 @@ class Console:
|
|
|
150
151
|
"default": "some_value" # Optional
|
|
151
152
|
}
|
|
152
153
|
```
|
|
154
|
+
3. Positional argument collection (string value):
|
|
155
|
+
```python
|
|
156
|
+
"alias_name": "before" # Collects non-flagged args before first flag
|
|
157
|
+
"alias_name": "after" # Collects non-flagged args after last flag
|
|
158
|
+
```
|
|
153
159
|
Example `find_args`:
|
|
154
160
|
```python
|
|
155
161
|
find_args={
|
|
156
|
-
"
|
|
162
|
+
"text": "before", # Positional args before flags
|
|
163
|
+
"arg1": { # With default
|
|
157
164
|
"flags": ["-a1", "--arg1"],
|
|
158
165
|
"default": "default_val"
|
|
159
166
|
},
|
|
160
|
-
"arg2": ("-a2", "--arg2"),
|
|
161
|
-
"arg3": ["-a3"],
|
|
162
|
-
"arg4": {
|
|
167
|
+
"arg2": ("-a2", "--arg2"), # Without default (original format)
|
|
168
|
+
"arg3": ["-a3"], # Without default (list format)
|
|
169
|
+
"arg4": { # Flag with default True
|
|
163
170
|
"flags": ["-f"],
|
|
164
171
|
"default": True
|
|
165
172
|
}
|
|
166
173
|
}
|
|
167
174
|
```
|
|
168
175
|
If the script is called via the command line:\n
|
|
169
|
-
`python script.py -a1 "value1" --arg2 -f`\n
|
|
176
|
+
`python script.py Hello World -a1 "value1" --arg2 -f`\n
|
|
170
177
|
...it would return an `Args` object where:
|
|
178
|
+
- `args.text.exists` is `True`, `args.text.value` is `["Hello", "World"]`
|
|
171
179
|
- `args.arg1.exists` is `True`, `args.arg1.value` is `"value1"`
|
|
172
180
|
- `args.arg2.exists` is `True`, `args.arg2.value` is `True` (flag present without value)
|
|
173
181
|
- `args.arg3.exists` is `False`, `args.arg3.value` is `None` (not present, no default)
|
|
@@ -176,19 +184,46 @@ class Console:
|
|
|
176
184
|
- `exists` will be `False`
|
|
177
185
|
- `value` will be the specified `default` value, or `None` if no default was specified.\n
|
|
178
186
|
----------------------------------------------------------------
|
|
187
|
+
For positional arguments:
|
|
188
|
+
- `"before"`: Collects all non-flagged arguments that appear before the first flag
|
|
189
|
+
- `"after"`: Collects all non-flagged arguments that appear after the last flag's value
|
|
190
|
+
----------------------------------------------------------------
|
|
179
191
|
Normally if `allow_spaces` is false, it will take a space as
|
|
180
192
|
the end of an args value. If it is true, it will take spaces as
|
|
181
|
-
part of the value until the next arg is found.
|
|
193
|
+
part of the value up until the next arg-flag is found.
|
|
182
194
|
(Multiple spaces will become one space in the value.)"""
|
|
183
195
|
args = _sys.argv[1:]
|
|
184
196
|
args_len = len(args)
|
|
185
197
|
arg_lookup = {}
|
|
186
198
|
results = {}
|
|
199
|
+
positional_configs = {}
|
|
200
|
+
before_count = 0
|
|
201
|
+
after_count = 0
|
|
202
|
+
|
|
203
|
+
# PARSE "find_args" CONFIGURATION
|
|
187
204
|
for alias, config in find_args.items():
|
|
188
205
|
flags = None
|
|
189
206
|
default_value = None
|
|
190
|
-
|
|
207
|
+
|
|
208
|
+
if isinstance(config, str):
|
|
209
|
+
# HANDLE POSITIONAL ARGUMENT COLLECTION
|
|
210
|
+
if config not in ("before", "after"):
|
|
211
|
+
raise ValueError(
|
|
212
|
+
f"Invalid positional argument type '{config}' for alias '{alias}'. Must be 'before' or 'after'."
|
|
213
|
+
)
|
|
214
|
+
if config == "before":
|
|
215
|
+
before_count += 1
|
|
216
|
+
if before_count > 1:
|
|
217
|
+
raise ValueError("Only one alias can have the value 'before' for positional argument collection.")
|
|
218
|
+
elif config == "after":
|
|
219
|
+
after_count += 1
|
|
220
|
+
if after_count > 1:
|
|
221
|
+
raise ValueError("Only one alias can have the value 'after' for positional argument collection.")
|
|
222
|
+
positional_configs[alias] = config
|
|
223
|
+
results[alias] = {"exists": False, "value": []}
|
|
224
|
+
elif isinstance(config, (list, tuple)):
|
|
191
225
|
flags = config
|
|
226
|
+
results[alias] = {"exists": False, "value": default_value}
|
|
192
227
|
elif isinstance(config, dict):
|
|
193
228
|
if "flags" not in config:
|
|
194
229
|
raise ValueError(f"Invalid configuration for alias '{alias}'. Dictionary must contain a 'flags' key.")
|
|
@@ -196,15 +231,54 @@ class Console:
|
|
|
196
231
|
default_value = config.get("default")
|
|
197
232
|
if not isinstance(flags, (list, tuple)):
|
|
198
233
|
raise ValueError(f"Invalid 'flags' for alias '{alias}'. Must be a list or tuple.")
|
|
234
|
+
results[alias] = {"exists": False, "value": default_value}
|
|
199
235
|
else:
|
|
200
|
-
raise TypeError(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
236
|
+
raise TypeError(
|
|
237
|
+
f"Invalid configuration type for alias '{alias}'. Must be a list, tuple, dict or literal 'before' / 'after'."
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# BUILD FLAG LOOKUP FOR NON-POSITIONAL ARGUMENTS
|
|
241
|
+
if flags is not None:
|
|
242
|
+
for flag in flags:
|
|
243
|
+
if flag in arg_lookup:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"Duplicate flag '{flag}' found. It's assigned to both '{arg_lookup[flag]}' and '{alias}'."
|
|
246
|
+
)
|
|
247
|
+
arg_lookup[flag] = alias
|
|
248
|
+
|
|
249
|
+
# FIND POSITIONS OF FIRST AND LAST FLAGS FOR POSITIONAL ARGUMENT COLLECTION
|
|
250
|
+
first_flag_pos = None
|
|
251
|
+
last_flag_with_value_pos = None
|
|
252
|
+
|
|
253
|
+
for i, arg in enumerate(args):
|
|
254
|
+
if arg in arg_lookup:
|
|
255
|
+
if first_flag_pos is None:
|
|
256
|
+
first_flag_pos = i
|
|
257
|
+
# CHECK IF THIS FLAG HAS A VALUE FOLLOWING IT
|
|
258
|
+
flag_has_value = (i + 1 < args_len and not args[i + 1].startswith("-") and args[i + 1] not in arg_lookup)
|
|
259
|
+
if flag_has_value:
|
|
260
|
+
if not allow_spaces:
|
|
261
|
+
last_flag_with_value_pos = i + 1
|
|
262
|
+
else:
|
|
263
|
+
# FIND THE END OF THE MULTI-WORD VALUE
|
|
264
|
+
j = i + 1
|
|
265
|
+
while j < args_len and not args[j].startswith("-") and args[j] not in arg_lookup:
|
|
266
|
+
j += 1
|
|
267
|
+
last_flag_with_value_pos = j - 1
|
|
268
|
+
|
|
269
|
+
# COLLECT "before" POSITIONAL ARGUMENTS
|
|
270
|
+
for alias, pos_type in positional_configs.items():
|
|
271
|
+
if pos_type == "before":
|
|
272
|
+
before_args = []
|
|
273
|
+
end_pos = first_flag_pos if first_flag_pos is not None else args_len
|
|
274
|
+
for i in range(end_pos):
|
|
275
|
+
if not args[i].startswith("-"):
|
|
276
|
+
before_args.append(String.to_type(args[i]))
|
|
277
|
+
if before_args:
|
|
278
|
+
results[alias]["value"] = before_args
|
|
279
|
+
results[alias]["exists"] = len(before_args) > 0
|
|
280
|
+
|
|
281
|
+
# PROCESS FLAGGED ARGUMENTS
|
|
208
282
|
i = 0
|
|
209
283
|
while i < args_len:
|
|
210
284
|
arg = args[i]
|
|
@@ -230,19 +304,43 @@ class Console:
|
|
|
230
304
|
if not value_found_after_flag:
|
|
231
305
|
results[alias]["value"] = True
|
|
232
306
|
i += 1
|
|
307
|
+
|
|
308
|
+
# COLLECT "after" POSITIONAL ARGUMENTS
|
|
309
|
+
for alias, pos_type in positional_configs.items():
|
|
310
|
+
if pos_type == "after":
|
|
311
|
+
after_args = []
|
|
312
|
+
start_pos = (last_flag_with_value_pos + 1) if last_flag_with_value_pos is not None else 0
|
|
313
|
+
# IF NO FLAGS WERE FOUND WITH VALUES, START AFTER THE LAST FLAG
|
|
314
|
+
if last_flag_with_value_pos is None and first_flag_pos is not None:
|
|
315
|
+
# FIND THE LAST FLAG POSITION
|
|
316
|
+
last_flag_pos = None
|
|
317
|
+
for i, arg in enumerate(args):
|
|
318
|
+
if arg in arg_lookup:
|
|
319
|
+
last_flag_pos = i
|
|
320
|
+
if last_flag_pos is not None:
|
|
321
|
+
start_pos = last_flag_pos + 1
|
|
322
|
+
|
|
323
|
+
for i in range(start_pos, args_len):
|
|
324
|
+
if not args[i].startswith("-") and args[i] not in arg_lookup:
|
|
325
|
+
after_args.append(String.to_type(args[i]))
|
|
326
|
+
|
|
327
|
+
if after_args:
|
|
328
|
+
results[alias]["value"] = after_args
|
|
329
|
+
results[alias]["exists"] = len(after_args) > 0
|
|
330
|
+
|
|
233
331
|
return Args(**results)
|
|
234
332
|
|
|
235
333
|
@staticmethod
|
|
236
334
|
def pause_exit(
|
|
237
|
-
pause: bool =
|
|
335
|
+
pause: bool = True,
|
|
238
336
|
exit: bool = False,
|
|
239
337
|
prompt: object = "",
|
|
240
338
|
exit_code: int = 0,
|
|
241
339
|
reset_ansi: bool = False,
|
|
242
340
|
) -> None:
|
|
243
|
-
"""Will print the `prompt` and then pause the program if `pause` is
|
|
244
|
-
|
|
245
|
-
print(prompt, end="", flush=True)
|
|
341
|
+
"""Will print the `prompt` and then pause the program if `pause` is
|
|
342
|
+
true and after the pause, exit the program if `exit` is set true."""
|
|
343
|
+
FormatCodes.print(prompt, end="", flush=True)
|
|
246
344
|
if reset_ansi:
|
|
247
345
|
FormatCodes.print("[_]", end="")
|
|
248
346
|
if pause:
|
|
@@ -281,7 +379,7 @@ class Console:
|
|
|
281
379
|
- `_console_tabsize` -⠀the tab size of the console (default is 8)\n
|
|
282
380
|
-----------------------------------------------------------------------------------
|
|
283
381
|
The log message can be formatted with special formatting codes. For more detailed
|
|
284
|
-
information about formatting codes, see `
|
|
382
|
+
information about formatting codes, see `format_codes` module documentation."""
|
|
285
383
|
title = "" if title is None else title.strip().upper()
|
|
286
384
|
title_len, tab_len = len(title) + 4, _console_tabsize - ((len(title) + 4) % _console_tabsize)
|
|
287
385
|
if title_bg_color is not None and Color.is_valid(title_bg_color):
|
|
@@ -342,7 +440,7 @@ class Console:
|
|
|
342
440
|
i = find_string_part(pos)
|
|
343
441
|
adjusted_pos = (pos - cumulative_pos[i]) + offset_adjusts[i]
|
|
344
442
|
parts = [result[i][:adjusted_pos], removal, result[i][adjusted_pos:]]
|
|
345
|
-
result[i] =
|
|
443
|
+
result[i] = "".join(parts)
|
|
346
444
|
offset_adjusts[i] += len(removal)
|
|
347
445
|
return result
|
|
348
446
|
|
|
@@ -353,8 +451,7 @@ class Console:
|
|
|
353
451
|
format_linebreaks: bool = True,
|
|
354
452
|
start: str = "",
|
|
355
453
|
end: str = "\n",
|
|
356
|
-
|
|
357
|
-
default_color: Optional[Rgba | Hexa] = COLOR.text,
|
|
454
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
358
455
|
pause: bool = False,
|
|
359
456
|
exit: bool = False,
|
|
360
457
|
) -> None:
|
|
@@ -362,7 +459,7 @@ class Console:
|
|
|
362
459
|
at the message and exit the program after the message was printed.
|
|
363
460
|
If `active` is false, no debug message will be printed."""
|
|
364
461
|
if active:
|
|
365
|
-
Console.log("DEBUG", prompt, format_linebreaks, start, end,
|
|
462
|
+
Console.log("DEBUG", prompt, format_linebreaks, start, end, COLOR.YELLOW, default_color)
|
|
366
463
|
Console.pause_exit(pause, exit)
|
|
367
464
|
|
|
368
465
|
@staticmethod
|
|
@@ -371,14 +468,13 @@ class Console:
|
|
|
371
468
|
format_linebreaks: bool = True,
|
|
372
469
|
start: str = "",
|
|
373
470
|
end: str = "\n",
|
|
374
|
-
|
|
375
|
-
default_color: Optional[Rgba | Hexa] = COLOR.text,
|
|
471
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
376
472
|
pause: bool = False,
|
|
377
473
|
exit: bool = False,
|
|
378
474
|
) -> None:
|
|
379
475
|
"""A preset for `log()`: `INFO` log message with the options to pause
|
|
380
476
|
at the message and exit the program after the message was printed."""
|
|
381
|
-
Console.log("INFO", prompt, format_linebreaks, start, end,
|
|
477
|
+
Console.log("INFO", prompt, format_linebreaks, start, end, COLOR.BLUE, default_color)
|
|
382
478
|
Console.pause_exit(pause, exit)
|
|
383
479
|
|
|
384
480
|
@staticmethod
|
|
@@ -387,14 +483,13 @@ class Console:
|
|
|
387
483
|
format_linebreaks: bool = True,
|
|
388
484
|
start: str = "",
|
|
389
485
|
end: str = "\n",
|
|
390
|
-
|
|
391
|
-
default_color: Optional[Rgba | Hexa] = COLOR.text,
|
|
486
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
392
487
|
pause: bool = False,
|
|
393
488
|
exit: bool = False,
|
|
394
489
|
) -> None:
|
|
395
490
|
"""A preset for `log()`: `DONE` log message with the options to pause
|
|
396
491
|
at the message and exit the program after the message was printed."""
|
|
397
|
-
Console.log("DONE", prompt, format_linebreaks, start, end,
|
|
492
|
+
Console.log("DONE", prompt, format_linebreaks, start, end, COLOR.TEAL, default_color)
|
|
398
493
|
Console.pause_exit(pause, exit)
|
|
399
494
|
|
|
400
495
|
@staticmethod
|
|
@@ -403,14 +498,13 @@ class Console:
|
|
|
403
498
|
format_linebreaks: bool = True,
|
|
404
499
|
start: str = "",
|
|
405
500
|
end: str = "\n",
|
|
406
|
-
|
|
407
|
-
default_color: Optional[Rgba | Hexa] = COLOR.text,
|
|
501
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
408
502
|
pause: bool = False,
|
|
409
503
|
exit: bool = False,
|
|
410
504
|
) -> None:
|
|
411
505
|
"""A preset for `log()`: `WARN` log message with the options to pause
|
|
412
506
|
at the message and exit the program after the message was printed."""
|
|
413
|
-
Console.log("WARN", prompt, format_linebreaks, start, end,
|
|
507
|
+
Console.log("WARN", prompt, format_linebreaks, start, end, COLOR.ORANGE, default_color)
|
|
414
508
|
Console.pause_exit(pause, exit)
|
|
415
509
|
|
|
416
510
|
@staticmethod
|
|
@@ -419,15 +513,14 @@ class Console:
|
|
|
419
513
|
format_linebreaks: bool = True,
|
|
420
514
|
start: str = "",
|
|
421
515
|
end: str = "\n",
|
|
422
|
-
|
|
423
|
-
default_color: Optional[Rgba | Hexa] = COLOR.text,
|
|
516
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
424
517
|
pause: bool = False,
|
|
425
518
|
exit: bool = True,
|
|
426
519
|
reset_ansi: bool = True,
|
|
427
520
|
) -> None:
|
|
428
521
|
"""A preset for `log()`: `FAIL` log message with the options to pause
|
|
429
522
|
at the message and exit the program after the message was printed."""
|
|
430
|
-
Console.log("FAIL", prompt, format_linebreaks, start, end,
|
|
523
|
+
Console.log("FAIL", prompt, format_linebreaks, start, end, COLOR.RED, default_color)
|
|
431
524
|
Console.pause_exit(pause, exit, reset_ansi=reset_ansi)
|
|
432
525
|
|
|
433
526
|
@staticmethod
|
|
@@ -436,15 +529,14 @@ class Console:
|
|
|
436
529
|
format_linebreaks: bool = True,
|
|
437
530
|
start: str = "",
|
|
438
531
|
end: str = "\n",
|
|
439
|
-
|
|
440
|
-
default_color: Optional[Rgba | Hexa] = COLOR.text,
|
|
532
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
441
533
|
pause: bool = False,
|
|
442
534
|
exit: bool = True,
|
|
443
535
|
reset_ansi: bool = True,
|
|
444
536
|
) -> None:
|
|
445
537
|
"""A preset for `log()`: `EXIT` log message with the options to pause
|
|
446
538
|
at the message and exit the program after the message was printed."""
|
|
447
|
-
Console.log("EXIT", prompt, format_linebreaks, start, end,
|
|
539
|
+
Console.log("EXIT", prompt, format_linebreaks, start, end, COLOR.MAGENTA, default_color)
|
|
448
540
|
Console.pause_exit(pause, exit, reset_ansi=reset_ansi)
|
|
449
541
|
|
|
450
542
|
@staticmethod
|
|
@@ -456,6 +548,7 @@ class Console:
|
|
|
456
548
|
default_color: Optional[Rgba | Hexa] = None,
|
|
457
549
|
w_padding: int = 2,
|
|
458
550
|
w_full: bool = False,
|
|
551
|
+
indent: int = 0,
|
|
459
552
|
) -> None:
|
|
460
553
|
"""Will print a box with a colored background, containing a formatted log message:
|
|
461
554
|
- `*values` -⠀the box content (each value is on a new line)
|
|
@@ -464,23 +557,25 @@ class Console:
|
|
|
464
557
|
- `box_bg_color` -⠀the background color of the box
|
|
465
558
|
- `default_color` -⠀the default text color of the `*values`
|
|
466
559
|
- `w_padding` -⠀the horizontal padding (in chars) to the box content
|
|
467
|
-
- `w_full` -⠀whether to make the box be the full console width or not
|
|
560
|
+
- `w_full` -⠀whether to make the box be the full console width or not
|
|
561
|
+
- `indent` -⠀the indentation of the box (in chars)\n
|
|
468
562
|
-----------------------------------------------------------------------------------
|
|
469
563
|
The box content can be formatted with special formatting codes. For more detailed
|
|
470
|
-
information about formatting codes, see `
|
|
564
|
+
information about formatting codes, see `format_codes` module documentation."""
|
|
471
565
|
lines, unfmt_lines, max_line_len = Console.__prepare_log_box(values, default_color)
|
|
472
566
|
pad_w_full = (Console.w - (max_line_len + (2 * w_padding))) if w_full else 0
|
|
473
567
|
if box_bg_color is not None and Color.is_valid(box_bg_color):
|
|
474
568
|
box_bg_color = Color.to_hexa(box_bg_color)
|
|
569
|
+
spaces_l = " " * indent
|
|
475
570
|
lines = [
|
|
476
|
-
f"[bg:{box_bg_color}]{' ' * w_padding}
|
|
477
|
-
|
|
571
|
+
f"{spaces_l}[bg:{box_bg_color}]{' ' * w_padding}"
|
|
572
|
+
+ _COMPILED["formatting"].sub(lambda m: f"{m.group(0)}[bg:{box_bg_color}]", line) +
|
|
573
|
+
(" " * ((w_padding + max_line_len - len(unfmt)) + pad_w_full)) + "[*]" for line, unfmt in zip(lines, unfmt_lines)
|
|
478
574
|
]
|
|
479
575
|
pady = " " * (Console.w if w_full else max_line_len + (2 * w_padding))
|
|
480
576
|
FormatCodes.print(
|
|
481
|
-
f"{start}[bg:{box_bg_color}]{pady}[*]\n"
|
|
482
|
-
+
|
|
483
|
-
+ f"\n[bg:{box_bg_color}]{pady}[_]",
|
|
577
|
+
f"{start}{spaces_l}[bg:{box_bg_color}]{pady}[*]\n" + "\n".join(lines)
|
|
578
|
+
+ f"\n{spaces_l}[bg:{box_bg_color}]{pady}[_]",
|
|
484
579
|
default_color=default_color or "#000",
|
|
485
580
|
sep="\n",
|
|
486
581
|
end=end,
|
|
@@ -492,10 +587,11 @@ class Console:
|
|
|
492
587
|
start: str = "",
|
|
493
588
|
end: str = "\n",
|
|
494
589
|
border_type: Literal["standard", "rounded", "strong", "double"] = "rounded",
|
|
495
|
-
border_style: str | Rgba | Hexa = f"dim|{COLOR.
|
|
590
|
+
border_style: str | Rgba | Hexa = f"dim|{COLOR.GRAY}",
|
|
496
591
|
default_color: Optional[Rgba | Hexa] = None,
|
|
497
592
|
w_padding: int = 1,
|
|
498
593
|
w_full: bool = False,
|
|
594
|
+
indent: int = 0,
|
|
499
595
|
_border_chars: Optional[tuple[str, str, str, str, str, str, str, str]] = None,
|
|
500
596
|
) -> None:
|
|
501
597
|
"""Will print a bordered box, containing a formatted log message:
|
|
@@ -507,10 +603,11 @@ class Console:
|
|
|
507
603
|
- `default_color` -⠀the default text color of the `*values`
|
|
508
604
|
- `w_padding` -⠀the horizontal padding (in chars) to the box content
|
|
509
605
|
- `w_full` -⠀whether to make the box be the full console width or not
|
|
606
|
+
- `indent` -⠀the indentation of the box (in chars)
|
|
510
607
|
- `_border_chars` -⠀define your own border characters set (overwrites `border_type`)\n
|
|
511
608
|
---------------------------------------------------------------------------------------
|
|
512
609
|
The box content can be formatted with special formatting codes. For more detailed
|
|
513
|
-
information about formatting codes, see `
|
|
610
|
+
information about formatting codes, see `format_codes` module documentation.\n
|
|
514
611
|
---------------------------------------------------------------------------------------
|
|
515
612
|
The `border_type` can be one of the following:
|
|
516
613
|
- `"standard" = ('┌', '─', '┐', '│', '┘', '─', '└', '│')`
|
|
@@ -534,18 +631,18 @@ class Console:
|
|
|
534
631
|
}
|
|
535
632
|
border_chars = borders.get(border_type, borders["standard"]) if _border_chars is None else _border_chars
|
|
536
633
|
lines, unfmt_lines, max_line_len = Console.__prepare_log_box(values, default_color)
|
|
537
|
-
print(unfmt_lines)
|
|
538
634
|
pad_w_full = (Console.w - (max_line_len + (2 * w_padding)) - (len(border_chars[1] * 2))) if w_full else 0
|
|
539
635
|
if border_style is not None and Color.is_valid(border_style):
|
|
540
636
|
border_style = Color.to_hexa(border_style)
|
|
637
|
+
spaces_l = " " * indent
|
|
541
638
|
border_l = f"[{border_style}]{border_chars[7]}[*]"
|
|
542
639
|
border_r = f"[{border_style}]{border_chars[3]}[_]"
|
|
543
640
|
lines = [
|
|
544
|
-
f"{border_l}{' ' * w_padding}{line}[_]" + " " *
|
|
545
|
-
for line, unfmt in zip(lines, unfmt_lines)
|
|
641
|
+
f"{spaces_l}{border_l}{' ' * w_padding}{line}[_]" + " " *
|
|
642
|
+
((w_padding + max_line_len - len(unfmt)) + pad_w_full) + border_r for line, unfmt in zip(lines, unfmt_lines)
|
|
546
643
|
]
|
|
547
|
-
border_t = f"[{border_style}]{border_chars[0]}{border_chars[1] * (Console.w - (len(border_chars[1] * 2)) if w_full else max_line_len + (2 * w_padding))}{border_chars[2]}[_]"
|
|
548
|
-
border_b = f"[{border_style}]{border_chars[6]}{border_chars[5] * (Console.w - (len(border_chars[1] * 2)) if w_full else max_line_len + (2 * w_padding))}{border_chars[4]}[_]"
|
|
644
|
+
border_t = f"{spaces_l}[{border_style}]{border_chars[0]}{border_chars[1] * (Console.w - (len(border_chars[1] * 2)) if w_full else max_line_len + (2 * w_padding))}{border_chars[2]}[_]"
|
|
645
|
+
border_b = f"{spaces_l}[{border_style}]{border_chars[6]}{border_chars[5] * (Console.w - (len(border_chars[1] * 2)) if w_full else max_line_len + (2 * w_padding))}{border_chars[4]}[_]"
|
|
549
646
|
FormatCodes.print(
|
|
550
647
|
f"{start}{border_t}[_]\n" + "\n".join(lines) + f"\n{border_b}[_]",
|
|
551
648
|
default_color=default_color,
|
|
@@ -568,22 +665,28 @@ class Console:
|
|
|
568
665
|
def confirm(
|
|
569
666
|
prompt: object = "Do you want to continue?",
|
|
570
667
|
start="",
|
|
571
|
-
end="
|
|
572
|
-
default_color: Optional[Rgba | Hexa] =
|
|
668
|
+
end="",
|
|
669
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
573
670
|
default_is_yes: bool = True,
|
|
574
671
|
) -> bool:
|
|
575
672
|
"""Ask a yes/no question.\n
|
|
576
673
|
---------------------------------------------------------------------------------------
|
|
674
|
+
- `prompt` -⠀the input prompt
|
|
675
|
+
- `start` -⠀something to print before the input
|
|
676
|
+
- `end` -⠀something to print after the input (e.g. `\\n`)
|
|
677
|
+
- `default_color` -⠀the default text color of the `prompt`
|
|
678
|
+
- `default_is_yes` -⠀the default answer if the user just presses enter
|
|
679
|
+
---------------------------------------------------------------------------------------
|
|
577
680
|
The prompt can be formatted with special formatting codes. For more detailed
|
|
578
|
-
information about formatting codes, see the `
|
|
681
|
+
information about formatting codes, see the `format_codes` module documentation."""
|
|
579
682
|
confirmed = input(
|
|
580
683
|
FormatCodes.to_ansi(
|
|
581
|
-
f'{start}
|
|
684
|
+
f'{start}{str(prompt)} [_|dim](({"Y" if default_is_yes else "y"}/{"n" if default_is_yes else "N"}): )',
|
|
582
685
|
default_color=default_color,
|
|
583
686
|
)
|
|
584
687
|
).strip().lower() in (("", "y", "yes") if default_is_yes else ("y", "yes"))
|
|
585
688
|
if end:
|
|
586
|
-
|
|
689
|
+
FormatCodes.print(end, end="")
|
|
587
690
|
return confirmed
|
|
588
691
|
|
|
589
692
|
@staticmethod
|
|
@@ -591,13 +694,13 @@ class Console:
|
|
|
591
694
|
prompt: object = "",
|
|
592
695
|
start="",
|
|
593
696
|
end="\n",
|
|
594
|
-
default_color: Optional[Rgba | Hexa] =
|
|
697
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
595
698
|
show_keybindings=True,
|
|
596
699
|
input_prefix=" ⮡ ",
|
|
597
700
|
reset_ansi=True,
|
|
598
701
|
) -> str:
|
|
599
|
-
"""An input where users can
|
|
600
|
-
|
|
702
|
+
"""An input where users can write (and paste) text over multiple lines.\n
|
|
703
|
+
---------------------------------------------------------------------------------------
|
|
601
704
|
- `prompt` -⠀the input prompt
|
|
602
705
|
- `start` -⠀something to print before the input
|
|
603
706
|
- `end` -⠀something to print after the input (e.g. `\\n`)
|
|
@@ -605,9 +708,9 @@ class Console:
|
|
|
605
708
|
- `show_keybindings` -⠀whether to show the special keybindings or not
|
|
606
709
|
- `input_prefix` -⠀the prefix of the input line
|
|
607
710
|
- `reset_ansi` -⠀whether to reset the ANSI codes after the input or not
|
|
608
|
-
|
|
711
|
+
---------------------------------------------------------------------------------------
|
|
609
712
|
The input prompt can be formatted with special formatting codes. For more detailed
|
|
610
|
-
information about formatting codes, see `
|
|
713
|
+
information about formatting codes, see the `format_codes` module documentation."""
|
|
611
714
|
kb = KeyBindings()
|
|
612
715
|
|
|
613
716
|
@kb.add("c-d", eager=True) # CTRL+D
|
|
@@ -617,135 +720,184 @@ class Console:
|
|
|
617
720
|
FormatCodes.print(start + str(prompt), default_color=default_color)
|
|
618
721
|
if show_keybindings:
|
|
619
722
|
FormatCodes.print("[dim][[b](CTRL+D)[dim] : end of input][_dim]")
|
|
620
|
-
input_string =
|
|
723
|
+
input_string = _pt.prompt(input_prefix, multiline=True, wrap_lines=True, key_bindings=kb)
|
|
621
724
|
FormatCodes.print("[_]" if reset_ansi else "", end=end[1:] if end.startswith("\n") else end)
|
|
622
725
|
return input_string
|
|
623
726
|
|
|
624
727
|
@staticmethod
|
|
625
|
-
def
|
|
728
|
+
def input(
|
|
626
729
|
prompt: object = "",
|
|
627
730
|
start="",
|
|
628
|
-
end="
|
|
629
|
-
default_color: Optional[Rgba | Hexa] =
|
|
630
|
-
|
|
731
|
+
end="",
|
|
732
|
+
default_color: Optional[Rgba | Hexa] = None,
|
|
733
|
+
placeholder: Optional[str] = None,
|
|
734
|
+
mask_char: Optional[str] = None,
|
|
631
735
|
min_len: Optional[int] = None,
|
|
632
736
|
max_len: Optional[int] = None,
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
-
|
|
640
|
-
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
FormatCodes.print(f"[_]{end}" if reset_ansi else end, default_color=default_color)
|
|
674
|
-
return True
|
|
675
|
-
|
|
676
|
-
def handle_backspace_delete():
|
|
677
|
-
nonlocal result, select_all
|
|
678
|
-
if select_all:
|
|
679
|
-
result, select_all = "", False
|
|
680
|
-
elif result and event.name == "backspace":
|
|
681
|
-
result = result[:-1]
|
|
682
|
-
update_display(Console.w)
|
|
683
|
-
|
|
684
|
-
def handle_paste():
|
|
685
|
-
nonlocal result, select_all
|
|
686
|
-
if select_all:
|
|
687
|
-
result, select_all = "", False
|
|
688
|
-
filtered_text = "".join(char for char in _pyperclip.paste() if allowed_chars == CHARS.all or char in allowed_chars)
|
|
689
|
-
if max_len is None or len(result) + len(filtered_text) <= max_len:
|
|
690
|
-
result += filtered_text
|
|
691
|
-
update_display(Console.w)
|
|
692
|
-
|
|
693
|
-
def handle_select_all():
|
|
694
|
-
nonlocal select_all
|
|
695
|
-
select_all = True
|
|
696
|
-
update_display(Console.w)
|
|
697
|
-
|
|
698
|
-
def handle_character_input():
|
|
699
|
-
nonlocal result
|
|
700
|
-
if event.name is not None and ((allowed_chars == CHARS.all or event.name in allowed_chars) and
|
|
701
|
-
(max_len is None or len(result) < max_len)):
|
|
702
|
-
result += event.name
|
|
703
|
-
update_display(Console.w)
|
|
704
|
-
|
|
705
|
-
while True:
|
|
706
|
-
event = _keyboard.read_event()
|
|
707
|
-
if event.event_type == "down":
|
|
708
|
-
if event.name == "enter" and handle_enter():
|
|
709
|
-
return result.rstrip("\n")
|
|
710
|
-
elif event.name in ("backspace", "delete", "entf"):
|
|
711
|
-
handle_backspace_delete()
|
|
712
|
-
elif (event.name == "v" and _keyboard.is_pressed("ctrl")) or _mouse.is_pressed("right"):
|
|
713
|
-
handle_paste()
|
|
714
|
-
elif event.name == "a" and _keyboard.is_pressed("ctrl"):
|
|
715
|
-
handle_select_all()
|
|
716
|
-
elif event.name == "c" and _keyboard.is_pressed("ctrl"):
|
|
717
|
-
raise KeyboardInterrupt
|
|
718
|
-
elif event.name == "esc":
|
|
719
|
-
return None
|
|
720
|
-
elif event.name == "space":
|
|
721
|
-
handle_character_input()
|
|
722
|
-
elif event.name is not None and len(event.name) == 1:
|
|
723
|
-
handle_character_input()
|
|
737
|
+
allowed_chars: str = CHARS.ALL, #type: ignore[assignment]
|
|
738
|
+
allow_paste: bool = True,
|
|
739
|
+
validator: Optional[Callable[[str], Optional[str]]] = None,
|
|
740
|
+
) -> str:
|
|
741
|
+
"""Acts like a standard Python `input()` a bunch of cool extra features.\n
|
|
742
|
+
------------------------------------------------------------------------------------
|
|
743
|
+
- `prompt` -⠀the input prompt
|
|
744
|
+
- `start` -⠀something to print before the input
|
|
745
|
+
- `end` -⠀something to print after the input (e.g. `\\n`)
|
|
746
|
+
- `default_color` -⠀the default text color of the `prompt`
|
|
747
|
+
- `placeholder` -⠀a placeholder text that is shown when the input is empty
|
|
748
|
+
- `mask_char` -⠀if set, the input will be masked with this character
|
|
749
|
+
- `min_len` -⠀the minimum length of the input (required to submit)
|
|
750
|
+
- `max_len` -⠀the maximum length of the input (can't write further if reached)
|
|
751
|
+
- `allowed_chars` -⠀a string of characters that are allowed to be inputted
|
|
752
|
+
(default allows all characters)
|
|
753
|
+
- `allow_paste` -⠀whether to allow pasting text into the input or not
|
|
754
|
+
- `validator` -⠀a function that takes the input string and returns a string error
|
|
755
|
+
message if invalid, or nothing if valid
|
|
756
|
+
------------------------------------------------------------------------------------
|
|
757
|
+
The input prompt can be formatted with special formatting codes. For more detailed
|
|
758
|
+
information about formatting codes, see the `format_codes` module documentation."""
|
|
759
|
+
result_text = ""
|
|
760
|
+
tried_pasting = False
|
|
761
|
+
filtered_chars = set()
|
|
762
|
+
|
|
763
|
+
class InputValidator(Validator):
|
|
764
|
+
|
|
765
|
+
def validate(self, document) -> None:
|
|
766
|
+
text_to_validate = result_text if mask_char else document.text
|
|
767
|
+
if min_len and len(text_to_validate) < min_len:
|
|
768
|
+
raise ValidationError(message="", cursor_position=len(document.text))
|
|
769
|
+
if validator and validator(text_to_validate) not in ("", None):
|
|
770
|
+
raise ValidationError(message="", cursor_position=len(document.text))
|
|
771
|
+
|
|
772
|
+
def bottom_toolbar() -> _pt.formatted_text.ANSI:
|
|
773
|
+
nonlocal tried_pasting
|
|
774
|
+
try:
|
|
775
|
+
if mask_char:
|
|
776
|
+
text_to_check = result_text
|
|
724
777
|
else:
|
|
725
|
-
|
|
726
|
-
|
|
778
|
+
app = _pt.application.get_app()
|
|
779
|
+
text_to_check = app.current_buffer.text
|
|
780
|
+
toolbar_msgs = []
|
|
781
|
+
if max_len and len(text_to_check) > max_len:
|
|
782
|
+
toolbar_msgs.append("[b|#FFF|bg:red]( Text too long! )")
|
|
783
|
+
if validator and text_to_check and (validation_error_msg := validator(text_to_check)) not in ("", None):
|
|
784
|
+
toolbar_msgs.append(f"[b|#000|bg:br:red] {validation_error_msg} [_bg]")
|
|
785
|
+
if filtered_chars:
|
|
786
|
+
plural = "" if len(char_list := "".join(sorted(filtered_chars))) == 1 else "s"
|
|
787
|
+
toolbar_msgs.append(f"[b|#000|bg:yellow]( Char{plural} '{char_list}' not allowed )")
|
|
788
|
+
filtered_chars.clear()
|
|
789
|
+
if min_len and len(text_to_check) < min_len:
|
|
790
|
+
toolbar_msgs.append(f"[b|#000|bg:yellow]( Need {min_len - len(text_to_check)} more chars )")
|
|
791
|
+
if tried_pasting:
|
|
792
|
+
toolbar_msgs.append("[b|#000|bg:br:yellow]( Pasting disabled )")
|
|
793
|
+
tried_pasting = False
|
|
794
|
+
if max_len and len(text_to_check) == max_len:
|
|
795
|
+
toolbar_msgs.append("[b|#000|bg:br:yellow]( Maximum length reached )")
|
|
796
|
+
return _pt.formatted_text.ANSI(FormatCodes.to_ansi(" ".join(toolbar_msgs)))
|
|
797
|
+
except Exception:
|
|
798
|
+
return _pt.formatted_text.ANSI("")
|
|
799
|
+
|
|
800
|
+
def process_insert_text(text: str) -> tuple[str, set[str]]:
|
|
801
|
+
removed_chars = set()
|
|
802
|
+
if not text: return "", removed_chars
|
|
803
|
+
processed_text = "".join(c for c in text if ord(c) >= 32)
|
|
804
|
+
if allowed_chars != CHARS.ALL:
|
|
805
|
+
filtered_text = ""
|
|
806
|
+
for char in processed_text:
|
|
807
|
+
if char in allowed_chars:
|
|
808
|
+
filtered_text += char
|
|
809
|
+
else:
|
|
810
|
+
removed_chars.add(char)
|
|
811
|
+
processed_text = filtered_text
|
|
812
|
+
if max_len:
|
|
813
|
+
if (remaining_space := max_len - len(result_text)) > 0:
|
|
814
|
+
if len(processed_text) > remaining_space:
|
|
815
|
+
processed_text = processed_text[:remaining_space]
|
|
816
|
+
else:
|
|
817
|
+
processed_text = ""
|
|
818
|
+
return processed_text, removed_chars
|
|
819
|
+
|
|
820
|
+
def insert_text_event(event: KeyPressEvent) -> None:
|
|
821
|
+
nonlocal result_text, filtered_chars
|
|
822
|
+
try:
|
|
823
|
+
insert_text = event.data
|
|
824
|
+
if not insert_text: return
|
|
825
|
+
buffer = event.app.current_buffer
|
|
826
|
+
cursor_pos = buffer.cursor_position
|
|
827
|
+
insert_text, filtered_chars = process_insert_text(insert_text)
|
|
828
|
+
if insert_text:
|
|
829
|
+
result_text = result_text[:cursor_pos] + insert_text + result_text[cursor_pos:]
|
|
830
|
+
if mask_char:
|
|
831
|
+
buffer.insert_text(mask_char[0] * len(insert_text))
|
|
832
|
+
else:
|
|
833
|
+
buffer.insert_text(insert_text)
|
|
834
|
+
except Exception:
|
|
835
|
+
pass
|
|
836
|
+
|
|
837
|
+
def remove_text_event(event: KeyPressEvent, is_backspace: bool = False) -> None:
|
|
838
|
+
nonlocal result_text
|
|
839
|
+
try:
|
|
840
|
+
buffer = event.app.current_buffer
|
|
841
|
+
cursor_pos = buffer.cursor_position
|
|
842
|
+
has_selection = buffer.selection_state is not None
|
|
843
|
+
if has_selection:
|
|
844
|
+
start, end = buffer.document.selection_range()
|
|
845
|
+
result_text = result_text[:start] + result_text[end:]
|
|
846
|
+
buffer.cursor_position = start
|
|
847
|
+
buffer.delete(end - start)
|
|
848
|
+
else:
|
|
849
|
+
if is_backspace:
|
|
850
|
+
if cursor_pos > 0:
|
|
851
|
+
result_text = result_text[:cursor_pos - 1] + result_text[cursor_pos:]
|
|
852
|
+
buffer.delete_before_cursor(1)
|
|
853
|
+
else:
|
|
854
|
+
if cursor_pos < len(result_text):
|
|
855
|
+
result_text = result_text[:cursor_pos] + result_text[cursor_pos + 1:]
|
|
856
|
+
buffer.delete(1)
|
|
857
|
+
except Exception:
|
|
858
|
+
pass
|
|
727
859
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
860
|
+
kb = KeyBindings()
|
|
861
|
+
|
|
862
|
+
@kb.add(Keys.Delete)
|
|
863
|
+
def _(event: KeyPressEvent) -> None:
|
|
864
|
+
remove_text_event(event)
|
|
865
|
+
|
|
866
|
+
@kb.add(Keys.Backspace)
|
|
867
|
+
def _(event: KeyPressEvent) -> None:
|
|
868
|
+
remove_text_event(event, is_backspace=True)
|
|
869
|
+
|
|
870
|
+
@kb.add(Keys.ControlA)
|
|
871
|
+
def _(event: KeyPressEvent) -> None:
|
|
872
|
+
buffer = event.app.current_buffer
|
|
873
|
+
buffer.cursor_position = 0
|
|
874
|
+
buffer.start_selection()
|
|
875
|
+
buffer.cursor_position = len(buffer.text)
|
|
876
|
+
|
|
877
|
+
@kb.add(Keys.BracketedPaste)
|
|
878
|
+
def _(event: KeyPressEvent) -> None:
|
|
879
|
+
if allow_paste:
|
|
880
|
+
insert_text_event(event)
|
|
881
|
+
else:
|
|
882
|
+
nonlocal tried_pasting
|
|
883
|
+
tried_pasting = True
|
|
884
|
+
|
|
885
|
+
@kb.add(Keys.Any)
|
|
886
|
+
def _(event: KeyPressEvent) -> None:
|
|
887
|
+
insert_text_event(event)
|
|
888
|
+
|
|
889
|
+
custom_style = Style.from_dict({'bottom-toolbar': 'noreverse'})
|
|
890
|
+
session = _pt.PromptSession(
|
|
891
|
+
message=_pt.formatted_text.ANSI(FormatCodes.to_ansi(str(prompt), default_color=default_color)),
|
|
892
|
+
validator=InputValidator(),
|
|
893
|
+
validate_while_typing=True,
|
|
894
|
+
key_bindings=kb,
|
|
895
|
+
bottom_toolbar=bottom_toolbar,
|
|
896
|
+
placeholder=_pt.formatted_text.ANSI(FormatCodes.to_ansi(f"[i|br:black]{placeholder}[_i|_c]"))
|
|
897
|
+
if placeholder else "",
|
|
898
|
+
style=custom_style,
|
|
751
899
|
)
|
|
900
|
+
FormatCodes.print(start, end="")
|
|
901
|
+
session.prompt()
|
|
902
|
+
FormatCodes.print(end, end="")
|
|
903
|
+
return result_text
|