@particle-academy/fancy-code 0.3.1 → 0.4.1

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";
package/dist/index.cjs CHANGED
@@ -110,10 +110,11 @@ function CodeEditorPanel({ className }) {
110
110
  "textarea",
111
111
  {
112
112
  ref: textareaRef,
113
- className: "relative m-0 block w-full resize-none overflow-hidden border-none bg-transparent p-2.5 text-[13px] leading-[1.5] text-transparent outline-none",
113
+ className: "relative m-0 block w-full resize-none border-none bg-transparent p-2.5 text-[13px] leading-[1.5] text-transparent outline-none",
114
114
  style: {
115
115
  caretColor: themeColors.cursorColor,
116
116
  minHeight: _minHeight ? _minHeight - 40 : 80,
117
+ overflow: "hidden",
117
118
  whiteSpace: wordWrap ? "pre-wrap" : "pre",
118
119
  overflowWrap: wordWrap ? "break-word" : "normal"
119
120
  },
@@ -662,6 +663,377 @@ var tokenizePhp = (source) => {
662
663
  return tokens;
663
664
  };
664
665
 
666
+ // src/engine/tokenizers/python.ts
667
+ var KEYWORDS2 = /* @__PURE__ */ new Set([
668
+ "and",
669
+ "as",
670
+ "assert",
671
+ "async",
672
+ "await",
673
+ "break",
674
+ "class",
675
+ "continue",
676
+ "def",
677
+ "del",
678
+ "elif",
679
+ "else",
680
+ "except",
681
+ "finally",
682
+ "for",
683
+ "from",
684
+ "global",
685
+ "if",
686
+ "import",
687
+ "in",
688
+ "is",
689
+ "lambda",
690
+ "nonlocal",
691
+ "not",
692
+ "or",
693
+ "pass",
694
+ "raise",
695
+ "return",
696
+ "try",
697
+ "while",
698
+ "with",
699
+ "yield",
700
+ "True",
701
+ "False",
702
+ "None"
703
+ ]);
704
+ var TYPES = /* @__PURE__ */ new Set([
705
+ "int",
706
+ "str",
707
+ "float",
708
+ "bool",
709
+ "list",
710
+ "dict",
711
+ "tuple",
712
+ "set",
713
+ "bytes",
714
+ "bytearray",
715
+ "memoryview",
716
+ "range",
717
+ "frozenset",
718
+ "complex",
719
+ "type",
720
+ "object",
721
+ "property",
722
+ "classmethod",
723
+ "staticmethod",
724
+ "Any",
725
+ "Optional",
726
+ "Union",
727
+ "Callable",
728
+ "List",
729
+ "Dict",
730
+ "Tuple",
731
+ "Set",
732
+ "Sequence",
733
+ "Mapping",
734
+ "Iterator",
735
+ "Generator",
736
+ "Coroutine"
737
+ ]);
738
+ var tokenizePython = (source) => {
739
+ const tokens = [];
740
+ const len = source.length;
741
+ let i = 0;
742
+ while (i < len) {
743
+ const ch = source[i];
744
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
745
+ i++;
746
+ continue;
747
+ }
748
+ if (ch === "#") {
749
+ const pos = i;
750
+ i++;
751
+ while (i < len && source[i] !== "\n") i++;
752
+ tokens.push({ type: "comment", start: pos, end: i });
753
+ continue;
754
+ }
755
+ if (ch === "@" && i + 1 < len && /[a-zA-Z_]/.test(source[i + 1])) {
756
+ const pos = i;
757
+ i++;
758
+ while (i < len && /[a-zA-Z0-9_.]/.test(source[i])) i++;
759
+ tokens.push({ type: "keyword", start: pos, end: i });
760
+ continue;
761
+ }
762
+ if (ch === '"' || ch === "'" || (ch === "f" || ch === "r" || ch === "b" || ch === "F" || ch === "R" || ch === "B") && (source[i + 1] === '"' || source[i + 1] === "'")) {
763
+ const pos = i;
764
+ if (ch !== '"' && ch !== "'") i++;
765
+ const quote = source[i];
766
+ if (source[i + 1] === quote && source[i + 2] === quote) {
767
+ const triple = quote + quote + quote;
768
+ i += 3;
769
+ while (i < len && source.slice(i, i + 3) !== triple) {
770
+ if (source[i] === "\\") i++;
771
+ i++;
772
+ }
773
+ i += 3;
774
+ tokens.push({ type: "string", start: pos, end: i });
775
+ continue;
776
+ }
777
+ i++;
778
+ while (i < len && source[i] !== quote && source[i] !== "\n") {
779
+ if (source[i] === "\\") i++;
780
+ i++;
781
+ }
782
+ if (i < len && source[i] === quote) i++;
783
+ tokens.push({ type: "string", start: pos, end: i });
784
+ continue;
785
+ }
786
+ if (ch >= "0" && ch <= "9" || ch === "." && i + 1 < len && source[i + 1] >= "0" && source[i + 1] <= "9") {
787
+ const pos = i;
788
+ if (ch === "0" && (source[i + 1] === "x" || source[i + 1] === "X")) {
789
+ i += 2;
790
+ while (i < len && /[0-9a-fA-F_]/.test(source[i])) i++;
791
+ } else if (ch === "0" && (source[i + 1] === "b" || source[i + 1] === "B")) {
792
+ i += 2;
793
+ while (i < len && /[01_]/.test(source[i])) i++;
794
+ } else if (ch === "0" && (source[i + 1] === "o" || source[i + 1] === "O")) {
795
+ i += 2;
796
+ while (i < len && /[0-7_]/.test(source[i])) i++;
797
+ } else {
798
+ while (i < len && /[0-9_.]/.test(source[i])) i++;
799
+ if (i < len && (source[i] === "e" || source[i] === "E")) {
800
+ i++;
801
+ if (i < len && (source[i] === "+" || source[i] === "-")) i++;
802
+ while (i < len && source[i] >= "0" && source[i] <= "9") i++;
803
+ }
804
+ }
805
+ if (i < len && source[i] === "j") i++;
806
+ tokens.push({ type: "number", start: pos, end: i });
807
+ continue;
808
+ }
809
+ if (/[a-zA-Z_]/.test(ch)) {
810
+ const pos = i;
811
+ i++;
812
+ while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
813
+ const word = source.slice(pos, i);
814
+ let j = i;
815
+ while (j < len && (source[j] === " " || source[j] === " ")) j++;
816
+ if (KEYWORDS2.has(word)) {
817
+ tokens.push({ type: "keyword", start: pos, end: i });
818
+ } else if (TYPES.has(word)) {
819
+ tokens.push({ type: "type", start: pos, end: i });
820
+ } else if (source[j] === "(") {
821
+ tokens.push({ type: "function", start: pos, end: i });
822
+ } else if (word[0] >= "A" && word[0] <= "Z") {
823
+ tokens.push({ type: "type", start: pos, end: i });
824
+ } else {
825
+ tokens.push({ type: "variable", start: pos, end: i });
826
+ }
827
+ continue;
828
+ }
829
+ if ("+-*/%=<>!&|^~:@".includes(ch)) {
830
+ const pos = i;
831
+ i++;
832
+ while (i < len && "+-*/%=<>!&|^~:".includes(source[i])) i++;
833
+ tokens.push({ type: "operator", start: pos, end: i });
834
+ continue;
835
+ }
836
+ if ("()[]{},.;\\".includes(ch)) {
837
+ tokens.push({ type: "punctuation", start: i, end: i + 1 });
838
+ i++;
839
+ continue;
840
+ }
841
+ tokens.push({ type: "plain", start: i, end: i + 1 });
842
+ i++;
843
+ }
844
+ return tokens;
845
+ };
846
+
847
+ // src/engine/tokenizers/go.ts
848
+ var KEYWORDS3 = /* @__PURE__ */ new Set([
849
+ "break",
850
+ "case",
851
+ "chan",
852
+ "const",
853
+ "continue",
854
+ "default",
855
+ "defer",
856
+ "else",
857
+ "fallthrough",
858
+ "for",
859
+ "func",
860
+ "go",
861
+ "goto",
862
+ "if",
863
+ "import",
864
+ "interface",
865
+ "map",
866
+ "package",
867
+ "range",
868
+ "return",
869
+ "select",
870
+ "struct",
871
+ "switch",
872
+ "type",
873
+ "var",
874
+ "nil",
875
+ "true",
876
+ "false",
877
+ "iota"
878
+ ]);
879
+ var TYPES2 = /* @__PURE__ */ new Set([
880
+ "bool",
881
+ "byte",
882
+ "complex64",
883
+ "complex128",
884
+ "error",
885
+ "float32",
886
+ "float64",
887
+ "int",
888
+ "int8",
889
+ "int16",
890
+ "int32",
891
+ "int64",
892
+ "rune",
893
+ "string",
894
+ "uint",
895
+ "uint8",
896
+ "uint16",
897
+ "uint32",
898
+ "uint64",
899
+ "uintptr",
900
+ "any"
901
+ ]);
902
+ var BUILTINS = /* @__PURE__ */ new Set([
903
+ "make",
904
+ "len",
905
+ "cap",
906
+ "new",
907
+ "append",
908
+ "copy",
909
+ "close",
910
+ "delete",
911
+ "complex",
912
+ "real",
913
+ "imag",
914
+ "panic",
915
+ "recover",
916
+ "print",
917
+ "println"
918
+ ]);
919
+ var tokenizeGo = (source) => {
920
+ const tokens = [];
921
+ const len = source.length;
922
+ let i = 0;
923
+ while (i < len) {
924
+ const ch = source[i];
925
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
926
+ i++;
927
+ continue;
928
+ }
929
+ if (ch === "/" && source[i + 1] === "/") {
930
+ const pos = i;
931
+ i += 2;
932
+ while (i < len && source[i] !== "\n") i++;
933
+ tokens.push({ type: "comment", start: pos, end: i });
934
+ continue;
935
+ }
936
+ if (ch === "/" && source[i + 1] === "*") {
937
+ const pos = i;
938
+ i += 2;
939
+ while (i < len && !(source[i] === "*" && source[i + 1] === "/")) i++;
940
+ i += 2;
941
+ tokens.push({ type: "comment", start: pos, end: i });
942
+ continue;
943
+ }
944
+ if (ch === "`") {
945
+ const pos = i;
946
+ i++;
947
+ while (i < len && source[i] !== "`") i++;
948
+ i++;
949
+ tokens.push({ type: "string", start: pos, end: i });
950
+ continue;
951
+ }
952
+ if (ch === '"') {
953
+ const pos = i;
954
+ i++;
955
+ while (i < len && source[i] !== '"' && source[i] !== "\n") {
956
+ if (source[i] === "\\") i++;
957
+ i++;
958
+ }
959
+ if (i < len && source[i] === '"') i++;
960
+ tokens.push({ type: "string", start: pos, end: i });
961
+ continue;
962
+ }
963
+ if (ch === "'") {
964
+ const pos = i;
965
+ i++;
966
+ while (i < len && source[i] !== "'" && source[i] !== "\n") {
967
+ if (source[i] === "\\") i++;
968
+ i++;
969
+ }
970
+ if (i < len && source[i] === "'") i++;
971
+ tokens.push({ type: "string", start: pos, end: i });
972
+ continue;
973
+ }
974
+ if (ch >= "0" && ch <= "9" || ch === "." && i + 1 < len && source[i + 1] >= "0" && source[i + 1] <= "9") {
975
+ const pos = i;
976
+ if (ch === "0" && (source[i + 1] === "x" || source[i + 1] === "X")) {
977
+ i += 2;
978
+ while (i < len && /[0-9a-fA-F_]/.test(source[i])) i++;
979
+ } else if (ch === "0" && (source[i + 1] === "b" || source[i + 1] === "B")) {
980
+ i += 2;
981
+ while (i < len && /[01_]/.test(source[i])) i++;
982
+ } else if (ch === "0" && (source[i + 1] === "o" || source[i + 1] === "O")) {
983
+ i += 2;
984
+ while (i < len && /[0-7_]/.test(source[i])) i++;
985
+ } else {
986
+ while (i < len && /[0-9_.]/.test(source[i])) i++;
987
+ if (i < len && (source[i] === "e" || source[i] === "E")) {
988
+ i++;
989
+ if (i < len && (source[i] === "+" || source[i] === "-")) i++;
990
+ while (i < len && source[i] >= "0" && source[i] <= "9") i++;
991
+ }
992
+ }
993
+ if (i < len && source[i] === "i") i++;
994
+ tokens.push({ type: "number", start: pos, end: i });
995
+ continue;
996
+ }
997
+ if (/[a-zA-Z_]/.test(ch)) {
998
+ const pos = i;
999
+ i++;
1000
+ while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
1001
+ const word = source.slice(pos, i);
1002
+ let j = i;
1003
+ while (j < len && (source[j] === " " || source[j] === " ")) j++;
1004
+ if (KEYWORDS3.has(word)) {
1005
+ tokens.push({ type: "keyword", start: pos, end: i });
1006
+ } else if (TYPES2.has(word)) {
1007
+ tokens.push({ type: "type", start: pos, end: i });
1008
+ } else if (BUILTINS.has(word) && source[j] === "(") {
1009
+ tokens.push({ type: "function", start: pos, end: i });
1010
+ } else if (source[j] === "(") {
1011
+ tokens.push({ type: "function", start: pos, end: i });
1012
+ } else if (word[0] >= "A" && word[0] <= "Z") {
1013
+ tokens.push({ type: "type", start: pos, end: i });
1014
+ } else {
1015
+ tokens.push({ type: "variable", start: pos, end: i });
1016
+ }
1017
+ continue;
1018
+ }
1019
+ if ("+-*/%=<>!&|^~:".includes(ch)) {
1020
+ const pos = i;
1021
+ i++;
1022
+ while (i < len && "+-*/%=<>!&|^~:".includes(source[i])) i++;
1023
+ tokens.push({ type: "operator", start: pos, end: i });
1024
+ continue;
1025
+ }
1026
+ if ("(){}[];,.".includes(ch)) {
1027
+ tokens.push({ type: "punctuation", start: i, end: i + 1 });
1028
+ i++;
1029
+ continue;
1030
+ }
1031
+ tokens.push({ type: "plain", start: i, end: i + 1 });
1032
+ i++;
1033
+ }
1034
+ return tokens;
1035
+ };
1036
+
665
1037
  // src/languages/builtin.ts
