@zhongqian97-code/ecode 0.2.4 → 0.2.5

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.
Files changed (2) hide show
  1. package/dist/index.js +199 -32
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -597,19 +597,45 @@ import { Box as Box3, Text as Text3, useInput } from "ink";
597
597
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
598
598
  var CURSOR_CHAR = "\u258C";
599
599
  var BLINK_INTERVAL_MS = 530;
600
+ function wordBackward(s, pos) {
601
+ let i = pos;
602
+ while (i > 0 && s[i - 1] === " ") {
603
+ i--;
604
+ }
605
+ while (i > 0 && s[i - 1] !== " ") {
606
+ i--;
607
+ }
608
+ return i;
609
+ }
610
+ function wordForward(s, pos) {
611
+ let i = pos;
612
+ const len = s.length;
613
+ while (i < len && s[i] !== " ") {
614
+ i++;
615
+ }
616
+ while (i < len && s[i] === " ") {
617
+ i++;
618
+ }
619
+ return i;
620
+ }
600
621
  var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placeholder }, ref) {
601
- const [lines, setLines] = useState2([""]);
622
+ const [value, setValue] = useState2("");
623
+ const [cursorPos, setCursorPos] = useState2(0);
602
624
  const [cursorVisible, setCursorVisible] = useState2(true);
603
- const linesRef = useRef(lines);
604
- linesRef.current = lines;
625
+ const valueRef = useRef(value);
626
+ valueRef.current = value;
627
+ const cursorPosRef = useRef(cursorPos);
628
+ cursorPosRef.current = cursorPos;
605
629
  const onChangeRef = useRef(onChange);
606
630
  onChangeRef.current = onChange;
607
631
  const onSubmitRef = useRef(onSubmit);
608
632
  onSubmitRef.current = onSubmit;
609
633
  useImperativeHandle(ref, () => ({
610
634
  fill(text) {
611
- const newLines = text ? text.split("\n") : [""];
612
- setLines(newLines);
635
+ valueRef.current = text;
636
+ cursorPosRef.current = text.length;
637
+ setValue(text);
638
+ setCursorPos(text.length);
613
639
  onChangeRef.current?.(text);
614
640
  }
615
641
  }));
@@ -625,49 +651,124 @@ var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placehold
625
651
  clearInterval(timer);
626
652
  };
627
653
  }, [isActive]);
