-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathcircuitpython_highlight.js
More file actions
221 lines (213 loc) · 6.73 KB
/
Copy pathcircuitpython_highlight.js
File metadata and controls
221 lines (213 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// CircuitPython syntax highlighting overlay for CodeMirror 6.
//
// CodeMirror 6 dropped the simple `extra_keywords` mechanism that CM5 had,
// so instead of forking @codemirror/lang-python we layer extra decorations
// on top of the existing Python syntax tree. We walk the tree inside the
// viewport, find identifier nodes whose text matches a CircuitPython name,
// and tag them with a CSS class that the theme can style.
import { ViewPlugin, Decoration } from "@codemirror/view";
import { syntaxTree } from "@codemirror/language";
import { Prec } from "@codemirror/state";
// Core/built-in CircuitPython modules. These are the identifiers that show
// up in `import foo` / `from foo import ...` inside CircuitPython code.
//
// Sourced from the upstream `shared-bindings/` directory in
// adafruit/circuitpython, plus port-specific bindings that are widely used
// (espidf/espnow/espulp on ESP, picodvi/rp2pio on RP2). Standard-Python
// modules that CircuitPython also exposes (math, os, time, random, struct,
// hashlib, ipaddress, locale, __future__) are intentionally omitted — they
// aren't CircuitPython-specific and highlighting them as such would be
// noisy in regular Python code shown in the editor.
//
// Underscore-prefixed internal bindings (_bleio, _eve, _pew, _pixelmap,
// _stage) are also omitted; users access those via the corresponding
// `adafruit_*` libraries which are matched by the prefix wildcard below.
//
// Third-party Adafruit libraries are matched by the `adafruit_` prefix
// instead of being listed individually, and community-bundle libraries by
// the `circuitpython_` prefix, so this set only needs updating when a new
// shared binding lands upstream.
const CIRCUITPYTHON_CORE_MODULES = new Set([
"aesio",
"alarm",
"analogbufio",
"analogio",
"atexit",
"audiobusio",
"audiocore",
"audiodelays",
"audiofilters",
"audiofreeverb",
"audioio",
"audiomixer",
"audiomp3",
"audiopwmio",
"audiospeed",
"aurora_epaper",
"bitbangio",
"bitmapfilter",
"bitmaptools",
"bitops",
"board",
"busdisplay",
"busio",
"camera",
"canio",
"codeop",
"countio",
"digitalio",
"displayio",
"dotclockframebuffer",
"dualbank",
"epaperdisplay",
"espidf",
"espnow",
"espulp",
"floppyio",
"fontio",
"fourwire",
"framebufferio",
"frequencyio",
"getpass",
"gifio",
"gnss",
"i2cdisplaybus",
"i2cioexpander",
"i2ctarget",
"imagecapture",
"is31fl3741",
"jpegio",
"keypad",
"keypad_demux",
"lvfontio",
"max3421e",
"mcp4822",
"mdns",
"memorymap",
"memorymonitor",
"microcontroller",
"mipidsi",
"msgpack",
"neopixel_write",
"nvm",
"onewireio",
"paralleldisplaybus",
"picodvi",
"ps2io",
"pulseio",
"pwmio",
"qrio",
"qspibus",
"rainbowio",
"rclcpy",
"rgbmatrix",
"rotaryio",
"rp2pio",
"rtc",
"sdcardio",
"sdioio",
"sharpdisplay",
"socketpool",
"spitarget",
"ssl",
"storage",
"supervisor",
"synthio",
"terminalio",
"tilepalettemapper",
"touchio",
"traceback",
"uheap",
"ulab",
"usb",
"usb_cdc",
"usb_hid",
"usb_host",
"usb_midi",
"usb_video",
"ustack",
"vectorio",
"warnings",
"watchdog",
"wifi",
"zlib",
// Early Adafruit-maintained libraries that predate the `adafruit_`
// naming convention and shipped without a prefix. Listed explicitly
// because the prefix wildcard below can't catch them.
"neopixel",
"simpleio",
]);
// Returns true when `name` is a CircuitPython module worth highlighting.
// Wildcard-matches anything starting with `adafruit_` (Adafruit-maintained
// libraries) or `circuitpython_` (community bundle libraries) so new
// libraries light up automatically without touching this file. Both
// prefixes are distinctive enough that false positives against ordinary
// Python code are essentially nil.
function isCircuitPythonModule(name) {
if (CIRCUITPYTHON_CORE_MODULES.has(name)) return true;
if (name.startsWith("adafruit_") && name.length > "adafruit_".length) {
return true;
}
if (
name.startsWith("circuitpython_") &&
name.length > "circuitpython_".length
) {
return true;
}
return false;
}
const moduleMark = Decoration.mark({ class: "tok-cp-module" });
// Build the decoration set for the part of the document currently visible.
// Walking only visible ranges keeps this cheap on big files.
function buildDecorations(view) {
const builder = [];
for (const { from, to } of view.visibleRanges) {
syntaxTree(view.state).iterate({
from,
to,
enter(node) {
// We care about identifier-like leaves only. Lezer Python emits
// `VariableName` for bare identifiers (including module names
// in `import foo` and `from foo import ...`). Module names
// accessed as attributes (e.g. `adafruit_io.MQTT`) come in as
// `VariableName` for the leftmost part, then `PropertyName`
// children — we only mark the root reference.
if (node.name !== "VariableName") return;
const text = view.state.doc.sliceString(node.from, node.to);
if (isCircuitPythonModule(text)) {
builder.push(moduleMark.range(node.from, node.to));
}
},
});
}
// Decoration ranges must be sorted by `from`, which they already are
// because we iterate the tree in document order.
return Decoration.set(builder);
}
// ViewPlugin keeps decorations in sync with viewport / document changes.
const circuitpythonHighlightPlugin = ViewPlugin.fromClass(
class {
constructor(view) {
this.decorations = buildDecorations(view);
}
update(update) {
if (
update.docChanged ||
update.viewportChanged ||
syntaxTree(update.startState) !== syntaxTree(update.state)
) {
this.decorations = buildDecorations(update.view);
}
}
},
{
decorations: (v) => v.decorations,
},
);
// Wrap the plugin with Prec.highest so its decoration nests inside the
// classHighlighter span. CodeMirror renders overlapping mark decorations
// as nested spans where higher-precedence decorations end up closer to
// the text. The inner span’s `color` is what the user sees, so making
// `tok-cp-module` the inner class is what lets our pink override the
// underlying `tok-variableName` blue without resorting to !important.
export const circuitpythonHighlight = Prec.highest(circuitpythonHighlightPlugin);