666
1038
  registerLanguage({
667
1039
  name: "JavaScript",
@@ -684,6 +1056,16 @@ registerLanguage({
684
1056
  aliases: ["php"],
685
1057
  tokenize: tokenizePhp
686
1058
  });
1059
+ registerLanguage({
1060
+ name: "Python",
1061
+ aliases: ["py", "python"],
1062
+ tokenize: tokenizePython
1063
+ });
1064
+ registerLanguage({
1065
+ name: "Go",
1066
+ aliases: ["go", "golang"],
1067
+ tokenize: tokenizeGo
1068
+ });
687
1069
  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
1070
  function LanguageSelector() {
689
1071
  const { language, setLanguage } = useCodeEditor();
@@ -946,14 +1328,23 @@ function useEditorEngine({
946
1328
  }
947
1329
  return count;
948
1330
  }, [value]);
1331
+ const autoResize = react.useCallback(() => {
1332
+ const ta = textareaRef.current;
1333
+ if (!ta) return;
1334
+ ta.style.height = "auto";
1335
+ ta.style.height = ta.scrollHeight + "px";
1336
+ }, []);
949
1337
  react.useEffect(() => {
950
1338
  const ta = textareaRef.current;
951
- if (!ta || ta.value === value) return;
952
- const { selectionStart, selectionEnd } = ta;
953
- ta.value = value;
954
- ta.selectionStart = selectionStart;
955
- ta.selectionEnd = selectionEnd;
956
- }, [value]);
1339
+ if (!ta) return;
1340
+ if (ta.value !== value) {
1341
+ const { selectionStart, selectionEnd } = ta;
1342
+ ta.value = value;
1343
+ ta.selectionStart = selectionStart;
1344
+ ta.selectionEnd = selectionEnd;
1345
+ }
1346
+ autoResize();
1347
+ }, [value, autoResize]);
957
1348
  const updateCursorInfo = react.useCallback(() => {
958
1349
  const ta = textareaRef.current;
959
1350
  if (!ta) return;
@@ -970,8 +1361,9 @@ function useEditorEngine({
970
1361
  const ta = textareaRef.current;
971
1362
  if (!ta) return;
972
1363
  onChangeRef.current?.(ta.value);
1364
+ autoResize();
973
1365
  updateCursorInfo();
974
- }, [updateCursorInfo]);
1366
+ }, [autoResize, updateCursorInfo]);
975
1367
  const handleSelect = react.useCallback(() => {
976
1368
  updateCursorInfo();
977
1369
  }, [updateCursorInfo]);
@@ -1031,6 +1423,7 @@ function useEditorEngine({
1031
1423
  ta.selectionStart = ta.selectionEnd = start + tabSize;
1032
1424
  onChangeRef.current?.(ta.value);
1033
1425
  }
1426
+ autoResize();
1034
1427
  updateCursorInfo();
1035
1428
  return;
1036
1429
  }
@@ -1049,11 +1442,12 @@ function useEditorEngine({
1049
1442
  ta.value = before + insertion + after;
1050
1443
  ta.selectionStart = ta.selectionEnd = start + insertion.length;
1051
1444
  onChangeRef.current?.(ta.value);
1445
+ autoResize();
1052
1446
  updateCursorInfo();
1053
1447
  return;
1054
1448
  }
1055
1449
  },
1056
- [readOnly, tabSize, updateCursorInfo]
1450
+ [readOnly, tabSize, autoResize, updateCursorInfo]
1057
1451
  );
1058
1452
  return {
1059
1453
  textareaRef,