pyscript-programming-language 1.14.0__py3-none-any.whl → 1.15.0.dev1__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.
- pyscript/core/analyzer.py +31 -40
- pyscript/core/buffer.py +6 -10
- pyscript/core/cache.py +4 -3
- pyscript/core/checks.py +5 -21
- pyscript/core/constants.py +1 -1
- pyscript/core/editor/bases.py +1 -1
- pyscript/core/editor/gui.py +87 -6
- pyscript/core/editor/terminal.py +14 -1
- pyscript/core/highlight.py +23 -3
- pyscript/core/interpreter.py +38 -48
- pyscript/core/lexer.py +46 -44
- pyscript/core/mapping.py +3 -0
- pyscript/core/nodes.py +415 -203
- pyscript/core/parser.py +144 -208
- pyscript/core/pysbuiltins.py +4 -37
- pyscript/core/pystypes.py +50 -8
- pyscript/core/results.py +2 -2
- pyscript/core/shell.py +42 -63
- pyscript/core/utils/ansi.py +45 -23
- pyscript/core/utils/decorators.py +8 -8
- pyscript/core/utils/generic.py +1 -4
- pyscript/core/utils/module.py +6 -3
- pyscript/core/utils/string.py +3 -3
- pyscript/core/version.py +2 -2
- pyscript/lib/ast/ast_dump.py +20 -32
- pyscript/lib/ast/ast_literal_eval.py +5 -12
- pyscript/lib/ast/ast_unparse.py +10 -10
- pyscript/lib/ast/ast_walk.py +20 -26
- pyscript/lib/brainfuck.pys +7 -2
- pyscript/lib/dual/__init__.pys +17 -0
- pyscript/lib/dual/py_dual.py +246 -0
- pyscript/lib/getch/__init__.pys +6 -1
- pyscript/lib/getch/py_getch.py +92 -11
- pyscript/lib/history.pys +10 -8
- pyscript/lib/inspect.pys +1 -1
- {pyscript_programming_language-1.14.0.dist-info → pyscript_programming_language-1.15.0.dev1.dist-info}/METADATA +6 -5
- {pyscript_programming_language-1.14.0.dist-info → pyscript_programming_language-1.15.0.dev1.dist-info}/RECORD +39 -37
- {pyscript_programming_language-1.14.0.dist-info → pyscript_programming_language-1.15.0.dev1.dist-info}/WHEEL +0 -0
- {pyscript_programming_language-1.14.0.dist-info → pyscript_programming_language-1.15.0.dev1.dist-info}/top_level.txt +0 -0
pyscript/core/analyzer.py
CHANGED
|
@@ -102,18 +102,17 @@ class PysAnalyzer(Pys):
|
|
|
102
102
|
|
|
103
103
|
keyword_names = set()
|
|
104
104
|
|
|
105
|
-
for
|
|
105
|
+
for argument in node.arguments:
|
|
106
106
|
|
|
107
|
-
if
|
|
108
|
-
token, value =
|
|
107
|
+
if argument.__class__ is tuple:
|
|
108
|
+
token, value = argument
|
|
109
109
|
name = token.value
|
|
110
110
|
if name in keyword_names:
|
|
111
111
|
self.throw(f"duplicate argument {name!r} in call definition", token.position)
|
|
112
112
|
return
|
|
113
113
|
keyword_names.add(name)
|
|
114
|
-
|
|
115
114
|
else:
|
|
116
|
-
value =
|
|
115
|
+
value = argument
|
|
117
116
|
|
|
118
117
|
self.visit(value)
|
|
119
118
|
if self.error:
|
|
@@ -163,7 +162,7 @@ class PysAnalyzer(Pys):
|
|
|
163
162
|
self.visit_declaration_from_AssignmentNode(node.target, f"cannot {operator} literal", operator)
|
|
164
163
|
|
|
165
164
|
def visit_StatementsNode(self, node: PysStatementsNode) -> None:
|
|
166
|
-
for element in node.
|
|
165
|
+
for element in node.elements:
|
|
167
166
|
self.visit(element)
|
|
168
167
|
if self.error:
|
|
169
168
|
return
|
|
@@ -179,7 +178,7 @@ class PysAnalyzer(Pys):
|
|
|
179
178
|
self.visit(node.value)
|
|
180
179
|
|
|
181
180
|
def visit_IfNode(self, node: PysIfNode) -> None:
|
|
182
|
-
for condition, body in node.
|
|
181
|
+
for condition, body in node.cases:
|
|
183
182
|
self.visit(condition)
|
|
184
183
|
if self.error:
|
|
185
184
|
return
|
|
@@ -188,8 +187,8 @@ class PysAnalyzer(Pys):
|
|
|
188
187
|
if self.error:
|
|
189
188
|
return
|
|
190
189
|
|
|
191
|
-
if node.
|
|
192
|
-
self.visit(node.
|
|
190
|
+
if node.else_:
|
|
191
|
+
self.visit(node.else_)
|
|
193
192
|
|
|
194
193
|
def visit_SwitchNode(self, node: PysSwitchNode) -> None:
|
|
195
194
|
self.visit(node.target)
|
|
@@ -198,8 +197,8 @@ class PysAnalyzer(Pys):
|
|
|
198
197
|
|
|
199
198
|
self.in_switch += 1
|
|
200
199
|
|
|
201
|
-
for
|
|
202
|
-
self.visit(
|
|
200
|
+
for case, body in node.cases:
|
|
201
|
+
self.visit(case)
|
|
203
202
|
if self.error:
|
|
204
203
|
return
|
|
205
204
|
|
|
@@ -207,8 +206,8 @@ class PysAnalyzer(Pys):
|
|
|
207
206
|
if self.error:
|
|
208
207
|
return
|
|
209
208
|
|
|
210
|
-
if node.
|
|
211
|
-
self.visit(node.
|
|
209
|
+
if node.default:
|
|
210
|
+
self.visit(node.default)
|
|
212
211
|
if self.error:
|
|
213
212
|
return
|
|
214
213
|
|
|
@@ -220,8 +219,8 @@ class PysAnalyzer(Pys):
|
|
|
220
219
|
if self.error:
|
|
221
220
|
return
|
|
222
221
|
|
|
223
|
-
for
|
|
224
|
-
self.visit(
|
|
222
|
+
for case, value in node.cases:
|
|
223
|
+
self.visit(case)
|
|
225
224
|
if self.error:
|
|
226
225
|
return
|
|
227
226
|
|
|
@@ -237,18 +236,18 @@ class PysAnalyzer(Pys):
|
|
|
237
236
|
if self.error:
|
|
238
237
|
return
|
|
239
238
|
|
|
240
|
-
for _, body in node.
|
|
239
|
+
for _, body in node.catches:
|
|
241
240
|
self.visit(body)
|
|
242
241
|
if self.error:
|
|
243
242
|
return
|
|
244
243
|
|
|
245
|
-
if node.
|
|
246
|
-
self.visit(node.
|
|
244
|
+
if node.else_:
|
|
245
|
+
self.visit(node.else_)
|
|
247
246
|
if self.error:
|
|
248
247
|
return
|
|
249
248
|
|
|
250
|
-
if node.
|
|
251
|
-
self.visit(node.
|
|
249
|
+
if node.finally_:
|
|
250
|
+
self.visit(node.finally_)
|
|
252
251
|
|
|
253
252
|
def visit_WithNode(self, node: PysWithNode) -> None:
|
|
254
253
|
for context, _ in node.contexts:
|
|
@@ -284,8 +283,8 @@ class PysAnalyzer(Pys):
|
|
|
284
283
|
|
|
285
284
|
self.in_loop -= 1
|
|
286
285
|
|
|
287
|
-
if node.
|
|
288
|
-
self.visit(node.
|
|
286
|
+
if node.else_:
|
|
287
|
+
self.visit(node.else_)
|
|
289
288
|
|
|
290
289
|
def visit_WhileNode(self, node: PysWhileNode) -> None:
|
|
291
290
|
self.visit(node.condition)
|
|
@@ -300,8 +299,8 @@ class PysAnalyzer(Pys):
|
|
|
300
299
|
|
|
301
300
|
self.in_loop -= 1
|
|
302
301
|
|
|
303
|
-
if node.
|
|
304
|
-
self.visit(node.
|
|
302
|
+
if node.else_:
|
|
303
|
+
self.visit(node.else_)
|
|
305
304
|
|
|
306
305
|
def visit_DoWhileNode(self, node: PysDoWhileNode) -> None:
|
|
307
306
|
self.in_loop += 1
|
|
@@ -316,11 +315,10 @@ class PysAnalyzer(Pys):
|
|
|
316
315
|
if self.error:
|
|
317
316
|
return
|
|
318
317
|
|
|
319
|
-
if node.
|
|
320
|
-
self.visit(node.
|
|
318
|
+
if node.else_:
|
|
319
|
+
self.visit(node.else_)
|
|
321
320
|
|
|
322
321
|
def visit_RepeatNode(self, node: PysRepeatNode) -> None:
|
|
323
|
-
|
|
324
322
|
if node.body:
|
|
325
323
|
self.in_loop += 1
|
|
326
324
|
|
|
@@ -334,8 +332,8 @@ class PysAnalyzer(Pys):
|
|
|
334
332
|
if self.error:
|
|
335
333
|
return
|
|
336
334
|
|
|
337
|
-
if node.
|
|
338
|
-
self.visit(node.
|
|
335
|
+
if node.else_:
|
|
336
|
+
self.visit(node.else_)
|
|
339
337
|
|
|
340
338
|
def visit_ClassNode(self, node: PysClassNode) -> None:
|
|
341
339
|
for decorator in node.decorators:
|
|
@@ -378,9 +376,9 @@ class PysAnalyzer(Pys):
|
|
|
378
376
|
|
|
379
377
|
parameter_names = set()
|
|
380
378
|
|
|
381
|
-
for
|
|
382
|
-
is_keyword =
|
|
383
|
-
token =
|
|
379
|
+
for parameter in node.parameters:
|
|
380
|
+
is_keyword = parameter.__class__ is tuple
|
|
381
|
+
token = parameter[0] if is_keyword else parameter
|
|
384
382
|
name = token.value
|
|
385
383
|
|
|
386
384
|
if name in parameter_names:
|
|
@@ -389,7 +387,7 @@ class PysAnalyzer(Pys):
|
|
|
389
387
|
parameter_names.add(name)
|
|
390
388
|
|
|
391
389
|
if is_keyword:
|
|
392
|
-
self.visit(
|
|
390
|
+
self.visit(parameter[1])
|
|
393
391
|
if self.error:
|
|
394
392
|
return
|
|
395
393
|
|
|
@@ -464,10 +462,6 @@ class PysAnalyzer(Pys):
|
|
|
464
462
|
if self.error:
|
|
465
463
|
return
|
|
466
464
|
|
|
467
|
-
elif type is PysKeywordNode:
|
|
468
|
-
self.throw(f"cannot delete {target.name.value}", target.position)
|
|
469
|
-
return
|
|
470
|
-
|
|
471
465
|
elif type is PysDebugNode:
|
|
472
466
|
self.throw("cannot delete to __debug__", node.position)
|
|
473
467
|
return
|
|
@@ -537,9 +531,6 @@ class PysAnalyzer(Pys):
|
|
|
537
531
|
if self.error:
|
|
538
532
|
return
|
|
539
533
|
|
|
540
|
-
elif type is PysKeywordNode:
|
|
541
|
-
self.throw(f"cannot {operator_name} to {node.name.value}", node.position)
|
|
542
|
-
|
|
543
534
|
elif type is PysDebugNode:
|
|
544
535
|
self.throw(f"cannot {operator_name} to __debug__", node.position)
|
|
545
536
|
|
pyscript/core/buffer.py
CHANGED
|
@@ -5,7 +5,7 @@ from .utils.string import normstr
|
|
|
5
5
|
|
|
6
6
|
from io import IOBase
|
|
7
7
|
from types import BuiltinMethodType
|
|
8
|
-
from typing import Iterable, Optional
|
|
8
|
+
from typing import Iterable, Optional
|
|
9
9
|
|
|
10
10
|
@immutable
|
|
11
11
|
class PysBuffer(Pys):
|
|
@@ -17,27 +17,23 @@ class PysFileBuffer(PysBuffer):
|
|
|
17
17
|
|
|
18
18
|
def __new__(
|
|
19
19
|
cls,
|
|
20
|
-
text:
|
|
20
|
+
text: 'str | bytes | bytearray | Iterable | BuiltinMethodType | IOBase | PysFileBuffer',
|
|
21
21
|
name: Optional[str | bytes] = None
|
|
22
22
|
) -> 'PysFileBuffer':
|
|
23
23
|
|
|
24
24
|
if isinstance(text, PysFileBuffer):
|
|
25
25
|
return text
|
|
26
|
-
|
|
27
26
|
elif isinstance(text, IOBase):
|
|
28
27
|
name = normstr(getattr(text, 'name', '<io>') if name is None else name)
|
|
29
28
|
text = normstr(text)
|
|
30
|
-
|
|
31
29
|
else:
|
|
32
30
|
name = '<string>' if name is None else normstr(name)
|
|
33
31
|
text = normstr(text)
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
setimuattr(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return instance
|
|
33
|
+
self = super().__new__(cls)
|
|
34
|
+
setimuattr(self, 'text', text)
|
|
35
|
+
setimuattr(self, 'name', name)
|
|
36
|
+
return self
|
|
41
37
|
|
|
42
38
|
def __repr__(self) -> str:
|
|
43
39
|
return f'<FileBuffer from {self.name!r}>'
|
pyscript/core/cache.py
CHANGED
|
@@ -18,6 +18,9 @@ pys_sys = ModuleType(
|
|
|
18
18
|
"Python sys module."
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
+
def intern_object(object: Any) -> Any:
|
|
22
|
+
return pys_sys.interns.setdefault((type(object), object), object)
|
|
23
|
+
|
|
21
24
|
pys_sys.__running_shell__ = False
|
|
22
25
|
pys_sys.__running_breakpoint__ = False
|
|
23
26
|
pys_sys.builtin_module_names = tuple(
|
|
@@ -33,6 +36,7 @@ pys_sys.singletons = {}
|
|
|
33
36
|
pys_sys.argv = ['']
|
|
34
37
|
pys_sys.encoding = 'utf-8'
|
|
35
38
|
pys_sys.flags = DEFAULT
|
|
39
|
+
pys_sys.intern_object = intern_object
|
|
36
40
|
pys_sys.displayhook = print_display
|
|
37
41
|
pys_sys.excepthook = print_traceback
|
|
38
42
|
pys_sys.clearhook = clear_shell
|
|
@@ -58,9 +62,6 @@ for name in (
|
|
|
58
62
|
if hasattr(sys, name):
|
|
59
63
|
setattr(pys_sys, name, getattr(sys, name))
|
|
60
64
|
|
|
61
|
-
def intern_object(object: Any) -> Any:
|
|
62
|
-
return pys_sys.interns.setdefault((type(object), object), object)
|
|
63
|
-
|
|
64
65
|
# sentinel
|
|
65
66
|
@singleton
|
|
66
67
|
@inheritable
|
pyscript/core/checks.py
CHANGED
|
@@ -4,10 +4,9 @@ from .nodes import *
|
|
|
4
4
|
from .token import TOKENS
|
|
5
5
|
|
|
6
6
|
is_expression = frozenset([
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
PysFunctionNode, PysEllipsisNode
|
|
7
|
+
PysConstantNode, PysDebugNode, PysIdentifierNode, PysDictionaryNode, PysSetNode, PysListNode, PysTupleNode,
|
|
8
|
+
PysAttributeNode, PysSubscriptNode, PysCallNode, PysChainOperatorNode, PysTernaryOperatorNode,
|
|
9
|
+
PysBinaryOperatorNode, PysUnaryOperatorNode, PysIncrementalNode, PysMatchNode, PysFunctionNode, PysEllipsisNode
|
|
11
10
|
]).__contains__
|
|
12
11
|
|
|
13
12
|
is_statement = frozenset([
|
|
@@ -16,31 +15,16 @@ is_statement = frozenset([
|
|
|
16
15
|
PysAssertNode, PysDeleteNode, PysContinueNode, PysBreakNode
|
|
17
16
|
]).__contains__
|
|
18
17
|
|
|
19
|
-
is_sequence = frozenset([
|
|
20
|
-
PysSetNode, PysListNode, PysTupleNode
|
|
21
|
-
]).__contains__
|
|
22
|
-
|
|
23
|
-
is_literal = frozenset([
|
|
24
|
-
PysNumberNode, PysStringNode
|
|
25
|
-
]).__contains__
|
|
26
|
-
|
|
18
|
+
is_sequence = frozenset([PysSetNode, PysListNode, PysTupleNode]).__contains__
|
|
27
19
|
is_keyword = frozenset(KEYWORDS).__contains__
|
|
28
20
|
is_constant_keyword = frozenset(CONSTANT_KEYWORDS).__contains__
|
|
29
|
-
|
|
30
|
-
is_equal = frozenset([
|
|
31
|
-
TOKENS['EQUAL'], TOKENS['EQUAL_COLON']
|
|
32
|
-
]).__contains__
|
|
21
|
+
is_equal = frozenset([TOKENS['EQUAL'], TOKENS['EQUAL_COLON']]).__contains__
|
|
33
22
|
|
|
34
23
|
is_blacklist_python_builtin = frozenset([
|
|
35
24
|
'IndentationError', 'TabError', 'breakpoint', 'compile', 'copyright', 'credits', 'dir', 'eval', 'exec', 'help',
|
|
36
25
|
'globals', 'license', 'locals', 'vars'
|
|
37
26
|
]).__contains__
|
|
38
27
|
|
|
39
|
-
is_python_extension = frozenset([
|
|
40
|
-
'.cgi', '.fcgi', '.gyp', '.gypi', '.ipy', '.lmi', '.pxd', '.pxi', '.py', '.py3', '.pyc', '.pyd', '.pyde', '.pyi',
|
|
41
|
-
'.pyo', '.pyp', '.pyproj', '.pyt', '.pyw', '.pyx', '.pyz', '.rpy', '.spec', '.tac', '.wsgi', '.xpy'
|
|
42
|
-
]).__contains__
|
|
43
|
-
|
|
44
28
|
is_left_bracket = frozenset(BRACKETS_MAP.keys()).__contains__
|
|
45
29
|
is_right_bracket = frozenset(BRACKETS_MAP.values()).__contains__
|
|
46
30
|
is_bracket = frozenset(BRACKETS_MAP.keys() | BRACKETS_MAP.values()).__contains__
|
pyscript/core/constants.py
CHANGED
|
@@ -17,7 +17,7 @@ ENV_PYSCRIPT_NO_COLOR_PROMPT = 'PYSCRIPT_NO_COLOR_PROMPT'
|
|
|
17
17
|
ENV_PYSCRIPT_MAXIMUM_TRACEBACK_LINE = 'PYSCRIPT_MAXIMUM_TRACEBACK_LINE'
|
|
18
18
|
ENV_PYSCRIPT_CLASSIC_LINE_SHELL = 'PYSCRIPT_CLASSIC_LINE_SHELL'
|
|
19
19
|
ENV_PYSCRIPT_HISTORY_PATH = 'PYSCRIPT_HISTORY_PATH'
|
|
20
|
-
|
|
20
|
+
ENV_PYSCRIPT_HISTORY_LIMIT = 'PYSCRIPT_HISTORY_LIMIT'
|
|
21
21
|
|
|
22
22
|
KEYWORDS = (
|
|
23
23
|
'__debug__', 'False', 'None', 'True', 'and', 'as', 'assert', 'break', 'case', 'catch', 'class', 'constructor',
|
pyscript/core/editor/bases.py
CHANGED
|
@@ -15,8 +15,8 @@ class PysEditor(Pys):
|
|
|
15
15
|
@typecheck
|
|
16
16
|
def __init__(self, file: PysFileBuffer, colored: bool = True) -> None:
|
|
17
17
|
self.file = file
|
|
18
|
-
self.colored = bool(colored)
|
|
19
18
|
self.basename = basename(self.file.name)
|
|
19
|
+
self.colored = bool(colored)
|
|
20
20
|
self.used = False
|
|
21
21
|
self.modified = False
|
|
22
22
|
|
pyscript/core/editor/gui.py
CHANGED
|
@@ -6,8 +6,17 @@ from ..utils.generic import boundary
|
|
|
6
6
|
from ..version import __version__
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
|
-
from tkinter import Tk, Scrollbar, Text, messagebox
|
|
10
|
-
from tkinter.font import Font
|
|
9
|
+
from tkinter import Tk, Scrollbar, Text, Canvas, messagebox
|
|
10
|
+
from tkinter.font import families, Font
|
|
11
|
+
|
|
12
|
+
MONO_FALLBACKS = (
|
|
13
|
+
'Fira Code',
|
|
14
|
+
'Cascadia Code', 'SF Mono', 'Ubuntu Mono', 'Liberation Mono',
|
|
15
|
+
'Consolas', 'Menlo', 'DejaVu Sans Mono',
|
|
16
|
+
'Courier New', 'Lucida Console', 'Monaco', 'Andale Mono',
|
|
17
|
+
'Courier', 'Terminal',
|
|
18
|
+
'monospace'
|
|
19
|
+
)
|
|
11
20
|
|
|
12
21
|
class PysGUIEditor(PysEditor, Tk):
|
|
13
22
|
|
|
@@ -45,6 +54,7 @@ try:
|
|
|
45
54
|
if self.text.edit_modified():
|
|
46
55
|
self.modified = True
|
|
47
56
|
update()
|
|
57
|
+
refresh_line_numbers()
|
|
48
58
|
self.text.edit_modified(False)
|
|
49
59
|
|
|
50
60
|
def on_change_font(value):
|
|
@@ -53,6 +63,7 @@ try:
|
|
|
53
63
|
size = boundary(font.cget('size') + value, 1, 127)
|
|
54
64
|
font.config(size=size)
|
|
55
65
|
update_tab_size()
|
|
66
|
+
refresh_line_numbers()
|
|
56
67
|
self.set_configuration('gui-size-font', size)
|
|
57
68
|
return 'break'
|
|
58
69
|
return wrapper
|
|
@@ -61,6 +72,7 @@ try:
|
|
|
61
72
|
text = self.text
|
|
62
73
|
wrap = text.cget('wrap') != 'char'
|
|
63
74
|
text.configure(wrap='char' if wrap else 'none')
|
|
75
|
+
refresh_line_numbers()
|
|
64
76
|
self.set_configuration('wrap', wrap)
|
|
65
77
|
return 'break'
|
|
66
78
|
|
|
@@ -86,9 +98,64 @@ try:
|
|
|
86
98
|
text.see('insert')
|
|
87
99
|
return 'break'
|
|
88
100
|
|
|
101
|
+
def draw_line_numbers():
|
|
102
|
+
self.line_job = None
|
|
103
|
+
text = self.text
|
|
104
|
+
canvas = self.line_numbers
|
|
105
|
+
font = self.font
|
|
106
|
+
|
|
107
|
+
bold = (font.cget('family'), font.cget('size'), 'bold')
|
|
108
|
+
digits = len(text.index('end-1c').split('.')[0])
|
|
109
|
+
padding = font.measure(' ')
|
|
110
|
+
width = font.measure('0' * digits) + padding * 2
|
|
111
|
+
|
|
112
|
+
canvas.delete('all')
|
|
113
|
+
canvas.configure(width=width, bg='#1f1f1f')
|
|
114
|
+
|
|
115
|
+
current = text.index('insert').split('.')[0]
|
|
116
|
+
index = text.index('@0,0')
|
|
117
|
+
previous = None
|
|
118
|
+
|
|
119
|
+
while True:
|
|
120
|
+
info = text.dlineinfo(index)
|
|
121
|
+
if info is None:
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
number = index.split('.')[0]
|
|
125
|
+
if number == previous:
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
canvas.create_text(
|
|
129
|
+
width - padding, info[1],
|
|
130
|
+
anchor='ne',
|
|
131
|
+
text=number,
|
|
132
|
+
font=bold if number == current else font,
|
|
133
|
+
fill='#ffffff'
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
previous = number
|
|
137
|
+
index = text.index(f'{index} +1 lines')
|
|
138
|
+
|
|
139
|
+
def refresh_line_numbers(event=None):
|
|
140
|
+
if self.line_job is None:
|
|
141
|
+
self.line_job = self.after_idle(draw_line_numbers)
|
|
142
|
+
|
|
143
|
+
def on_scroll(first, last):
|
|
144
|
+
self.scrollbar.set(first, last)
|
|
145
|
+
refresh_line_numbers()
|
|
146
|
+
|
|
147
|
+
installed = frozenset(families())
|
|
148
|
+
for font_name in MONO_FALLBACKS:
|
|
149
|
+
if font_name in installed:
|
|
150
|
+
break
|
|
151
|
+
else:
|
|
152
|
+
font_name = 'monospace'
|
|
153
|
+
|
|
89
154
|
self.lexer = PygmentsPyScriptLexer()
|
|
90
|
-
self.font = Font(family=
|
|
155
|
+
self.font = Font(family=font_name, size=boundary(self.get_configuration('gui-size-font', 10), 1, 127))
|
|
156
|
+
self.line_job = None
|
|
91
157
|
self.scrollbar = Scrollbar(self)
|
|
158
|
+
self.line_numbers = Canvas(self, width=0, highlightthickness=0, bd=0)
|
|
92
159
|
self.text = Text(
|
|
93
160
|
self,
|
|
94
161
|
undo=True,
|
|
@@ -98,7 +165,7 @@ try:
|
|
|
98
165
|
bg='#000000',
|
|
99
166
|
fg='#ffffff',
|
|
100
167
|
insertbackground='white',
|
|
101
|
-
yscrollcommand=
|
|
168
|
+
yscrollcommand=on_scroll
|
|
102
169
|
)
|
|
103
170
|
|
|
104
171
|
update_tab_size()
|
|
@@ -106,6 +173,8 @@ try:
|
|
|
106
173
|
self.scrollbar.pack_configure(side='right', fill='y')
|
|
107
174
|
self.scrollbar.configure(command=self.text.yview)
|
|
108
175
|
|
|
176
|
+
self.line_numbers.pack_configure(side='left', fill='y')
|
|
177
|
+
self.text.pack_configure(side='left', expand=True, fill='both')
|
|
109
178
|
self.text.pack_configure(side='left', expand=True, fill='both')
|
|
110
179
|
|
|
111
180
|
self.text.insert('1.0', self.file.text)
|
|
@@ -115,6 +184,9 @@ try:
|
|
|
115
184
|
self.text.bind('<Return>', on_enter)
|
|
116
185
|
self.text.bind('<Prior>', on_page_up)
|
|
117
186
|
self.text.bind('<Next>', on_page_down)
|
|
187
|
+
self.text.bind('<KeyRelease>', refresh_line_numbers)
|
|
188
|
+
self.text.bind('<ButtonRelease-1>', refresh_line_numbers)
|
|
189
|
+
self.text.bind('<Configure>', refresh_line_numbers)
|
|
118
190
|
|
|
119
191
|
self.bind('<Configure>', on_configure)
|
|
120
192
|
self.bind_all('<Control-S>', on_save)
|
|
@@ -145,14 +217,16 @@ try:
|
|
|
145
217
|
self.wm_protocol('WM_DELETE_WINDOW', on_close)
|
|
146
218
|
|
|
147
219
|
if PYGMENTS and colored:
|
|
220
|
+
self.update_job = None
|
|
221
|
+
|
|
148
222
|
tag_configure = self.text.tag_configure
|
|
149
223
|
for token, style in PygmentsPyScriptStyle.list_styles():
|
|
150
224
|
color = style['color']
|
|
151
225
|
if color:
|
|
152
226
|
tag_configure(str(token), foreground=f'#{color}')
|
|
153
227
|
|
|
154
|
-
def
|
|
155
|
-
self.
|
|
228
|
+
def refresh_highlight():
|
|
229
|
+
self.update_job = None
|
|
156
230
|
|
|
157
231
|
index = '1.0'
|
|
158
232
|
|
|
@@ -172,11 +246,18 @@ try:
|
|
|
172
246
|
text_tag_add(to_string(type), index, end_index)
|
|
173
247
|
index = end_index
|
|
174
248
|
|
|
249
|
+
def update():
|
|
250
|
+
self.title(f'PyScript {__version__} - {self.basename}{"*" if self.modified else ""}')
|
|
251
|
+
if self.update_job is not None:
|
|
252
|
+
self.after_cancel(self.update_job)
|
|
253
|
+
self.update_job = self.after(500, refresh_highlight)
|
|
254
|
+
|
|
175
255
|
else:
|
|
176
256
|
def update():
|
|
177
257
|
self.title(f'PyScript {__version__} - {self.basename}{"*" if self.modified else ""}')
|
|
178
258
|
|
|
179
259
|
update()
|
|
260
|
+
refresh_line_numbers()
|
|
180
261
|
|
|
181
262
|
def run(self) -> None:
|
|
182
263
|
PysEditor.run(self)
|
pyscript/core/editor/terminal.py
CHANGED
|
@@ -65,6 +65,13 @@ try:
|
|
|
65
65
|
title = file
|
|
66
66
|
return ANSI(f'\x1b[7m{title}{" " * max(0, columns - len(title))}\x1b[0m')
|
|
67
67
|
|
|
68
|
+
def create_line_prefix(line_number, wrap_count):
|
|
69
|
+
width = len(str(self.text.document.line_count))
|
|
70
|
+
number = str(line_number + 1) if wrap_count == 0 else ''
|
|
71
|
+
is_current = line_number == self.text.document.cursor_position_row
|
|
72
|
+
style = 'bg:#ffffff fg:#000000' + (' bold' if is_current else '')
|
|
73
|
+
return [(style, f' {number:>{width}} '), ('', ' ')]
|
|
74
|
+
|
|
68
75
|
else:
|
|
69
76
|
app_other_keyword.update({
|
|
70
77
|
'color_depth': ColorDepth.DEPTH_1_BIT
|
|
@@ -85,10 +92,16 @@ try:
|
|
|
85
92
|
title = file
|
|
86
93
|
return title + ' ' * max(0, columns - len(title))
|
|
87
94
|
|
|
95
|
+
def create_line_prefix(line_number, wrap_count):
|
|
96
|
+
width = len(str(self.text.document.line_count))
|
|
97
|
+
number = str(line_number + 1) if wrap_count == 0 else ''
|
|
98
|
+
return f' {number:>{width}} | '
|
|
99
|
+
|
|
88
100
|
self.text = TextArea(
|
|
89
101
|
multiline=True,
|
|
90
102
|
wrap_lines=on_wrap,
|
|
91
103
|
input_processors=[TabsProcessor(tabstop=4)],
|
|
104
|
+
get_line_prefix=create_line_prefix,
|
|
92
105
|
**text_other_keyword
|
|
93
106
|
)
|
|
94
107
|
|
|
@@ -188,7 +201,7 @@ try:
|
|
|
188
201
|
)
|
|
189
202
|
|
|
190
203
|
self.text.text = self.file.text
|
|
191
|
-
self.text.buffer.on_text_changed
|
|
204
|
+
self.text.buffer.on_text_changed.add_handler(on_change)
|
|
192
205
|
|
|
193
206
|
Application.__init__(
|
|
194
207
|
self,
|
pyscript/core/highlight.py
CHANGED
|
@@ -9,10 +9,11 @@ from .pysbuiltins import pys_builtins
|
|
|
9
9
|
from .token import TOKENS
|
|
10
10
|
from .utils.ansi import acolor
|
|
11
11
|
from .utils.decorators import typecheck
|
|
12
|
+
from .utils.generic import dfrozen
|
|
12
13
|
from .utils.string import normstr
|
|
13
14
|
|
|
14
15
|
from html import escape as html_escape
|
|
15
|
-
from typing import Callable, Optional
|
|
16
|
+
from typing import Callable, Iterator, Optional
|
|
16
17
|
|
|
17
18
|
BUILTIN_TYPES = frozenset(
|
|
18
19
|
name
|
|
@@ -31,10 +32,11 @@ try:
|
|
|
31
32
|
from pygments.lexer import RegexLexer, include, bygroups
|
|
32
33
|
from pygments.style import Style
|
|
33
34
|
from pygments.token import (
|
|
34
|
-
Comment, Error, Escape, Generic, Keyword, Name, Number, Operator, Punctuation, String, Whitespace
|
|
35
|
+
_TokenType, Comment, Error, Escape, Generic, Keyword, Name, Number, Operator, Punctuation, String, Whitespace
|
|
35
36
|
)
|
|
36
37
|
from pygments.unistring import xid_start, xid_continue
|
|
37
38
|
|
|
39
|
+
_brackets_tokens = dfrozen({index: getattr(Punctuation, f'Brackets{index}') for index in range(3)})
|
|
38
40
|
_set_constant_keywords = frozenset(CONSTANT_KEYWORDS)
|
|
39
41
|
_keywords = '|'.join(KEYWORDS)
|
|
40
42
|
_unicode_name = f'[{xid_start}][{xid_continue}]*'
|
|
@@ -112,6 +114,10 @@ try:
|
|
|
112
114
|
Comment.Single: HIGHLIGHT_MAP['comment'],
|
|
113
115
|
Error: HIGHLIGHT_MAP['invalid'],
|
|
114
116
|
Generic.Prompt: HIGHLIGHT_MAP['keyword-other'],
|
|
117
|
+
|
|
118
|
+
Punctuation.Brackets0: HIGHLIGHT_MAP['brackets-0'],
|
|
119
|
+
Punctuation.Brackets1: HIGHLIGHT_MAP['brackets-1'],
|
|
120
|
+
Punctuation.Brackets2: HIGHLIGHT_MAP['brackets-2']
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
class PygmentsPyScriptLexer(Pys, RegexLexer):
|
|
@@ -324,6 +330,20 @@ try:
|
|
|
324
330
|
|
|
325
331
|
}
|
|
326
332
|
|
|
333
|
+
def get_tokens_unprocessed(self, *args, **kwargs) -> Iterator[tuple[int, _TokenType, str]]:
|
|
334
|
+
brackets_stack = []
|
|
335
|
+
|
|
336
|
+
for index, token, value in super().get_tokens_unprocessed(*args, **kwargs):
|
|
337
|
+
if token is Punctuation and len(value) == 1 and is_bracket(ttype := ord(value)):
|
|
338
|
+
if is_left_bracket(ttype):
|
|
339
|
+
token = _brackets_tokens[len(brackets_stack) % 3]
|
|
340
|
+
brackets_stack.append(ttype)
|
|
341
|
+
elif not (brackets_stack and BRACKETS_MAP[brackets_stack.pop()] == ttype):
|
|
342
|
+
token = Error
|
|
343
|
+
else:
|
|
344
|
+
token = _brackets_tokens[len(brackets_stack) % 3]
|
|
345
|
+
yield index, token, value
|
|
346
|
+
|
|
327
347
|
class PygmentsPyScriptShellLexer(PygmentsPyScriptLexer):
|
|
328
348
|
|
|
329
349
|
"""
|
|
@@ -521,8 +541,8 @@ def pys_highlight(
|
|
|
521
541
|
type_format = 'comment'
|
|
522
542
|
|
|
523
543
|
elif is_bracket(ttype):
|
|
524
|
-
type_format = f'brackets-{len(brackets_stack) % max_bracket_level}'
|
|
525
544
|
if is_left_bracket(ttype):
|
|
545
|
+
type_format = f'brackets-{len(brackets_stack) % max_bracket_level}'
|
|
526
546
|
brackets_stack.append(ttype)
|
|
527
547
|
elif not (brackets_stack and BRACKETS_MAP[brackets_stack.pop()] == ttype):
|
|
528
548
|
type_format = 'invalid'
|