@fresh-editor/fresh-editor 0.2.22 → 0.2.23
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/CHANGELOG.md +41 -0
- package/package.json +1 -1
- package/plugins/audit_mode.i18n.json +141 -379
- package/plugins/audit_mode.ts +1078 -451
- package/plugins/config-schema.json +146 -0
- package/plugins/git_explorer.ts +7 -7
- package/plugins/lib/fresh.d.ts +25 -2
- package/plugins/pkg.ts +151 -397
- package/plugins/theme_editor.i18n.json +182 -14
- package/plugins/theme_editor.ts +192 -85
package/plugins/pkg.ts
CHANGED
|
@@ -1654,6 +1654,10 @@ interface PkgManagerState {
|
|
|
1654
1654
|
selectedIndex: number;
|
|
1655
1655
|
focus: FocusTarget; // What element has Tab focus
|
|
1656
1656
|
isLoading: boolean;
|
|
1657
|
+
viewportHeight: number;
|
|
1658
|
+
// Buffer group fields
|
|
1659
|
+
groupId: number | null;
|
|
1660
|
+
panelBuffers: Record<string, number>;
|
|
1657
1661
|
}
|
|
1658
1662
|
|
|
1659
1663
|
const pkgState: PkgManagerState = {
|
|
@@ -1667,6 +1671,9 @@ const pkgState: PkgManagerState = {
|
|
|
1667
1671
|
selectedIndex: 0,
|
|
1668
1672
|
focus: { type: "list" },
|
|
1669
1673
|
isLoading: false,
|
|
1674
|
+
viewportHeight: 24,
|
|
1675
|
+
groupId: null,
|
|
1676
|
+
panelBuffers: {},
|
|
1670
1677
|
};
|
|
1671
1678
|
|
|
1672
1679
|
// Theme-aware color configuration
|
|
@@ -1931,9 +1938,9 @@ function formatNumber(n: number | undefined): string {
|
|
|
1931
1938
|
}
|
|
1932
1939
|
|
|
1933
1940
|
// Layout constants
|
|
1934
|
-
const
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1941
|
+
const TOTAL_WIDTH = 88;
|
|
1942
|
+
const LIST_WIDTH = 36;
|
|
1943
|
+
function DETAIL_WIDTH(): number { return TOTAL_WIDTH - LIST_WIDTH - 3; }
|
|
1937
1944
|
|
|
1938
1945
|
/**
|
|
1939
1946
|
* Helper to check if a button is focused
|
|
@@ -1987,103 +1994,60 @@ function wrapText(text: string, maxWidth: number): string[] {
|
|
|
1987
1994
|
/**
|
|
1988
1995
|
* Build virtual buffer entries for the package manager (split-view layout)
|
|
1989
1996
|
*/
|
|
1990
|
-
function
|
|
1997
|
+
function utf8ByteLength(str: string): number {
|
|
1998
|
+
let bytes = 0;
|
|
1999
|
+
for (let i = 0; i < str.length; i++) {
|
|
2000
|
+
const code = str.charCodeAt(i);
|
|
2001
|
+
if (code < 0x80) {
|
|
2002
|
+
bytes += 1;
|
|
2003
|
+
} else if (code < 0x800) {
|
|
2004
|
+
bytes += 2;
|
|
2005
|
+
} else if (code >= 0xD800 && code <= 0xDBFF) {
|
|
2006
|
+
// Surrogate pair = 4 bytes, skip low surrogate
|
|
2007
|
+
bytes += 4;
|
|
2008
|
+
i++;
|
|
2009
|
+
} else {
|
|
2010
|
+
bytes += 3;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return bytes;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
function buildPkgHeaderEntries(): TextPropertyEntry[] {
|
|
1991
2017
|
const entries: TextPropertyEntry[] = [];
|
|
2018
|
+
entries.push({ text: " Packages\n", properties: { type: "header" } });
|
|
2019
|
+
// Search bar
|
|
2020
|
+
const searchFocused = pkgState.focus.type === "search";
|
|
2021
|
+
const searchLeft = searchFocused ? "[" : " ";
|
|
2022
|
+
const searchRight = searchFocused ? "]" : " ";
|
|
2023
|
+
const searchVal = pkgState.searchQuery || "";
|
|
2024
|
+
entries.push({ text: ` Search: ${searchLeft}${searchVal.padEnd(30)}${searchRight}\n`, properties: { type: "search-input", focused: searchFocused } });
|
|
2025
|
+
// Filter bar
|
|
2026
|
+
const filters = ["All", "Installed", "Plugins", "Themes", "Languages", "Bundles"];
|
|
2027
|
+
let filterLine = " ";
|
|
2028
|
+
for (let i = 0; i < filters.length; i++) {
|
|
2029
|
+
const isActive = pkgState.filter === filters[i].toLowerCase();
|
|
2030
|
+
const isFocused = pkgState.focus.type === "filter" && pkgState.focus.index === i;
|
|
2031
|
+
const lb = isFocused ? "[" : " ";
|
|
2032
|
+
const rb = isFocused ? "]" : " ";
|
|
2033
|
+
filterLine += `${lb} ${filters[i]} ${rb} `;
|
|
2034
|
+
}
|
|
2035
|
+
const syncFocused = pkgState.focus.type === "sync";
|
|
2036
|
+
const sl = syncFocused ? "[" : " ";
|
|
2037
|
+
const sr = syncFocused ? "]" : " ";
|
|
2038
|
+
filterLine += ` ${sl} Sync ${sr}`;
|
|
2039
|
+
entries.push({ text: filterLine + "\n", properties: { type: "filter-bar" } });
|
|
2040
|
+
return entries;
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
function buildPkgListEntries(): TextPropertyEntry[] {
|
|
1992
2044
|
const items = getFilteredItems();
|
|
1993
|
-
const selectedItem = items.length > 0 && pkgState.selectedIndex < items.length
|
|
1994
|
-
? items[pkgState.selectedIndex] : null;
|
|
1995
2045
|
const installedItems = items.filter(i => i.installed);
|
|
1996
2046
|
const availableItems = items.filter(i => !i.installed);
|
|
2047
|
+
const entries: TextPropertyEntry[] = [];
|
|
1997
2048
|
|
|
1998
|
-
// === HEADER ===
|
|
1999
|
-
entries.push({
|
|
2000
|
-
text: " Packages\n",
|
|
2001
|
-
properties: { type: "header" },
|
|
2002
|
-
});
|
|
2003
|
-
|
|
2004
|
-
// Empty line after header
|
|
2005
|
-
entries.push({ text: "\n", properties: { type: "blank" } });
|
|
2006
|
-
|
|
2007
|
-
// === SEARCH BAR (input-style) ===
|
|
2008
|
-
const searchFocused = isButtonFocused("search");
|
|
2009
|
-
const searchInputWidth = 30;
|
|
2010
|
-
const searchText = pkgState.searchQuery || "";
|
|
2011
|
-
const searchDisplay = searchText.length > searchInputWidth - 1
|
|
2012
|
-
? searchText.slice(0, searchInputWidth - 2) + "…"
|
|
2013
|
-
: searchText.padEnd(searchInputWidth);
|
|
2014
|
-
|
|
2015
|
-
entries.push({ text: " Search: ", properties: { type: "search-label" } });
|
|
2016
|
-
entries.push({
|
|
2017
|
-
text: searchFocused ? `[${searchDisplay}]` : ` ${searchDisplay} `,
|
|
2018
|
-
properties: { type: "search-input", focused: searchFocused },
|
|
2019
|
-
});
|
|
2020
|
-
entries.push({ text: "\n", properties: { type: "newline" } });
|
|
2021
|
-
|
|
2022
|
-
// === FILTER BAR with focusable buttons ===
|
|
2023
|
-
const filters: Array<{ id: string; label: string }> = [
|
|
2024
|
-
{ id: "all", label: "All" },
|
|
2025
|
-
{ id: "installed", label: "Installed" },
|
|
2026
|
-
{ id: "plugins", label: "Plugins" },
|
|
2027
|
-
{ id: "themes", label: "Themes" },
|
|
2028
|
-
{ id: "languages", label: "Languages" },
|
|
2029
|
-
{ id: "bundles", label: "Bundles" },
|
|
2030
|
-
];
|
|
2031
|
-
|
|
2032
|
-
// Build filter buttons with position tracking
|
|
2033
|
-
let filterBarParts: Array<{ text: string; type: string; focused?: boolean; active?: boolean }> = [];
|
|
2034
|
-
filterBarParts.push({ text: " ", type: "spacer" });
|
|
2035
|
-
|
|
2036
|
-
for (let i = 0; i < filters.length; i++) {
|
|
2037
|
-
const f = filters[i];
|
|
2038
|
-
const isActive = pkgState.filter === f.id;
|
|
2039
|
-
const isFocused = isButtonFocused("filter", i);
|
|
2040
|
-
// Always reserve space for brackets - show [ ] when focused, spaces when not
|
|
2041
|
-
const leftBracket = isFocused ? "[" : " ";
|
|
2042
|
-
const rightBracket = isFocused ? "]" : " ";
|
|
2043
|
-
filterBarParts.push({
|
|
2044
|
-
text: `${leftBracket} ${f.label} ${rightBracket}`,
|
|
2045
|
-
type: "filter-btn",
|
|
2046
|
-
focused: isFocused,
|
|
2047
|
-
active: isActive,
|
|
2048
|
-
});
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
filterBarParts.push({ text: " ", type: "spacer" });
|
|
2052
|
-
|
|
2053
|
-
// Sync button - always reserve space for brackets
|
|
2054
|
-
const syncFocused = isButtonFocused("sync");
|
|
2055
|
-
const syncLeft = syncFocused ? "[" : " ";
|
|
2056
|
-
const syncRight = syncFocused ? "]" : " ";
|
|
2057
|
-
filterBarParts.push({ text: `${syncLeft} Sync ${syncRight}`, type: "sync-btn", focused: syncFocused });
|
|
2058
|
-
|
|
2059
|
-
// Emit each filter bar part as separate entry for individual styling
|
|
2060
|
-
for (const part of filterBarParts) {
|
|
2061
|
-
entries.push({
|
|
2062
|
-
text: part.text,
|
|
2063
|
-
properties: {
|
|
2064
|
-
type: part.type,
|
|
2065
|
-
focused: part.focused,
|
|
2066
|
-
active: part.active,
|
|
2067
|
-
},
|
|
2068
|
-
});
|
|
2069
|
-
}
|
|
2070
|
-
entries.push({ text: "\n", properties: { type: "newline" } });
|
|
2071
|
-
|
|
2072
|
-
// === TOP SEPARATOR ===
|
|
2073
|
-
entries.push({
|
|
2074
|
-
text: " " + "─".repeat(TOTAL_WIDTH - 2) + "\n",
|
|
2075
|
-
properties: { type: "separator" },
|
|
2076
|
-
});
|
|
2077
|
-
|
|
2078
|
-
// === SPLIT VIEW: Package list on left, Details on right ===
|
|
2079
|
-
|
|
2080
|
-
// Build left panel lines (package list)
|
|
2081
|
-
const leftLines: Array<{ text: string; type: string; selected?: boolean; installed?: boolean }> = [];
|
|
2082
|
-
|
|
2083
|
-
// Installed section
|
|
2084
2049
|
if (installedItems.length > 0) {
|
|
2085
|
-
|
|
2086
|
-
|
|
2050
|
+
entries.push({ text: `INSTALLED (${installedItems.length})\n`, properties: { type: "section-title" } });
|
|
2087
2051
|
let idx = 0;
|
|
2088
2052
|
for (const item of installedItems) {
|
|
2089
2053
|
const isSelected = idx === pkgState.selectedIndex;
|
|
@@ -2091,335 +2055,101 @@ function buildListViewEntries(): TextPropertyEntry[] {
|
|
|
2091
2055
|
const prefix = isSelected && listFocused ? "▸" : " ";
|
|
2092
2056
|
const status = item.updateAvailable ? "↑" : "✓";
|
|
2093
2057
|
const ver = item.version.length > 7 ? item.version.slice(0, 6) + "…" : item.version;
|
|
2094
|
-
const
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2058
|
+
const nameW = Math.max(8, LIST_WIDTH - 16);
|
|
2059
|
+
const name = item.name.length > nameW ? item.name.slice(0, nameW - 1) + "…" : item.name;
|
|
2060
|
+
entries.push({ text: `${prefix} ${name.padEnd(nameW)} ${ver.padEnd(7)} ${status}\n`, properties: { type: "package-row", selected: isSelected, installed: true } });
|
|
2097
2061
|
idx++;
|
|
2098
2062
|
}
|
|
2099
2063
|
}
|
|
2100
2064
|
|
|
2101
|
-
// Available section
|
|
2102
2065
|
if (availableItems.length > 0) {
|
|
2103
|
-
if (
|
|
2104
|
-
|
|
2105
|
-
|
|
2066
|
+
if (entries.length > 0) entries.push({ text: "\n", properties: { type: "blank" } });
|
|
2067
|
+
entries.push({ text: `AVAILABLE (${availableItems.length})\n`, properties: { type: "section-title" } });
|
|
2106
2068
|
let idx = installedItems.length;
|
|
2107
2069
|
for (const item of availableItems) {
|
|
2108
2070
|
const isSelected = idx === pkgState.selectedIndex;
|
|
2109
2071
|
const listFocused = pkgState.focus.type === "list";
|
|
2110
2072
|
const prefix = isSelected && listFocused ? "▸" : " ";
|
|
2111
2073
|
const typeTag = item.packageType === "theme" ? "T" : item.packageType === "language" ? "L" : item.packageType === "bundle" ? "B" : "P";
|
|
2112
|
-
const
|
|
2113
|
-
const
|
|
2114
|
-
|
|
2074
|
+
const availNameW = Math.max(8, LIST_WIDTH - 10);
|
|
2075
|
+
const name = item.name.length > availNameW ? item.name.slice(0, availNameW - 1) + "…" : item.name;
|
|
2076
|
+
entries.push({ text: `${prefix} ${name.padEnd(availNameW)} [${typeTag}]\n`, properties: { type: "package-row", selected: isSelected, installed: false } });
|
|
2115
2077
|
idx++;
|
|
2116
2078
|
}
|
|
2117
2079
|
}
|
|
2118
2080
|
|
|
2119
|
-
// Empty state for left panel
|
|
2120
2081
|
if (items.length === 0) {
|
|
2121
2082
|
if (pkgState.isLoading) {
|
|
2122
|
-
|
|
2123
|
-
} else if (!isRegistrySynced()) {
|
|
2124
|
-
leftLines.push({ text: "Registry not synced", type: "empty-state" });
|
|
2125
|
-
leftLines.push({ text: "Tab to Sync button", type: "empty-state" });
|
|
2083
|
+
entries.push({ text: "Loading...\n", properties: { type: "empty-state" } });
|
|
2126
2084
|
} else {
|
|
2127
|
-
|
|
2085
|
+
entries.push({ text: "No packages found\n", properties: { type: "empty-state" } });
|
|
2128
2086
|
}
|
|
2129
2087
|
}
|
|
2130
2088
|
|
|
2131
|
-
|
|
2132
|
-
|
|
2089
|
+
return entries;
|
|
2090
|
+
}
|
|
2133
2091
|
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2092
|
+
function buildPkgDetailEntries(): TextPropertyEntry[] {
|
|
2093
|
+
const items = getFilteredItems();
|
|
2094
|
+
const selectedItem = items.length > 0 && pkgState.selectedIndex < items.length
|
|
2095
|
+
? items[pkgState.selectedIndex] : null;
|
|
2096
|
+
const entries: TextPropertyEntry[] = [];
|
|
2138
2097
|
|
|
2139
|
-
|
|
2098
|
+
if (selectedItem) {
|
|
2099
|
+
entries.push({ text: selectedItem.name + "\n", properties: { type: "detail-title" } });
|
|
2100
|
+
entries.push({ text: "─".repeat(Math.min(selectedItem.name.length + 2, 50)) + "\n", properties: { type: "detail-sep" } });
|
|
2140
2101
|
let metaLine = `v${selectedItem.version}`;
|
|
2141
2102
|
if (selectedItem.author) metaLine += ` • ${selectedItem.author}`;
|
|
2142
2103
|
if (selectedItem.license) metaLine += ` • ${selectedItem.license}`;
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
rightLines.push({ text: "", type: "blank" });
|
|
2147
|
-
|
|
2148
|
-
// Description (wrapped)
|
|
2104
|
+
entries.push({ text: metaLine + "\n", properties: { type: "detail-meta" } });
|
|
2105
|
+
entries.push({ text: "\n", properties: { type: "blank" } });
|
|
2149
2106
|
const descText = selectedItem.description || "No description available";
|
|
2150
|
-
const descLines = wrapText(descText,
|
|
2107
|
+
const descLines = wrapText(descText, 50);
|
|
2151
2108
|
for (const line of descLines) {
|
|
2152
|
-
|
|
2109
|
+
entries.push({ text: line + "\n", properties: { type: "detail-desc" } });
|
|
2153
2110
|
}
|
|
2154
|
-
|
|
2155
|
-
rightLines.push({ text: "", type: "blank" });
|
|
2156
|
-
|
|
2157
|
-
// Keywords
|
|
2111
|
+
entries.push({ text: "\n", properties: { type: "blank" } });
|
|
2158
2112
|
if (selectedItem.keywords && selectedItem.keywords.length > 0) {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
rightLines.push({ text: "", type: "blank" });
|
|
2113
|
+
entries.push({ text: `Tags: ${selectedItem.keywords.slice(0, 4).join(", ")}\n`, properties: { type: "detail-tags" } });
|
|
2114
|
+
entries.push({ text: "\n", properties: { type: "blank" } });
|
|
2162
2115
|
}
|
|
2163
|
-
|
|
2164
|
-
// Repository URL
|
|
2165
2116
|
if (selectedItem.repository) {
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
if (displayUrl.length > DETAIL_WIDTH - 2) {
|
|
2171
|
-
displayUrl = displayUrl.slice(0, DETAIL_WIDTH - 5) + "...";
|
|
2172
|
-
}
|
|
2173
|
-
rightLines.push({ text: displayUrl, type: "detail-url" });
|
|
2174
|
-
rightLines.push({ text: "", type: "blank" });
|
|
2117
|
+
let displayUrl = selectedItem.repository.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
2118
|
+
if (displayUrl.length > 50) displayUrl = displayUrl.slice(0, 47) + "...";
|
|
2119
|
+
entries.push({ text: displayUrl + "\n", properties: { type: "detail-url" } });
|
|
2120
|
+
entries.push({ text: "\n", properties: { type: "blank" } });
|
|
2175
2121
|
}
|
|
2176
|
-
|
|
2177
|
-
// Action buttons - always reserve space for brackets
|
|
2178
2122
|
const actions = getActionButtons();
|
|
2179
2123
|
for (let i = 0; i < actions.length; i++) {
|
|
2180
|
-
const focused =
|
|
2181
|
-
const
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2184
|
-
rightLines.push({ text: btnText, type: "action-btn", focused, btnIndex: i });
|
|
2124
|
+
const focused = pkgState.focus.type === "action" && pkgState.focus.index === i;
|
|
2125
|
+
const lb = focused ? "[" : " ";
|
|
2126
|
+
const rb = focused ? "]" : " ";
|
|
2127
|
+
entries.push({ text: `${lb} ${actions[i]} ${rb}\n`, properties: { type: "action-btn", focused, btnIndex: i } });
|
|
2185
2128
|
}
|
|
2186
2129
|
} else {
|
|
2187
|
-
|
|
2188
|
-
rightLines.push({ text: "to view details", type: "empty-state" });
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
// Merge left and right panels into rows
|
|
2192
|
-
const maxRows = Math.max(leftLines.length, rightLines.length, 8);
|
|
2193
|
-
for (let i = 0; i < maxRows; i++) {
|
|
2194
|
-
const leftItem = leftLines[i];
|
|
2195
|
-
const rightItem = rightLines[i];
|
|
2196
|
-
|
|
2197
|
-
// Left side (padded to fixed width)
|
|
2198
|
-
const leftText = leftItem ? (" " + leftItem.text) : "";
|
|
2199
|
-
entries.push({
|
|
2200
|
-
text: leftText.padEnd(LIST_WIDTH),
|
|
2201
|
-
properties: {
|
|
2202
|
-
type: leftItem?.type || "blank",
|
|
2203
|
-
selected: leftItem?.selected,
|
|
2204
|
-
installed: leftItem?.installed,
|
|
2205
|
-
},
|
|
2206
|
-
});
|
|
2207
|
-
|
|
2208
|
-
// Divider
|
|
2209
|
-
entries.push({ text: "│", properties: { type: "divider" } });
|
|
2210
|
-
|
|
2211
|
-
// Right side
|
|
2212
|
-
const rightText = rightItem ? (" " + rightItem.text) : "";
|
|
2213
|
-
entries.push({
|
|
2214
|
-
text: rightText,
|
|
2215
|
-
properties: {
|
|
2216
|
-
type: rightItem?.type || "blank",
|
|
2217
|
-
focused: rightItem?.focused,
|
|
2218
|
-
btnIndex: rightItem?.btnIndex,
|
|
2219
|
-
},
|
|
2220
|
-
});
|
|
2221
|
-
|
|
2222
|
-
entries.push({ text: "\n", properties: { type: "newline" } });
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
// === BOTTOM SEPARATOR ===
|
|
2226
|
-
entries.push({
|
|
2227
|
-
text: " " + "─".repeat(TOTAL_WIDTH - 2) + "\n",
|
|
2228
|
-
properties: { type: "separator" },
|
|
2229
|
-
});
|
|
2230
|
-
|
|
2231
|
-
// === HELP LINE ===
|
|
2232
|
-
let helpText = " ↑↓ Navigate Tab Next / Search Enter ";
|
|
2233
|
-
if (pkgState.focus.type === "action") {
|
|
2234
|
-
helpText += "Activate";
|
|
2235
|
-
} else if (pkgState.focus.type === "filter") {
|
|
2236
|
-
helpText += "Filter";
|
|
2237
|
-
} else if (pkgState.focus.type === "sync") {
|
|
2238
|
-
helpText += "Sync";
|
|
2239
|
-
} else if (pkgState.focus.type === "search") {
|
|
2240
|
-
helpText += "Search";
|
|
2241
|
-
} else {
|
|
2242
|
-
helpText += "Select";
|
|
2130
|
+
entries.push({ text: "Select a package\nto view details\n", properties: { type: "empty-state" } });
|
|
2243
2131
|
}
|
|
2244
|
-
helpText += " Esc Close\n";
|
|
2245
|
-
|
|
2246
|
-
entries.push({
|
|
2247
|
-
text: helpText,
|
|
2248
|
-
properties: { type: "help" },
|
|
2249
|
-
});
|
|
2250
2132
|
|
|
2251
2133
|
return entries;
|
|
2252
2134
|
}
|
|
2253
2135
|
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
if (code < 0x80) {
|
|
2264
|
-
bytes += 1;
|
|
2265
|
-
} else if (code < 0x800) {
|
|
2266
|
-
bytes += 2;
|
|
2267
|
-
} else if (code >= 0xD800 && code <= 0xDBFF) {
|
|
2268
|
-
// Surrogate pair = 4 bytes, skip low surrogate
|
|
2269
|
-
bytes += 4;
|
|
2270
|
-
i++;
|
|
2271
|
-
} else {
|
|
2272
|
-
bytes += 3;
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
return bytes;
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
|
-
/**
|
|
2279
|
-
* Apply theme-aware highlighting to the package manager view
|
|
2280
|
-
*/
|
|
2281
|
-
function applyPkgManagerHighlighting(): void {
|
|
2282
|
-
if (pkgState.bufferId === null) return;
|
|
2283
|
-
|
|
2284
|
-
// Clear existing overlays
|
|
2285
|
-
editor.clearNamespace(pkgState.bufferId, "pkg");
|
|
2286
|
-
|
|
2287
|
-
const entries = buildListViewEntries();
|
|
2288
|
-
let byteOffset = 0;
|
|
2289
|
-
|
|
2290
|
-
for (const entry of entries) {
|
|
2291
|
-
const props = entry.properties as Record<string, unknown>;
|
|
2292
|
-
const len = utf8ByteLength(entry.text);
|
|
2293
|
-
|
|
2294
|
-
// Determine theme colors based on entry type
|
|
2295
|
-
let themeStyle: ThemeColor | null = null;
|
|
2296
|
-
|
|
2297
|
-
switch (props.type) {
|
|
2298
|
-
case "header":
|
|
2299
|
-
themeStyle = pkgTheme.header;
|
|
2300
|
-
break;
|
|
2301
|
-
|
|
2302
|
-
case "section-title":
|
|
2303
|
-
themeStyle = pkgTheme.sectionTitle;
|
|
2304
|
-
break;
|
|
2305
|
-
|
|
2306
|
-
case "filter-btn":
|
|
2307
|
-
if (props.focused && props.active) {
|
|
2308
|
-
// Both focused and active - use focused style
|
|
2309
|
-
themeStyle = pkgTheme.buttonFocused;
|
|
2310
|
-
} else if (props.focused) {
|
|
2311
|
-
// Only focused (not the active filter)
|
|
2312
|
-
themeStyle = pkgTheme.filterFocused;
|
|
2313
|
-
} else if (props.active) {
|
|
2314
|
-
// Active filter but not focused
|
|
2315
|
-
themeStyle = pkgTheme.filterActive;
|
|
2316
|
-
} else {
|
|
2317
|
-
themeStyle = pkgTheme.filterInactive;
|
|
2318
|
-
}
|
|
2319
|
-
break;
|
|
2320
|
-
|
|
2321
|
-
case "sync-btn":
|
|
2322
|
-
themeStyle = props.focused ? pkgTheme.buttonFocused : pkgTheme.button;
|
|
2323
|
-
break;
|
|
2324
|
-
|
|
2325
|
-
case "search-label":
|
|
2326
|
-
themeStyle = pkgTheme.infoLabel;
|
|
2327
|
-
break;
|
|
2328
|
-
|
|
2329
|
-
case "search-input":
|
|
2330
|
-
// Search input field styling - distinct background
|
|
2331
|
-
themeStyle = props.focused ? pkgTheme.searchBoxFocused : pkgTheme.searchBox;
|
|
2332
|
-
break;
|
|
2333
|
-
|
|
2334
|
-
case "package-row":
|
|
2335
|
-
if (props.selected) {
|
|
2336
|
-
themeStyle = pkgTheme.selected;
|
|
2337
|
-
} else if (props.installed) {
|
|
2338
|
-
themeStyle = pkgTheme.installed;
|
|
2339
|
-
} else {
|
|
2340
|
-
themeStyle = pkgTheme.available;
|
|
2341
|
-
}
|
|
2342
|
-
break;
|
|
2343
|
-
|
|
2344
|
-
case "detail-title":
|
|
2345
|
-
themeStyle = pkgTheme.header;
|
|
2346
|
-
break;
|
|
2347
|
-
|
|
2348
|
-
case "detail-sep":
|
|
2349
|
-
case "separator":
|
|
2350
|
-
themeStyle = pkgTheme.separator;
|
|
2351
|
-
break;
|
|
2352
|
-
|
|
2353
|
-
case "divider":
|
|
2354
|
-
themeStyle = pkgTheme.divider;
|
|
2355
|
-
break;
|
|
2356
|
-
|
|
2357
|
-
case "detail-meta":
|
|
2358
|
-
case "detail-tags":
|
|
2359
|
-
case "detail-url":
|
|
2360
|
-
themeStyle = pkgTheme.infoLabel;
|
|
2361
|
-
break;
|
|
2362
|
-
|
|
2363
|
-
case "detail-desc":
|
|
2364
|
-
themeStyle = pkgTheme.description;
|
|
2365
|
-
break;
|
|
2366
|
-
|
|
2367
|
-
case "action-btn":
|
|
2368
|
-
themeStyle = props.focused ? pkgTheme.buttonFocused : pkgTheme.button;
|
|
2369
|
-
break;
|
|
2370
|
-
|
|
2371
|
-
case "help":
|
|
2372
|
-
themeStyle = pkgTheme.help;
|
|
2373
|
-
break;
|
|
2374
|
-
|
|
2375
|
-
case "empty-state":
|
|
2376
|
-
themeStyle = pkgTheme.emptyState;
|
|
2377
|
-
break;
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
if (themeStyle) {
|
|
2381
|
-
const fg = themeStyle.fg;
|
|
2382
|
-
const bg = themeStyle.bg;
|
|
2383
|
-
|
|
2384
|
-
// Build overlay options - prefer theme keys, fallback to RGB
|
|
2385
|
-
const options: Record<string, unknown> = {};
|
|
2386
|
-
|
|
2387
|
-
if (fg?.theme) {
|
|
2388
|
-
options.fg = fg.theme;
|
|
2389
|
-
} else if (fg?.rgb) {
|
|
2390
|
-
options.fg = fg.rgb;
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
if (bg?.theme) {
|
|
2394
|
-
options.bg = bg.theme;
|
|
2395
|
-
} else if (bg?.rgb) {
|
|
2396
|
-
options.bg = bg.rgb;
|
|
2397
|
-
}
|
|
2398
|
-
|
|
2399
|
-
if (Object.keys(options).length > 0) {
|
|
2400
|
-
editor.addOverlay(
|
|
2401
|
-
pkgState.bufferId,
|
|
2402
|
-
"pkg",
|
|
2403
|
-
byteOffset,
|
|
2404
|
-
byteOffset + len,
|
|
2405
|
-
options
|
|
2406
|
-
);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
byteOffset += len;
|
|
2411
|
-
}
|
|
2136
|
+
function buildPkgFooterEntries(): TextPropertyEntry[] {
|
|
2137
|
+
let helpText = " ↑↓ Navigate Tab Next / Search Enter ";
|
|
2138
|
+
if (pkgState.focus.type === "action") helpText += "Activate";
|
|
2139
|
+
else if (pkgState.focus.type === "filter") helpText += "Filter";
|
|
2140
|
+
else if (pkgState.focus.type === "sync") helpText += "Sync";
|
|
2141
|
+
else if (pkgState.focus.type === "search") helpText += "Search";
|
|
2142
|
+
else helpText += "Select";
|
|
2143
|
+
helpText += " Esc Close";
|
|
2144
|
+
return [{ text: helpText + "\n", properties: { type: "help" } }];
|
|
2412
2145
|
}
|
|
2413
2146
|
|
|
2414
|
-
/**
|
|
2415
|
-
* Update the package manager view
|
|
2416
|
-
*/
|
|
2417
2147
|
function updatePkgManagerView(): void {
|
|
2418
|
-
if (pkgState.
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
editor.
|
|
2422
|
-
|
|
2148
|
+
if (pkgState.groupId === null) return;
|
|
2149
|
+
editor.setPanelContent(pkgState.groupId, "header", buildPkgHeaderEntries());
|
|
2150
|
+
editor.setPanelContent(pkgState.groupId, "list", buildPkgListEntries());
|
|
2151
|
+
editor.setPanelContent(pkgState.groupId, "detail", buildPkgDetailEntries());
|
|
2152
|
+
editor.setPanelContent(pkgState.groupId, "footer", buildPkgFooterEntries());
|
|
2423
2153
|
}
|
|
2424
2154
|
|
|
2425
2155
|
/**
|
|
@@ -2445,30 +2175,39 @@ async function openPackageManager(): Promise<void> {
|
|
|
2445
2175
|
pkgState.focus = { type: "list" };
|
|
2446
2176
|
|
|
2447
2177
|
// Build package list immediately with installed packages and cached registry
|
|
2448
|
-
// This allows viewing/managing installed packages without waiting for network
|
|
2449
2178
|
pkgState.items = buildPackageList();
|
|
2450
2179
|
pkgState.isLoading = false;
|
|
2451
2180
|
|
|
2452
|
-
//
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2181
|
+
// Create buffer group with layout:
|
|
2182
|
+
// vertical: [header(fixed 4), horizontal: [list, detail], footer(fixed 1)]
|
|
2183
|
+
const layout = JSON.stringify({
|
|
2184
|
+
type: "split",
|
|
2185
|
+
direction: "v",
|
|
2186
|
+
ratio: 0.1,
|
|
2187
|
+
first: { type: "fixed", id: "header", height: 3 },
|
|
2188
|
+
// ^ 3 rows: title, search, filter bar
|
|
2189
|
+
second: {
|
|
2190
|
+
type: "split",
|
|
2191
|
+
direction: "v",
|
|
2192
|
+
ratio: 0.9,
|
|
2193
|
+
first: {
|
|
2194
|
+
type: "split",
|
|
2195
|
+
direction: "h",
|
|
2196
|
+
ratio: 0.4,
|
|
2197
|
+
first: { type: "scrollable", id: "list" },
|
|
2198
|
+
second: { type: "scrollable", id: "detail" },
|
|
2199
|
+
},
|
|
2200
|
+
second: { type: "fixed", id: "footer", height: 1 },
|
|
2201
|
+
},
|
|
2465
2202
|
});
|
|
2466
2203
|
|
|
2467
|
-
|
|
2204
|
+
const groupResult = await editor.createBufferGroup("*Packages*", "pkg-manager", layout);
|
|
2205
|
+
pkgState.groupId = groupResult.groupId;
|
|
2206
|
+
pkgState.panelBuffers = groupResult.panels;
|
|
2468
2207
|
pkgState.isOpen = true;
|
|
2469
2208
|
|
|
2470
|
-
//
|
|
2471
|
-
|
|
2209
|
+
// Set initial content for all panels
|
|
2210
|
+
updatePkgManagerView();
|
|
2472
2211
|
|
|
2473
2212
|
// Sync registry in background and update view when done
|
|
2474
2213
|
// User can still interact with installed packages during sync
|
|
@@ -2486,8 +2225,12 @@ async function openPackageManager(): Promise<void> {
|
|
|
2486
2225
|
function closePackageManager(): void {
|
|
2487
2226
|
if (!pkgState.isOpen) return;
|
|
2488
2227
|
|
|
2489
|
-
// Close the buffer
|
|
2490
|
-
if (pkgState.
|
|
2228
|
+
// Close the buffer group if using the new system
|
|
2229
|
+
if (pkgState.groupId !== null) {
|
|
2230
|
+
editor.closeBufferGroup(pkgState.groupId);
|
|
2231
|
+
pkgState.groupId = null;
|
|
2232
|
+
pkgState.panelBuffers = {};
|
|
2233
|
+
} else if (pkgState.bufferId !== null) {
|
|
2491
2234
|
editor.closeBuffer(pkgState.bufferId);
|
|
2492
2235
|
}
|
|
2493
2236
|
|
|
@@ -2731,6 +2474,17 @@ registerHandler("onPkgSearchConfirmed", onPkgSearchConfirmed);
|
|
|
2731
2474
|
|
|
2732
2475
|
editor.on("prompt_confirmed", "onPkgSearchConfirmed");
|
|
2733
2476
|
|
|
2477
|
+
function on_pkg_resize(): void {
|
|
2478
|
+
if (!pkgState.isOpen) return;
|
|
2479
|
+
const viewport = editor.getViewport();
|
|
2480
|
+
if (viewport) {
|
|
2481
|
+
pkgState.viewportHeight = viewport.height;
|
|
2482
|
+
}
|
|
2483
|
+
updatePkgManagerView();
|
|
2484
|
+
}
|
|
2485
|
+
registerHandler("on_pkg_resize", on_pkg_resize);
|
|
2486
|
+
editor.on("resize", "on_pkg_resize");
|
|
2487
|
+
|
|
2734
2488
|
// Legacy Finder-based UI (kept for backwards compatibility)
|
|
2735
2489
|
const registryFinder = new Finder<[string, RegistryEntry]>(editor, {
|
|
2736
2490
|
id: "pkg-registry",
|