654
+ function setValueSync(newValue) {
655
+ valueRef.current = newValue;
656
+ setValue(newValue);
657
+ }
658
+ function setCursorPosSync(newPos) {
659
+ cursorPosRef.current = newPos;
660
+ setCursorPos(newPos);
661
+ }
628
662
  useInput(
629
663
  (input, key) => {
630
- const currentLines = linesRef.current;
664
+ const v = valueRef.current;
665
+ const pos = cursorPosRef.current;
631
666
  if (key.return && key.shift) {
632
- const newLines = [...currentLines, ""];
633
- setLines(newLines);
634
- onChangeRef.current?.(newLines.join("\n"));
667
+ const newValue = v.slice(0, pos) + "\n" + v.slice(pos);
668
+ setValueSync(newValue);
669
+ setCursorPosSync(pos + 1);
670
+ onChangeRef.current?.(newValue);
635
671
  return;
636
672
  }
637
673
  if (key.return) {
638
- const text = currentLines.join("\n");
639
- onSubmitRef.current(text);
640
- setLines([""]);
674
+ onSubmitRef.current(v);
675
+ setValueSync("");
676
+ setCursorPosSync(0);
641
677
  onChangeRef.current?.("");
642
678
  return;
643
679
  }
644
680
  if (key.backspace || key.delete) {
645
- const lastIdx = currentLines.length - 1;
646
- const lastLine = currentLines[lastIdx];
647
- let newLines;
648
- if (lastLine.length > 0) {
649
- newLines = [...currentLines.slice(0, lastIdx), lastLine.slice(0, -1)];
650
- } else if (currentLines.length > 1) {
651
- newLines = currentLines.slice(0, -1);
652
- } else {
681
+ if (pos === 0) return;
682
+ const newValue = v.slice(0, pos - 1) + v.slice(pos);
683
+ setValueSync(newValue);
684
+ setCursorPosSync(pos - 1);
685
+ onChangeRef.current?.(newValue);
686
+ return;
687
+ }
688
+ if (key.ctrl) {
689
+ switch (input) {
690
+ case "a": {
691
+ setCursorPosSync(0);
692
+ return;
693
+ }
694
+ case "e": {
695
+ setCursorPosSync(v.length);
696
+ return;
697
+ }
698
+ case "b": {
699
+ setCursorPosSync(Math.max(0, pos - 1));
700
+ return;
701
+ }
702
+ case "f": {
703
+ setCursorPosSync(Math.min(v.length, pos + 1));
704
+ return;
705
+ }
706
+ case "k": {
707
+ const nextNl = v.indexOf("\n", pos);
708
+ const lineEnd = nextNl === -1 ? v.length : nextNl;
709
+ const newValue = v.slice(0, pos) + v.slice(lineEnd);
710
+ setValueSync(newValue);
711
+ onChangeRef.current?.(newValue);
712
+ return;
713
+ }
714
+ case "u": {
715
+ const newValue = v.slice(pos);
716
+ setValueSync(newValue);
717
+ setCursorPosSync(0);
718
+ onChangeRef.current?.(newValue);
719
+ return;
720
+ }
721
+ case "w": {
722
+ const newPos = wordBackward(v, pos);
723
+ const newValue = v.slice(0, newPos) + v.slice(pos);
724
+ setValueSync(newValue);
725
+ setCursorPosSync(newPos);
726
+ onChangeRef.current?.(newValue);
727
+ return;
728
+ }
729
+ case "d": {
730
+ if (pos >= v.length) return;
731
+ const newValue = v.slice(0, pos) + v.slice(pos + 1);
732
+ setValueSync(newValue);
733
+ onChangeRef.current?.(newValue);
734
+ return;
735
+ }
736
+ default:
737
+ return;
738
+ }
739
+ }
740
+ if (key.meta) {
741
+ if (input === "b") {
742
+ setCursorPosSync(wordBackward(v, pos));
653
743
  return;
654
744
  }
655
- setLines(newLines);
656
- onChangeRef.current?.(newLines.join("\n"));
745
+ if (input === "f") {
746
+ setCursorPosSync(wordForward(v, pos));
747
+ return;
748
+ }
749
+ return;
750
+ }
751
+ if (key.leftArrow) {
752
+ setCursorPosSync(Math.max(0, pos - 1));
657
753
  return;
658
754
  }
659
- if (key.ctrl || key.escape || key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.tab || key.pageUp || key.pageDown) {
755
+ if (key.rightArrow) {
756
+ setCursorPosSync(Math.min(v.length, pos + 1));
757
+ return;
758
+ }
759
+ if (key.escape || key.upArrow || key.downArrow || key.tab || key.pageUp || key.pageDown) {
660
760
  return;
661
761
  }
662
762
  if (input.length > 0) {
663
- const newLines = [...currentLines.slice(0, -1), currentLines[currentLines.length - 1] + input];
664
- setLines(newLines);
665
- onChangeRef.current?.(newLines.join("\n"));
763
+ const newValue = v.slice(0, pos) + input + v.slice(pos);
764
+ setValueSync(newValue);
765
+ setCursorPosSync(pos + input.length);
766
+ onChangeRef.current?.(newValue);
666
767
  }
667
768
  },
668
769
  { isActive }
669
770
  );
670
- const isEmpty = lines.every((line) => line === "");
771
+ const isEmpty = value === "";
671
772
  const renderLines = () => {
672
773
  if (isEmpty && placeholder) {
673
774
  return /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -676,19 +777,41 @@ var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placehold
676
777
  isActive && cursorVisible && /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: CURSOR_CHAR })
677
778
  ] });
678
779
  }
780
+ const lines = value.split("\n");
781
+ let remaining = cursorPos;
782
+ let cursorLine = 0;
783
+ let cursorCol = 0;
784
+ for (let i = 0; i < lines.length; i++) {
785
+ const lineLen = lines[i].length;
786
+ if (remaining <= lineLen) {
787
+ cursorLine = i;
788
+ cursorCol = remaining;
789
+ break;
790
+ }
791
+ remaining -= lineLen + 1;
792
+ }
679
793
  return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: lines.map((line, idx) => {
680
- const isLastLine = idx === lines.length - 1;
681
794
  const prefix = idx === 0 ? "> " : " ";
795
+ const showCursor = isActive && cursorVisible && idx === cursorLine;
796
+ if (!showCursor) {
797
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
798
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
799
+ /* @__PURE__ */ jsx3(Text3, { children: line })
800
+ ] }, idx);
801
+ }
802
+ const before = line.slice(0, cursorCol);
803
+ const after = line.slice(cursorCol);
682
804
  return /* @__PURE__ */ jsxs3(Box3, { children: [
683
805
  /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
684
- /* @__PURE__ */ jsx3(Text3, { children: line }),
685
- isActive && isLastLine && cursorVisible && /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: CURSOR_CHAR })
806
+ /* @__PURE__ */ jsx3(Text3, { children: before }),
807
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: CURSOR_CHAR }),
808
+ /* @__PURE__ */ jsx3(Text3, { children: after })
686
809
  ] }, idx);
