@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 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 four built-ins using `registerLanguage`:
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();