@particle-academy/fancy-code 0.3.0 → 0.4.0
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.
- package/README.md +27 -1
- package/dist/index.cjs +381 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +381 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,10 +201,12 @@ function RunButton() {
|
|
|
201
201
|
| TypeScript | `ts`, `typescript`, `tsx` |
|
|
202
202
|
| HTML | `html`, `htm` |
|
|
203
203
|
| PHP | `php` |
|
|
204
|
+
| Python | `py`, `python` |
|
|
205
|
+
| Go | `go`, `golang` |
|
|
204
206
|
|
|
205
207
|
## Custom Language Registration
|
|
206
208
|
|
|
207
|
-
Add languages beyond the
|
|
209
|
+
Add languages beyond the built-ins using `registerLanguage`:
|
|
208
210
|
|
|
209
211
|
```tsx
|
|
210
212
|
import { registerLanguage } from "@particle-academy/fancy-code";
|
|
@@ -354,6 +356,30 @@ export { registerTheme, getTheme, getRegisteredThemes };
|
|
|
354
356
|
export type { ThemeDefinition };
|
|
355
357
|
```
|
|
356
358
|
|
|
359
|
+
## Related: TreeNav from react-fancy
|
|
360
|
+
|
|
361
|
+
For IDE-style layouts, pair `CodeEditor` with the `TreeNav` component from `@particle-academy/react-fancy`. TreeNav provides hierarchical file/folder navigation with expand/collapse, selection, and extension-based file icons.
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
import { TreeNav } from "@particle-academy/react-fancy";
|
|
365
|
+
import { CodeEditor } from "@particle-academy/fancy-code";
|
|
366
|
+
|
|
367
|
+
<div className="flex" style={{ height: 600 }}>
|
|
368
|
+
<TreeNav
|
|
369
|
+
nodes={fileTree}
|
|
370
|
+
selectedId={activeFile}
|
|
371
|
+
onSelect={(id, node) => openFile(id, node)}
|
|
372
|
+
/>
|
|
373
|
+
<CodeEditor value={code} onChange={setCode} language={lang}>
|
|
374
|
+
<CodeEditor.Toolbar />
|
|
375
|
+
<CodeEditor.Panel />
|
|
376
|
+
<CodeEditor.StatusBar />
|
|
377
|
+
</CodeEditor>
|
|
378
|
+
</div>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
See the full IDE demo at `/react-demos/ide` in the monorepo.
|
|
382
|
+
|
|
357
383
|
## Demo Pages
|
|
358
384
|
|
|
359
385
|
The demo page lives in the monorepo at `resources/js/react-demos/pages/CodeEditorDemo.tsx` and is accessible at `/react-demos/code-editor`. It demonstrates:
|
package/dist/index.cjs
CHANGED
|
@@ -662,6 +662,377 @@ var tokenizePhp = (source) => {
|
|
|
662
662
|
return tokens;
|
|
663
663
|
};
|
|
664
664
|
|
|
665
|
+
// src/engine/tokenizers/python.ts
|
|
666
|
+
var KEYWORDS2 = /* @__PURE__ */ new Set([
|
|
667
|
+
"and",
|
|
668
|
+
"as",
|
|
669
|
+
"assert",
|
|
670
|
+
"async",
|
|
671
|
+
"await",
|
|
672
|
+
"break",
|
|
673
|
+
"class",
|
|
674
|
+
"continue",
|
|
675
|
+
"def",
|
|
676
|
+
"del",
|
|
677
|
+
"elif",
|
|
678
|
+
"else",
|
|
679
|
+
"except",
|
|
680
|
+
"finally",
|
|
681
|
+
"for",
|
|
682
|
+
"from",
|
|
683
|
+
"global",
|
|
684
|
+
"if",
|
|
685
|
+
"import",
|
|
686
|
+
"in",
|
|
687
|
+
"is",
|
|
688
|
+
"lambda",
|
|
689
|
+
"nonlocal",
|
|
690
|
+
"not",
|
|
691
|
+
"or",
|
|
692
|
+
"pass",
|
|
693
|
+
"raise",
|
|
694
|
+
"return",
|
|
695
|
+
"try",
|
|
696
|
+
"while",
|
|
697
|
+
"with",
|
|
698
|
+
"yield",
|
|
699
|
+
"True",
|
|
700
|
+
"False",
|
|
701
|
+
"None"
|
|
702
|
+
]);
|
|
703
|
+
var TYPES = /* @__PURE__ */ new Set([
|
|
704
|
+
"int",
|
|
705
|
+
"str",
|
|
706
|
+
"float",
|
|
707
|
+
"bool",
|
|
708
|
+
"list",
|
|
709
|
+
"dict",
|
|
710
|
+
"tuple",
|
|
711
|
+
"set",
|
|
712
|
+
"bytes",
|
|
713
|
+
"bytearray",
|
|
714
|
+
"memoryview",
|
|
715
|
+
"range",
|
|
716
|
+
"frozenset",
|
|
717
|
+
"complex",
|
|
718
|
+
"type",
|
|
719
|
+
"object",
|
|
720
|
+
"property",
|
|
721
|
+
"classmethod",
|
|
722
|
+
"staticmethod",
|
|
723
|
+
"Any",
|
|
724
|
+
"Optional",
|
|
725
|
+
"Union",
|
|
726
|
+
"Callable",
|
|
727
|
+
"List",
|
|
728
|
+
"Dict",
|
|
729
|
+
"Tuple",
|
|
730
|
+
"Set",
|
|
731
|
+
"Sequence",
|
|
732
|
+
"Mapping",
|
|
733
|
+
"Iterator",
|
|
734
|
+
"Generator",
|
|
735
|
+
"Coroutine"
|
|
736
|
+
]);
|
|
737
|
+
var tokenizePython = (source) => {
|
|
738
|
+
const tokens = [];
|
|
739
|
+
const len = source.length;
|
|
740
|
+
let i = 0;
|
|
741
|
+
while (i < len) {
|
|
742
|
+
const ch = source[i];
|
|
743
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
744
|
+
i++;
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (ch === "#") {
|
|
748
|
+
const pos = i;
|
|
749
|
+
i++;
|
|
750
|
+
while (i < len && source[i] !== "\n") i++;
|
|
751
|
+
tokens.push({ type: "comment", start: pos, end: i });
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (ch === "@" && i + 1 < len && /[a-zA-Z_]/.test(source[i + 1])) {
|
|
755
|
+
const pos = i;
|
|
756
|
+
i++;
|
|
757
|
+
while (i < len && /[a-zA-Z0-9_.]/.test(source[i])) i++;
|
|
758
|
+
tokens.push({ type: "keyword", start: pos, end: i });
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (ch === '"' || ch === "'" || (ch === "f" || ch === "r" || ch === "b" || ch === "F" || ch === "R" || ch === "B") && (source[i + 1] === '"' || source[i + 1] === "'")) {
|
|
762
|
+
const pos = i;
|
|
763
|
+
if (ch !== '"' && ch !== "'") i++;
|
|
764
|
+
const quote = source[i];
|
|
765
|
+
if (source[i + 1] === quote && source[i + 2] === quote) {
|
|
766
|
+
const triple = quote + quote + quote;
|
|
767
|
+
i += 3;
|
|
768
|
+
while (i < len && source.slice(i, i + 3) !== triple) {
|
|
769
|
+
if (source[i] === "\\") i++;
|
|
770
|
+
i++;
|
|
771
|
+
}
|
|
772
|
+
i += 3;
|
|
773
|
+
tokens.push({ type: "string", start: pos, end: i });
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
i++;
|
|
777
|
+
while (i < len && source[i] !== quote && source[i] !== "\n") {
|
|
778
|
+
if (source[i] === "\\") i++;
|
|
779
|
+
i++;
|
|
780
|
+
}
|
|
781
|
+
if (i < len && source[i] === quote) i++;
|
|
782
|
+
tokens.push({ type: "string", start: pos, end: i });
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (ch >= "0" && ch <= "9" || ch === "." && i + 1 < len && source[i + 1] >= "0" && source[i + 1] <= "9") {
|
|
786
|
+
const pos = i;
|
|
787
|
+
if (ch === "0" && (source[i + 1] === "x" || source[i + 1] === "X")) {
|
|
788
|
+
i += 2;
|
|
789
|
+
while (i < len && /[0-9a-fA-F_]/.test(source[i])) i++;
|
|
790
|
+
} else if (ch === "0" && (source[i + 1] === "b" || source[i + 1] === "B")) {
|
|
791
|
+
i += 2;
|
|
792
|
+
while (i < len && /[01_]/.test(source[i])) i++;
|
|
793
|
+
} else if (ch === "0" && (source[i + 1] === "o" || source[i + 1] === "O")) {
|
|
794
|
+
i += 2;
|
|
795
|
+
while (i < len && /[0-7_]/.test(source[i])) i++;
|
|
796
|
+
} else {
|
|
797
|
+
while (i < len && /[0-9_.]/.test(source[i])) i++;
|
|
798
|
+
if (i < len && (source[i] === "e" || source[i] === "E")) {
|
|
799
|
+
i++;
|
|
800
|
+
if (i < len && (source[i] === "+" || source[i] === "-")) i++;
|
|
801
|
+
while (i < len && source[i] >= "0" && source[i] <= "9") i++;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (i < len && source[i] === "j") i++;
|
|
805
|
+
tokens.push({ type: "number", start: pos, end: i });
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
809
|
+
const pos = i;
|
|
810
|
+
i++;
|
|
811
|
+
while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
|
|
812
|
+
const word = source.slice(pos, i);
|
|
813
|
+
let j = i;
|
|
814
|
+
while (j < len && (source[j] === " " || source[j] === " ")) j++;
|
|
815
|
+
if (KEYWORDS2.has(word)) {
|
|
816
|
+
tokens.push({ type: "keyword", start: pos, end: i });
|
|
817
|
+
} else if (TYPES.has(word)) {
|
|
818
|
+
tokens.push({ type: "type", start: pos, end: i });
|
|
819
|
+
} else if (source[j] === "(") {
|
|
820
|
+
tokens.push({ type: "function", start: pos, end: i });
|
|
821
|
+
} else if (word[0] >= "A" && word[0] <= "Z") {
|
|
822
|
+
tokens.push({ type: "type", start: pos, end: i });
|
|
823
|
+
} else {
|
|
824
|
+
tokens.push({ type: "variable", start: pos, end: i });
|
|
825
|
+
}
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
if ("+-*/%=<>!&|^~:@".includes(ch)) {
|
|
829
|
+
const pos = i;
|
|
830
|
+
i++;
|
|
831
|
+
while (i < len && "+-*/%=<>!&|^~:".includes(source[i])) i++;
|
|
832
|
+
tokens.push({ type: "operator", start: pos, end: i });
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if ("()[]{},.;\\".includes(ch)) {
|
|
836
|
+
tokens.push({ type: "punctuation", start: i, end: i + 1 });
|
|
837
|
+
i++;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
tokens.push({ type: "plain", start: i, end: i + 1 });
|
|
841
|
+
i++;
|
|
842
|
+
}
|
|
843
|
+
return tokens;
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// src/engine/tokenizers/go.ts
|
|
847
|
+
var KEYWORDS3 = /* @__PURE__ */ new Set([
|
|
848
|
+
"break",
|
|
849
|
+
"case",
|
|
850
|
+
"chan",
|
|
851
|
+
"const",
|
|
852
|
+
"continue",
|
|
853
|
+
"default",
|
|
854
|
+
"defer",
|
|
855
|
+
"else",
|
|
856
|
+
"fallthrough",
|
|
857
|
+
"for",
|
|
858
|
+
"func",
|
|
859
|
+
"go",
|
|
860
|
+
"goto",
|
|
861
|
+
"if",
|
|
862
|
+
"import",
|
|
863
|
+
"interface",
|
|
864
|
+
"map",
|
|
865
|
+
"package",
|
|
866
|
+
"range",
|
|
867
|
+
"return",
|
|
868
|
+
"select",
|
|
869
|
+
"struct",
|
|
870
|
+
"switch",
|
|
871
|
+
"type",
|
|
872
|
+
"var",
|
|
873
|
+
"nil",
|
|
874
|
+
"true",
|
|
875
|
+
"false",
|
|
876
|
+
"iota"
|
|
877
|
+
]);
|
|
878
|
+
var TYPES2 = /* @__PURE__ */ new Set([
|
|
879
|
+
"bool",
|
|
880
|
+
"byte",
|
|
881
|
+
"complex64",
|
|
882
|
+
"complex128",
|
|
883
|
+
"error",
|
|
884
|
+
"float32",
|
|
885
|
+
"float64",
|
|
886
|
+
"int",
|
|
887
|
+
"int8",
|
|
888
|
+
"int16",
|
|
889
|
+
"int32",
|
|
890
|
+
"int64",
|
|
891
|
+
"rune",
|
|
892
|
+
"string",
|
|
893
|
+
"uint",
|
|
894
|
+
"uint8",
|
|
895
|
+
"uint16",
|
|
896
|
+
"uint32",
|
|
897
|
+
"uint64",
|
|
898
|
+
"uintptr",
|
|
899
|
+
"any"
|
|
900
|
+
]);
|
|
901
|
+
var BUILTINS = /* @__PURE__ */ new Set([
|
|
902
|
+
"make",
|
|
903
|
+
"len",
|
|
904
|
+
"cap",
|
|
905
|
+
"new",
|
|
906
|
+
"append",
|
|
907
|
+
"copy",
|
|
908
|
+
"close",
|
|
909
|
+
"delete",
|
|
910
|
+
"complex",
|
|
911
|
+
"real",
|
|
912
|
+
"imag",
|
|
913
|
+
"panic",
|
|
914
|
+
"recover",
|
|
915
|
+
"print",
|
|
916
|
+
"println"
|
|
917
|
+
]);
|
|
918
|
+
var tokenizeGo = (source) => {
|
|
919
|
+
const tokens = [];
|
|
920
|
+
const len = source.length;
|
|
921
|
+
let i = 0;
|
|
922
|
+
while (i < len) {
|
|
923
|
+
const ch = source[i];
|
|
924
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
925
|
+
i++;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
929
|
+
const pos = i;
|
|
930
|
+
i += 2;
|
|
931
|
+
while (i < len && source[i] !== "\n") i++;
|
|
932
|
+
tokens.push({ type: "comment", start: pos, end: i });
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
936
|
+
const pos = i;
|
|
937
|
+
i += 2;
|
|
938
|
+
while (i < len && !(source[i] === "*" && source[i + 1] === "/")) i++;
|
|
939
|
+
i += 2;
|
|
940
|
+
tokens.push({ type: "comment", start: pos, end: i });
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (ch === "`") {
|
|
944
|
+
const pos = i;
|
|
945
|
+
i++;
|
|
946
|
+
while (i < len && source[i] !== "`") i++;
|
|
947
|
+
i++;
|
|
948
|
+
tokens.push({ type: "string", start: pos, end: i });
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (ch === '"') {
|
|
952
|
+
const pos = i;
|
|
953
|
+
i++;
|
|
954
|
+
while (i < len && source[i] !== '"' && source[i] !== "\n") {
|
|
955
|
+
if (source[i] === "\\") i++;
|
|
956
|
+
i++;
|
|
957
|
+
}
|
|
958
|
+
if (i < len && source[i] === '"') i++;
|
|
959
|
+
tokens.push({ type: "string", start: pos, end: i });
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (ch === "'") {
|
|
963
|
+
const pos = i;
|
|
964
|
+
i++;
|
|
965
|
+
while (i < len && source[i] !== "'" && source[i] !== "\n") {
|
|
966
|
+
if (source[i] === "\\") i++;
|
|
967
|
+
i++;
|
|
968
|
+
}
|
|
969
|
+
if (i < len && source[i] === "'") i++;
|
|
970
|
+
tokens.push({ type: "string", start: pos, end: i });
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (ch >= "0" && ch <= "9" || ch === "." && i + 1 < len && source[i + 1] >= "0" && source[i + 1] <= "9") {
|
|
974
|
+
const pos = i;
|
|
975
|
+
if (ch === "0" && (source[i + 1] === "x" || source[i + 1] === "X")) {
|
|
976
|
+
i += 2;
|
|
977
|
+
while (i < len && /[0-9a-fA-F_]/.test(source[i])) i++;
|
|
978
|
+
} else if (ch === "0" && (source[i + 1] === "b" || source[i + 1] === "B")) {
|
|
979
|
+
i += 2;
|
|
980
|
+
while (i < len && /[01_]/.test(source[i])) i++;
|
|
981
|
+
} else if (ch === "0" && (source[i + 1] === "o" || source[i + 1] === "O")) {
|
|
982
|
+
i += 2;
|
|
983
|
+
while (i < len && /[0-7_]/.test(source[i])) i++;
|
|
984
|
+
} else {
|
|
985
|
+
while (i < len && /[0-9_.]/.test(source[i])) i++;
|
|
986
|
+
if (i < len && (source[i] === "e" || source[i] === "E")) {
|
|
987
|
+
i++;
|
|
988
|
+
if (i < len && (source[i] === "+" || source[i] === "-")) i++;
|
|
989
|
+
while (i < len && source[i] >= "0" && source[i] <= "9") i++;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (i < len && source[i] === "i") i++;
|
|
993
|
+
tokens.push({ type: "number", start: pos, end: i });
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
997
|
+
const pos = i;
|
|
998
|
+
i++;
|
|
999
|
+
while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
|
|
1000
|
+
const word = source.slice(pos, i);
|
|
1001
|
+
let j = i;
|
|
1002
|
+
while (j < len && (source[j] === " " || source[j] === " ")) j++;
|
|
1003
|
+
if (KEYWORDS3.has(word)) {
|
|
1004
|
+
tokens.push({ type: "keyword", start: pos, end: i });
|
|
1005
|
+
} else if (TYPES2.has(word)) {
|
|
1006
|
+
tokens.push({ type: "type", start: pos, end: i });
|
|
1007
|
+
} else if (BUILTINS.has(word) && source[j] === "(") {
|
|
1008
|
+
tokens.push({ type: "function", start: pos, end: i });
|
|
1009
|
+
} else if (source[j] === "(") {
|
|
1010
|
+
tokens.push({ type: "function", start: pos, end: i });
|
|
1011
|
+
} else if (word[0] >= "A" && word[0] <= "Z") {
|
|
1012
|
+
tokens.push({ type: "type", start: pos, end: i });
|
|
1013
|
+
} else {
|
|
1014
|
+
tokens.push({ type: "variable", start: pos, end: i });
|
|
1015
|
+
}
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if ("+-*/%=<>!&|^~:".includes(ch)) {
|
|
1019
|
+
const pos = i;
|
|
1020
|
+
i++;
|
|
1021
|
+
while (i < len && "+-*/%=<>!&|^~:".includes(source[i])) i++;
|
|
1022
|
+
tokens.push({ type: "operator", start: pos, end: i });
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if ("(){}[];,.".includes(ch)) {
|
|
1026
|
+
tokens.push({ type: "punctuation", start: i, end: i + 1 });
|
|
1027
|
+
i++;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
tokens.push({ type: "plain", start: i, end: i + 1 });
|
|
1031
|
+
i++;
|
|
1032
|
+
}
|
|
1033
|
+
return tokens;
|
|
1034
|
+
};
|
|
1035
|
+
|
|
665
1036
|
// src/languages/builtin.ts
|
|
666
1037
|
registerLanguage({
|
|
667
1038
|
name: "JavaScript",
|
|
@@ -684,6 +1055,16 @@ registerLanguage({
|
|
|
684
1055
|
aliases: ["php"],
|
|
685
1056
|
tokenize: tokenizePhp
|
|
686
1057
|
});
|
|
1058
|
+
registerLanguage({
|
|
1059
|
+
name: "Python",
|
|
1060
|
+
aliases: ["py", "python"],
|
|
1061
|
+
tokenize: tokenizePython
|
|
1062
|
+
});
|
|
1063
|
+
registerLanguage({
|
|
1064
|
+
name: "Go",
|
|
1065
|
+
aliases: ["go", "golang"],
|
|
1066
|
+
tokenize: tokenizeGo
|
|
1067
|
+
});
|
|
687
1068
|
var iconBtnClass = "inline-flex items-center justify-center rounded-md p-1 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-200";
|
|
688
1069
|
function LanguageSelector() {
|
|
689
1070
|
const { language, setLanguage } = useCodeEditor();
|