687
810
  }) });
688
811
  };
689
812
  return /* @__PURE__ */ jsx3(Box3, { children: isActive ? renderLines() : /* @__PURE__ */ jsxs3(Box3, { children: [
690
813
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "> " }),
691
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isEmpty ? placeholder ?? "" : lines.join(" ") })
814
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isEmpty ? placeholder ?? "" : value })
692
815
  ] }) });
693
816
  });
694
817
  var Input_default = Input;
@@ -764,6 +887,11 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
764
887
  const [confirmPrompt, setConfirmPrompt] = useState3(void 0);
765
888
  const [expandTools, setExpandTools] = useState3(false);
766
889
  const [scrollOffset, setScrollOffset] = useState3(0);
890
+ const [inputHistory, setInputHistory] = useState3([]);
891
+ const inputHistoryRef = useRef2([]);
892
+ inputHistoryRef.current = inputHistory;
893
+ const historyIndexRef = useRef2(-1);
894
+ const isNavigatingHistoryRef = useRef2(false);
767
895
  const totalLines = useMemo(() => {
768
896
  const visible = messages.filter((m) => m.role !== "system");
769
897
  return visible.reduce(
@@ -799,7 +927,7 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
799
927
  }
800
928
  loggedCountRef.current = messages.length;
801
929
  }, [messages]);
802
- useInput2((_input, key) => {
930
+ useInput2((input, key) => {
803
931
  const skillList = registry2?.list() ?? [];
804
932
  const suggestions = computeSuggestions(skillList, acState);
805
933
  const open = isOpen(acState, suggestions);
@@ -837,6 +965,38 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
837
965
  setScrollOffset((prev) => Math.max(0, prev - scrollStep));
838
966
  return;
839
967
  }
968
+ if (key.ctrl && input === "v") {
969
+ setScrollOffset((prev) => Math.max(0, prev - scrollStep));
970
+ return;
971
+ }
972
+ if (key.meta && input === "v") {
973
+ setScrollOffset((prev) => Math.min(prev + scrollStep, Math.max(0, totalLines - 1)));
974
+ return;
975
+ }
976
+ if (key.ctrl && input === "p") {
977
+ const history = inputHistoryRef.current;
978
+ const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
979
+ if (newIndex >= 0 && newIndex < history.length) {
980
+ historyIndexRef.current = newIndex;
981
+ isNavigatingHistoryRef.current = true;
982
+ inputRef.current?.fill(history[newIndex]);
983
+ }
984
+ return;
985
+ }
986
+ if (key.ctrl && input === "n") {
987
+ const history = inputHistoryRef.current;
988
+ if (historyIndexRef.current > 0) {
989
+ const newIndex = historyIndexRef.current - 1;
990
+ historyIndexRef.current = newIndex;
991
+ isNavigatingHistoryRef.current = true;
992
+ inputRef.current?.fill(history[newIndex]);
993
+ } else if (historyIndexRef.current === 0) {
994
+ historyIndexRef.current = -1;
995
+ isNavigatingHistoryRef.current = true;
996
+ inputRef.current?.fill("");
997
+ }
998
+ return;
999
+ }
840
1000
  });
841
1001
  const confirm = useCallback((prompt) => {
842
1002
  return new Promise((resolve2) => {
@@ -980,6 +1140,8 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
980
1140
  return;
981
1141
  }
982
1142
  if (!trimmed) return;
1143
+ setInputHistory((prev) => [trimmed, ...prev.slice(0, 99)]);
1144
+ historyIndexRef.current = -1;
983
1145
  setScrollOffset(0);
984
1146
  if (registry2) {
985
1147
  const skillResult = handleSkillInput(trimmed, registry2);
@@ -1021,6 +1183,11 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
1021
1183
  );
1022
1184
  const isInputActive = status === "idle" || status === "awaiting_confirm";
1023
1185
  const handleInputTextChange = useCallback((text) => {
1186
+ if (isNavigatingHistoryRef.current) {
1187
+ isNavigatingHistoryRef.current = false;
1188
+ } else {
1189
+ historyIndexRef.current = -1;
1190
+ }
1024
1191
  if (status !== "awaiting_confirm") {
1025
1192
  setAcState((prev) => handleInputChange(prev, text));
1026
1193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",