pi-rtk-optimizer 0.7.1 → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  // Vendored from ../zellij-modal/index.ts to keep pi-rtk-optimizer standalone.
2
2
  // Keep this module in sync when upstream zellij-modal primitives change.
3
- import { getSettingsListTheme, type ExtensionAPI, type Theme } from "@mariozechner/pi-coding-agent";
3
+ import { getSettingsListTheme, type ExtensionAPI, type Theme } from "@earendil-works/pi-coding-agent";
4
4
  import {
5
5
  Box,
6
6
  Container,
@@ -10,7 +10,7 @@ import {
10
10
  truncateToWidth,
11
11
  visibleWidth,
12
12
  type SettingItem,
13
- } from "@mariozechner/pi-tui";
13
+ } from "@earendil-works/pi-tui";
14
14
 
15
15
  const ANSI_RESET = "\x1b[0m";
16
16
 
@@ -782,13 +782,22 @@ export class ZellijModal implements ZellijModalComponent {
782
782
  /**
783
783
  * Options for the pre-built settings modal content renderer.
784
784
  */
785
+ export interface SettingsTab {
786
+ label: string;
787
+ settings: SettingItem[];
788
+ }
789
+
785
790
  export interface SettingsModalOptions {
786
791
  /** Modal heading. */
787
792
  title: string;
788
793
  /** Optional descriptive subtitle shown above settings. */
789
794
  description?: string;
790
- /** Settings list items. */
791
- settings: SettingItem[];
795
+ /** Settings list items (used when tabs are not provided). */
796
+ settings?: SettingItem[];
797
+ /** Optional tabs for grouped settings. */
798
+ tabs?: SettingsTab[];
799
+ /** Initial active tab index. */
800
+ activeTabIndex?: number;
792
801
  /** Called when a setting value changes. */
793
802
  onChange: (id: string, value: string) => void;
794
803
  /** Called when modal should close. */
@@ -808,6 +817,9 @@ export class ZellijSettingsModal implements ZellijModalContentRenderer {
808
817
  private settingsList: SettingsList;
809
818
  private options: SettingsModalOptions;
810
819
  private theme: Theme;
820
+ private showTabs: boolean;
821
+ private activeTabIndex: number;
822
+ private tabLists: SettingsList[];
811
823
 
812
824
  constructor(options: SettingsModalOptions, theme: Theme) {
813
825
  if (!options.title || !options.title.trim()) {
@@ -816,37 +828,34 @@ export class ZellijSettingsModal implements ZellijModalContentRenderer {
816
828
 
817
829
  this.options = options;
818
830
  this.theme = theme;
831
+ this.showTabs = options.tabs !== undefined && options.tabs.length > 0;
832
+ this.tabLists = this.showTabs ? options.tabs!.map((tab) => this.createSettingsList(tab.settings, () => this.options.onClose())) : [];
833
+ this.activeTabIndex = this.normalizeActiveTabIndex(options.activeTabIndex ?? 0);
819
834
  this.container = new Container();
820
835
  this.contentBox = new Box(0, 0);
821
836
 
822
- this.contentBox.addChild(new Text(this.theme.fg("accent", this.theme.bold(options.title)), 0, 0));
837
+ if (this.showTabs) {
838
+ this.settingsList = this.tabLists[this.activeTabIndex] ?? this.tabLists[0]!;
839
+ } else {
840
+ this.contentBox.addChild(new Text(this.theme.fg("accent", this.theme.bold(options.title)), 0, 0));
841
+
842
+ if (options.description) {
843
+ this.contentBox.addChild(new Spacer(1));
844
+ this.contentBox.addChild(new Text(this.theme.fg("muted", options.description), 0, 0));
845
+ }
823
846
 
824
- if (options.description) {
825
847
  this.contentBox.addChild(new Spacer(1));
826
- this.contentBox.addChild(new Text(this.theme.fg("muted", options.description), 0, 0));
827
- }
848
+ const fallbackSettings = options.settings ?? [];
849
+ this.settingsList = this.createSettingsList(fallbackSettings, () => this.options.onClose());
850
+ this.contentBox.addChild(this.settingsList);
828
851
 
829
- this.contentBox.addChild(new Spacer(1));
830
- this.settingsList = new SettingsList(
831
- options.settings,
832
- Math.min(Math.max(options.settings.length + 2, 6), 18),
833
- getSettingsListTheme(),
834
- (id: string, value: string) => {
835
- this.options.onChange(id, value);
836
- },
837
- () => {
838
- this.options.onClose();
839
- },
840
- { enableSearch: options.enableSearch ?? true },
841
- );
842
- this.contentBox.addChild(this.settingsList);
852
+ if (options.helpText) {
853
+ this.contentBox.addChild(new Spacer(1));
854
+ this.contentBox.addChild(new Text(this.theme.fg("dim", options.helpText), 0, 0));
855
+ }
843
856
 
844
- if (options.helpText) {
845
- this.contentBox.addChild(new Spacer(1));
846
- this.contentBox.addChild(new Text(this.theme.fg("dim", options.helpText), 0, 0));
857
+ this.container.addChild(this.contentBox);
847
858
  }
848
-
849
- this.container.addChild(this.contentBox);
850
859
  }
851
860
 
852
861
  /**
@@ -855,18 +864,100 @@ export class ZellijSettingsModal implements ZellijModalContentRenderer {
855
864
  render(width: number): string[] {
856
865
  const safeWidth = Math.max(1, width);
857
866
  try {
858
- return this.container.render(safeWidth);
867
+ if (!this.showTabs) {
868
+ return this.container.render(safeWidth);
869
+ }
870
+
871
+ const lines: string[] = [];
872
+
873
+ // Title
874
+ lines.push(this.theme.fg("accent", this.theme.bold(this.options.title)));
875
+ lines.push("");
876
+
877
+ // Tab bar
878
+ lines.push(this.renderTabBar(safeWidth));
879
+ lines.push("");
880
+
881
+ // Active settings list for the selected tab.
882
+ const activeList = this.tabLists[this.activeTabIndex] ?? this.tabLists[0];
883
+ if (activeList) {
884
+ const listRender = activeList.render(safeWidth);
885
+ lines.push(...listRender);
886
+ }
887
+
888
+ // Separator + help text
889
+ if (this.options.helpText) {
890
+ lines.push(this.theme.fg("border", "─".repeat(Math.max(0, safeWidth))));
891
+ lines.push(this.theme.fg("dim", this.options.helpText));
892
+ }
893
+
894
+ return lines;
859
895
  } catch (error) {
860
896
  const message = error instanceof Error ? error.message : String(error);
861
897
  return [this.theme.fg("error", truncateToWidth(`Settings render error: ${message}`, safeWidth, "…"))];
862
898
  }
863
899
  }
864
900
 
901
+ private createSettingsList(settings: SettingItem[], onCancel: () => void): SettingsList {
902
+ return new SettingsList(
903
+ settings,
904
+ Math.min(Math.max(settings.length + 2, 6), 18),
905
+ getSettingsListTheme(),
906
+ (id: string, value: string) => {
907
+ this.options.onChange(id, value);
908
+ },
909
+ onCancel,
910
+ { enableSearch: this.options.enableSearch ?? true },
911
+ );
912
+ }
913
+
914
+ private normalizeActiveTabIndex(index: number): number {
915
+ if (!this.showTabs || this.tabLists.length === 0) {
916
+ return 0;
917
+ }
918
+
919
+ const normalized = Number.isFinite(index) ? Math.floor(index) : 0;
920
+ return ((normalized % this.tabLists.length) + this.tabLists.length) % this.tabLists.length;
921
+ }
922
+
923
+ private renderTabBar(width: number): string {
924
+ if (!this.showTabs || !this.options.tabs || this.options.tabs.length === 0) {
925
+ return "";
926
+ }
927
+
928
+ const parts: string[] = [];
929
+ for (let i = 0; i < this.options.tabs.length; i++) {
930
+ const label = this.options.tabs[i].label;
931
+ if (i === this.activeTabIndex) {
932
+ parts.push(this.theme.fg("accent", `[ ${label} ]`));
933
+ } else {
934
+ parts.push(this.theme.fg("muted", ` ${label} `));
935
+ }
936
+ }
937
+
938
+ return truncateToWidth(parts.join(""), width, "…");
939
+ }
940
+
941
+ private switchTab(direction: number): void {
942
+ if (!this.showTabs || this.tabLists.length === 0) {
943
+ return;
944
+ }
945
+ this.activeTabIndex = this.normalizeActiveTabIndex(this.activeTabIndex + direction);
946
+ this.settingsList = this.tabLists[this.activeTabIndex] ?? this.tabLists[0]!;
947
+ }
948
+
865
949
  /**
866
950
  * Invalidate internal caches.
867
951
  */
868
952
  invalidate(): void {
869
- this.container.invalidate();
953
+ if (!this.showTabs) {
954
+ this.container.invalidate();
955
+ return;
956
+ }
957
+
958
+ for (const list of this.tabLists) {
959
+ list.invalidate();
960
+ }
870
961
  }
871
962
 
872
963
  /**
@@ -876,6 +967,15 @@ export class ZellijSettingsModal implements ZellijModalContentRenderer {
876
967
  if (isEnterActivationInput(data)) {
877
968
  return;
878
969
  }
970
+ if (this.showTabs && data === "\x1b[D") {
971
+ this.switchTab(-1);
972
+ return;
973
+ }
974
+ if (this.showTabs && data === "\x1b[C") {
975
+ this.switchTab(1);
976
+ return;
977
+ }
978
+
879
979
  this.settingsList.handleInput(data);
880
980
  }
881
981
 
@@ -883,7 +983,14 @@ export class ZellijSettingsModal implements ZellijModalContentRenderer {
883
983
  * Programmatically update one setting value in the list.
884
984
  */
885
985
  updateValue(id: string, value: string): void {
886
- this.settingsList.updateValue(id, value);
986
+ if (!this.showTabs) {
987
+ this.settingsList.updateValue(id, value);
988
+ return;
989
+ }
990
+
991
+ for (const list of this.tabLists) {
992
+ list.updateValue(id, value);
993
+ }
887
994
  }
888
995
  }
889
996