thonny-html-highlighter 1.0.0__tar.gz

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.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: thonny-html-highlighter
3
+ Version: 1.0.0
4
+ Summary: Coloration syntaxique HTML, CSS et JavaScript pour Thonny
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: thonny>=5.0.0
8
+ Dynamic: requires-python
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "thonny-html-highlighter"
7
+ version = "1.0.0"
8
+ description = "Coloration syntaxique HTML, CSS et JavaScript pour Thonny"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+
12
+ dependencies = [
13
+ "thonny>=5.0.0",
14
+ ]
15
+
16
+ [tool.setuptools.packages.find]
17
+ include = ["thonnycontrib*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ from setuptools import setup, find_namespace_packages
2
+
3
+
4
+ setup(
5
+ name="thonny-html-highlighter",
6
+ version="1.0.0",
7
+
8
+ description="Coloration syntaxique HTML, CSS et JavaScript pour Thonny",
9
+
10
+ packages=find_namespace_packages(),
11
+
12
+ install_requires=[
13
+ "thonny>=5.0.0",
14
+ ],
15
+
16
+ python_requires=">=3.10",
17
+ )
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: thonny-html-highlighter
3
+ Version: 1.0.0
4
+ Summary: Coloration syntaxique HTML, CSS et JavaScript pour Thonny
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: thonny>=5.0.0
8
+ Dynamic: requires-python
@@ -0,0 +1,8 @@
1
+ pyproject.toml
2
+ setup.py
3
+ thonny_html_highlighter.egg-info/PKG-INFO
4
+ thonny_html_highlighter.egg-info/SOURCES.txt
5
+ thonny_html_highlighter.egg-info/dependency_links.txt
6
+ thonny_html_highlighter.egg-info/requires.txt
7
+ thonny_html_highlighter.egg-info/top_level.txt
8
+ thonnycontrib/html_highlighter.py
@@ -0,0 +1,574 @@
1
+ import re
2
+
3
+ from thonny import get_workbench
4
+ from tkinter import TclError
5
+
6
+
7
+ # ============================================================
8
+ # Utilitaires
9
+ # ============================================================
10
+
11
+ def position(offset, index):
12
+ """Convertit un index caractère en position Tkinter."""
13
+ return f"1.0+{offset + index}c"
14
+
15
+
16
+ # ============================================================
17
+ # Expressions régulières HTML
18
+ # ============================================================
19
+
20
+ COMMENT_RE = re.compile(
21
+ r"<!--.*?-->",
22
+ re.DOTALL
23
+ )
24
+
25
+ TAG_RE = re.compile(
26
+ r"<\s*/?\s*[A-Za-z][^>]*?>",
27
+ re.DOTALL
28
+ )
29
+
30
+ TAG_NAME_RE = re.compile(
31
+ r"<\s*/?\s*([A-Za-z][\w:-]*)"
32
+ )
33
+
34
+ ATTRIBUTE_RE = re.compile(
35
+ r"\b([A-Za-z_:][\w:.-]*)\s*="
36
+ )
37
+
38
+ VALUE_RE = re.compile(
39
+ r"=\s*(\"[^\"]*\"|'[^']*')"
40
+ )
41
+
42
+
43
+ # ============================================================
44
+ # Blocs CSS / JavaScript
45
+ # ============================================================
46
+
47
+ STYLE_RE = re.compile(
48
+ r"<style\b[^>]*>(.*?)</style\s*>",
49
+ re.IGNORECASE | re.DOTALL
50
+ )
51
+
52
+ SCRIPT_RE = re.compile(
53
+ r"<script\b[^>]*>(.*?)</script\s*>",
54
+ re.IGNORECASE | re.DOTALL
55
+ )
56
+
57
+
58
+ # ============================================================
59
+ # HTML
60
+ # ============================================================
61
+
62
+ def highlight_html_part(text, source, offset):
63
+
64
+ # --------------------------------------------------------
65
+ # Commentaires HTML
66
+ # --------------------------------------------------------
67
+
68
+ for match in COMMENT_RE.finditer(source):
69
+
70
+ text.tag_add(
71
+ "html_comment",
72
+ position(offset, match.start()),
73
+ position(offset, match.end())
74
+ )
75
+
76
+ # --------------------------------------------------------
77
+ # Balises
78
+ # --------------------------------------------------------
79
+
80
+ for match in TAG_RE.finditer(source):
81
+
82
+ tag_start = match.start()
83
+ tag_end = match.end()
84
+
85
+ # Balise complète
86
+ text.tag_add(
87
+ "html_tag",
88
+ position(offset, tag_start),
89
+ position(offset, tag_end)
90
+ )
91
+
92
+ # Nom de la balise
93
+ name = TAG_NAME_RE.search(match.group())
94
+
95
+ if name:
96
+
97
+ start = tag_start + name.start(1)
98
+ end = tag_start + name.end(1)
99
+
100
+ text.tag_add(
101
+ "html_tag_name",
102
+ position(offset, start),
103
+ position(offset, end)
104
+ )
105
+
106
+ # Attributs
107
+ tag_content = match.group()
108
+
109
+ for attr in ATTRIBUTE_RE.finditer(tag_content):
110
+
111
+ start = tag_start + attr.start(1)
112
+ end = tag_start + attr.end(1)
113
+
114
+ text.tag_add(
115
+ "html_attribute",
116
+ position(offset, start),
117
+ position(offset, end)
118
+ )
119
+
120
+ # Valeurs des attributs
121
+ for value in VALUE_RE.finditer(tag_content):
122
+
123
+ start = tag_start + value.start(1)
124
+ end = tag_start + value.end(1)
125
+
126
+ text.tag_add(
127
+ "html_value",
128
+ position(offset, start),
129
+ position(offset, end)
130
+ )
131
+
132
+
133
+ # ============================================================
134
+ # CSS
135
+ # ============================================================
136
+
137
+ def highlight_css(text, source, offset):
138
+
139
+ # --------------------------------------------------------
140
+ # Commentaires CSS
141
+ # --------------------------------------------------------
142
+
143
+ comment_re = re.compile(
144
+ r"/\*.*?\*/",
145
+ re.DOTALL
146
+ )
147
+
148
+ for match in comment_re.finditer(source):
149
+
150
+ text.tag_add(
151
+ "css_comment",
152
+ position(offset, match.start()),
153
+ position(offset, match.end())
154
+ )
155
+
156
+ # --------------------------------------------------------
157
+ # Sélecteurs
158
+ # --------------------------------------------------------
159
+
160
+ selector_re = re.compile(
161
+ r"([^{}]+)\{"
162
+ )
163
+
164
+ for match in selector_re.finditer(source):
165
+
166
+ start = match.start(1)
167
+ end = match.end(1)
168
+
169
+ text.tag_add(
170
+ "css_selector",
171
+ position(offset, start),
172
+ position(offset, end)
173
+ )
174
+
175
+ # --------------------------------------------------------
176
+ # Propriétés CSS
177
+ # --------------------------------------------------------
178
+
179
+ property_re = re.compile(
180
+ r"\b([A-Za-z-]+)\s*:"
181
+ )
182
+
183
+ for match in property_re.finditer(source):
184
+
185
+ text.tag_add(
186
+ "css_property",
187
+ position(offset, match.start(1)),
188
+ position(offset, match.end(1))
189
+ )
190
+
191
+ # --------------------------------------------------------
192
+ # Valeurs CSS
193
+ # --------------------------------------------------------
194
+
195
+ value_re = re.compile(
196
+ r":\s*([^;{}]+)"
197
+ )
198
+
199
+ for match in value_re.finditer(source):
200
+
201
+ text.tag_add(
202
+ "css_value",
203
+ position(offset, match.start(1)),
204
+ position(offset, match.end(1))
205
+ )
206
+
207
+
208
+ # ============================================================
209
+ # JavaScript
210
+ # ============================================================
211
+
212
+ def highlight_javascript(text, source, offset):
213
+
214
+ # --------------------------------------------------------
215
+ # Commentaires //
216
+ # --------------------------------------------------------
217
+
218
+ line_comment_re = re.compile(
219
+ r"//.*"
220
+ )
221
+
222
+ for match in line_comment_re.finditer(source):
223
+
224
+ text.tag_add(
225
+ "js_comment",
226
+ position(offset, match.start()),
227
+ position(offset, match.end())
228
+ )
229
+
230
+ # --------------------------------------------------------
231
+ # Commentaires /* ... */
232
+ # --------------------------------------------------------
233
+
234
+ block_comment_re = re.compile(
235
+ r"/\*.*?\*/",
236
+ re.DOTALL
237
+ )
238
+
239
+ for match in block_comment_re.finditer(source):
240
+
241
+ text.tag_add(
242
+ "js_comment",
243
+ position(offset, match.start()),
244
+ position(offset, match.end())
245
+ )
246
+
247
+ # --------------------------------------------------------
248
+ # Chaînes de caractères
249
+ # --------------------------------------------------------
250
+
251
+ string_re = re.compile(
252
+ r"""(["'`])(?:\\.|(?!\1).)*\1"""
253
+ )
254
+
255
+ for match in string_re.finditer(source):
256
+
257
+ text.tag_add(
258
+ "js_string",
259
+ position(offset, match.start()),
260
+ position(offset, match.end())
261
+ )
262
+
263
+ # --------------------------------------------------------
264
+ # Mots-clés
265
+ # --------------------------------------------------------
266
+
267
+ keyword_re = re.compile(
268
+ r"\b("
269
+ r"let|const|var|function|return|if|else|"
270
+ r"for|while|do|switch|case|break|continue|"
271
+ r"new|class|this|true|false|null|undefined|"
272
+ r"try|catch|finally|throw|async|await|"
273
+ r"import|export|from|default|"
274
+ r"typeof|instanceof|in|of"
275
+ r")\b"
276
+ )
277
+
278
+ for match in keyword_re.finditer(source):
279
+
280
+ text.tag_add(
281
+ "js_keyword",
282
+ position(offset, match.start()),
283
+ position(offset, match.end())
284
+ )
285
+
286
+ # --------------------------------------------------------
287
+ # Objets JavaScript courants
288
+ # --------------------------------------------------------
289
+
290
+ object_re = re.compile(
291
+ r"\b("
292
+ r"console|window|document|Math|JSON|Array|Object|"
293
+ r"String|Number|Boolean|Date|Promise|"
294
+ r"RegExp|Set|Map|Error"
295
+ r")\b"
296
+ )
297
+
298
+ for match in object_re.finditer(source):
299
+
300
+ text.tag_add(
301
+ "js_object",
302
+ position(offset, match.start()),
303
+ position(offset, match.end())
304
+ )
305
+
306
+ # --------------------------------------------------------
307
+ # Méthodes après un point
308
+ #
309
+ # console.log()
310
+ # document.getElementById()
311
+ # Math.random()
312
+ # --------------------------------------------------------
313
+
314
+ method_re = re.compile(
315
+ r"\.([A-Za-z_$][\w$]*)"
316
+ )
317
+
318
+ for match in method_re.finditer(source):
319
+
320
+ text.tag_add(
321
+ "js_method",
322
+ position(offset, match.start(1)),
323
+ position(offset, match.end(1))
324
+ )
325
+
326
+ # --------------------------------------------------------
327
+ # Nombres
328
+ # --------------------------------------------------------
329
+
330
+ number_re = re.compile(
331
+ r"\b\d+(?:\.\d+)?\b"
332
+ )
333
+
334
+ for match in number_re.finditer(source):
335
+
336
+ text.tag_add(
337
+ "js_number",
338
+ position(offset, match.start()),
339
+ position(offset, match.end())
340
+ )
341
+
342
+
343
+ # ============================================================
344
+ # Configuration des couleurs
345
+ # ============================================================
346
+
347
+ def configure_tags(text):
348
+
349
+ # --------------------------------------------------------
350
+ # HTML
351
+ # --------------------------------------------------------
352
+
353
+ text.tag_configure(
354
+ "html_tag",
355
+ foreground="#800080"
356
+ )
357
+
358
+ text.tag_configure(
359
+ "html_tag_name",
360
+ foreground="#0000FF"
361
+ )
362
+
363
+ text.tag_configure(
364
+ "html_attribute",
365
+ foreground="#008000"
366
+ )
367
+
368
+ text.tag_configure(
369
+ "html_value",
370
+ foreground="#A31515"
371
+ )
372
+
373
+ text.tag_configure(
374
+ "html_comment",
375
+ foreground="#808080"
376
+ )
377
+
378
+ # --------------------------------------------------------
379
+ # CSS
380
+ # --------------------------------------------------------
381
+
382
+ text.tag_configure(
383
+ "css_selector",
384
+ foreground="#800080"
385
+ )
386
+
387
+ text.tag_configure(
388
+ "css_property",
389
+ foreground="#0000FF"
390
+ )
391
+
392
+ text.tag_configure(
393
+ "css_value",
394
+ foreground="#A31515"
395
+ )
396
+
397
+ text.tag_configure(
398
+ "css_comment",
399
+ foreground="#808080"
400
+ )
401
+
402
+ # --------------------------------------------------------
403
+ # JavaScript
404
+ # --------------------------------------------------------
405
+
406
+ text.tag_configure(
407
+ "js_keyword",
408
+ foreground="#0000FF"
409
+ )
410
+
411
+ text.tag_configure(
412
+ "js_string",
413
+ foreground="#A31515"
414
+ )
415
+
416
+ text.tag_configure(
417
+ "js_comment",
418
+ foreground="#808080"
419
+ )
420
+
421
+ text.tag_configure(
422
+ "js_object",
423
+ foreground="#008080"
424
+ )
425
+
426
+ text.tag_configure(
427
+ "js_method",
428
+ foreground="#AA5500"
429
+ )
430
+
431
+ text.tag_configure(
432
+ "js_number",
433
+ foreground="#008080"
434
+ )
435
+
436
+
437
+ # ============================================================
438
+ # Suppression des anciennes couleurs
439
+ # ============================================================
440
+
441
+ def clear_tags(text):
442
+
443
+ tags = [
444
+ "html_tag",
445
+ "html_tag_name",
446
+ "html_attribute",
447
+ "html_value",
448
+ "html_comment",
449
+
450
+ "css_selector",
451
+ "css_property",
452
+ "css_value",
453
+ "css_comment",
454
+
455
+ "js_keyword",
456
+ "js_string",
457
+ "js_comment",
458
+ "js_object",
459
+ "js_method",
460
+ "js_number",
461
+ ]
462
+
463
+ for tag in tags:
464
+
465
+ text.tag_remove(
466
+ tag,
467
+ "1.0",
468
+ "end"
469
+ )
470
+
471
+
472
+ # ============================================================
473
+ # Coloration complète
474
+ # ============================================================
475
+
476
+ def highlight_html(text):
477
+
478
+ try:
479
+
480
+ source = text.get(
481
+ "1.0",
482
+ "end-1c"
483
+ )
484
+
485
+ clear_tags(text)
486
+
487
+ configure_tags(text)
488
+
489
+ # ----------------------------------------------------
490
+ # HTML
491
+ # ----------------------------------------------------
492
+
493
+ highlight_html_part(
494
+ text,
495
+ source,
496
+ 0
497
+ )
498
+
499
+ # ----------------------------------------------------
500
+ # CSS
501
+ # ----------------------------------------------------
502
+
503
+ for match in STYLE_RE.finditer(source):
504
+
505
+ css_start = match.start(1)
506
+ css_source = match.group(1)
507
+
508
+ highlight_css(
509
+ text,
510
+ css_source,
511
+ css_start
512
+ )
513
+
514
+ # ----------------------------------------------------
515
+ # JavaScript
516
+ # ----------------------------------------------------
517
+
518
+ for match in SCRIPT_RE.finditer(source):
519
+
520
+ js_start = match.start(1)
521
+ js_source = match.group(1)
522
+
523
+ highlight_javascript(
524
+ text,
525
+ js_source,
526
+ js_start
527
+ )
528
+
529
+ except TclError:
530
+ # L'éditeur peut être détruit pendant le changement
531
+ pass
532
+
533
+
534
+ # ============================================================
535
+ # Événement modification du texte
536
+ # ============================================================
537
+
538
+ def on_text_change(event):
539
+
540
+ text = event.widget
541
+
542
+ # Évite de traiter autre chose qu'un éditeur Thonny
543
+ try:
544
+ if not text.winfo_exists():
545
+ return
546
+
547
+ highlight_html(text)
548
+
549
+ except TclError:
550
+ pass
551
+
552
+
553
+ # ============================================================
554
+ # Chargement du plugin
555
+ # ============================================================
556
+
557
+ def load_plugin():
558
+
559
+ workbench = get_workbench()
560
+
561
+ # Éditeurs de code Thonny
562
+ workbench.bind_class(
563
+ "EditorCodeViewText",
564
+ "<<TextChange>>",
565
+ on_text_change,
566
+ True
567
+ )
568
+
569
+ workbench.bind_class(
570
+ "EditorCodeViewText",
571
+ "<FocusIn>",
572
+ on_text_change,
573
+ True
574
+ )