osdy-pi 0.1.8 → 0.1.9
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 +5 -5
- package/extensions/osdy-pi/diff-panel.ts +76 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ Osdy Pi includes a small command group:
|
|
|
95
95
|
- `sound setup` opens the guided global sound-setup wizard for audio notifications.
|
|
96
96
|
- `working-tree on|off|toggle|status` controls the persistent git working-tree summary widget.
|
|
97
97
|
- `working-tree position top|bottom` moves the summary widget above or below the editor.
|
|
98
|
-
- `diff` opens a
|
|
98
|
+
- `diff` opens a floating centered diff window to inspect the current per-file diff without leaving Pi.
|
|
99
99
|
- `osdy-theme` is the default OsdyTheme header with pink, cyan, and purple styling, plus the mascot glow on the right edge.
|
|
100
100
|
- `classic` keeps the previous classic header shape with the shared mascot.
|
|
101
101
|
- `/osdy-pi-osdy-theme` and `/osdy-pi-classic` are direct aliases.
|
|
@@ -125,15 +125,15 @@ Use these commands to control it:
|
|
|
125
125
|
/osdy-pi diff
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
The `/osdy-pi diff` viewer now
|
|
128
|
+
The `/osdy-pi diff` viewer now opens in the most viable floating-window form for Osdy Pi: a centered floating dialog with a selector step and a patch step.
|
|
129
129
|
|
|
130
|
-
1. select a changed file in
|
|
131
|
-
2. open its patch in
|
|
130
|
+
1. select a changed file in the floating window
|
|
131
|
+
2. open its patch in the same centered floating window
|
|
132
132
|
3. return with `esc` / `backspace` or close with `q`
|
|
133
133
|
|
|
134
134
|
Controls:
|
|
135
135
|
|
|
136
|
-
- selector: `↑` / `↓` or `j` / `k`, then `enter` or `→`
|
|
136
|
+
- selector: type to filter, `↑` / `↓` or `j` / `k`, then `enter` or `→`
|
|
137
137
|
- patch view: `PgUp` / `PgDn`
|
|
138
138
|
- back: `esc` / `backspace` or `←`
|
|
139
139
|
- close: `q`
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
type TUI,
|
|
13
13
|
} from "@earendil-works/pi-tui";
|
|
14
14
|
import { truncateMiddle } from "./format.js";
|
|
15
|
-
import { centerVisible } from "./utils.js";
|
|
16
15
|
import type {
|
|
17
16
|
SimpleTheme,
|
|
18
17
|
WorkingTreeFileSummary,
|
|
@@ -153,6 +152,7 @@ class WorkingTreeDiffPanel implements Component {
|
|
|
153
152
|
private selectedIndex = 0;
|
|
154
153
|
private scrollOffset = 0;
|
|
155
154
|
private mode: "select" | "view" = "select";
|
|
155
|
+
private searchTerm = "";
|
|
156
156
|
|
|
157
157
|
constructor(
|
|
158
158
|
private readonly tui: TUI,
|
|
@@ -161,24 +161,46 @@ class WorkingTreeDiffPanel implements Component {
|
|
|
161
161
|
private readonly close: () => void,
|
|
162
162
|
) {}
|
|
163
163
|
|
|
164
|
+
private get filteredFiles(): DiffFileView[] {
|
|
165
|
+
const query = this.searchTerm.trim().toLowerCase();
|
|
166
|
+
if (!query) return this.files;
|
|
167
|
+
return this.files.filter((entry) =>
|
|
168
|
+
entry.file.path.toLowerCase().includes(query),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
private get selected(): DiffFileView | undefined {
|
|
165
|
-
return this.
|
|
173
|
+
return this.filteredFiles[this.selectedIndex];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private clampSelection(): void {
|
|
177
|
+
const files = this.filteredFiles;
|
|
178
|
+
this.selectedIndex = Math.max(
|
|
179
|
+
0,
|
|
180
|
+
Math.min(Math.max(0, files.length - 1), this.selectedIndex),
|
|
181
|
+
);
|
|
166
182
|
}
|
|
167
183
|
|
|
168
184
|
private moveSelection(delta: number): void {
|
|
169
|
-
|
|
185
|
+
const files = this.filteredFiles;
|
|
186
|
+
if (files.length === 0) return;
|
|
170
187
|
this.selectedIndex = Math.max(
|
|
171
188
|
0,
|
|
172
|
-
Math.min(
|
|
189
|
+
Math.min(files.length - 1, this.selectedIndex + delta),
|
|
173
190
|
);
|
|
174
191
|
this.scrollOffset = 0;
|
|
175
192
|
this.tui.requestRender();
|
|
176
193
|
}
|
|
177
194
|
|
|
195
|
+
private updateSearch(next: string): void {
|
|
196
|
+
this.searchTerm = next;
|
|
197
|
+
this.selectedIndex = 0;
|
|
198
|
+
this.scrollOffset = 0;
|
|
199
|
+
this.tui.requestRender();
|
|
200
|
+
}
|
|
201
|
+
|
|
178
202
|
private renderModal(width: number, title: string, lines: string[]): string[] {
|
|
179
|
-
|
|
180
|
-
const modalLines = borderBox(this.theme, modalWidth, title, lines);
|
|
181
|
-
return ["", ...modalLines.map((line) => centerVisible(line, width)), ""];
|
|
203
|
+
return borderBox(this.theme, width, title, lines);
|
|
182
204
|
}
|
|
183
205
|
|
|
184
206
|
handleInput(data: string): void {
|
|
@@ -188,9 +210,19 @@ class WorkingTreeDiffPanel implements Component {
|
|
|
188
210
|
}
|
|
189
211
|
if (this.mode === "select") {
|
|
190
212
|
if (matchesKey(data, "escape")) {
|
|
213
|
+
if (this.searchTerm.length > 0) {
|
|
214
|
+
this.updateSearch("");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
191
217
|
this.close();
|
|
192
218
|
return;
|
|
193
219
|
}
|
|
220
|
+
if (matchesKey(data, "backspace")) {
|
|
221
|
+
if (this.searchTerm.length > 0) {
|
|
222
|
+
this.updateSearch(this.searchTerm.slice(0, -1));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
194
226
|
if (matchesKey(data, "up") || matchesKey(data, "k")) {
|
|
195
227
|
this.moveSelection(-1);
|
|
196
228
|
return;
|
|
@@ -205,9 +237,15 @@ class WorkingTreeDiffPanel implements Component {
|
|
|
205
237
|
matchesKey(data, "right") ||
|
|
206
238
|
matchesKey(data, "l")
|
|
207
239
|
) {
|
|
240
|
+
if (this.filteredFiles.length === 0) return;
|
|
208
241
|
this.mode = "view";
|
|
209
242
|
this.scrollOffset = 0;
|
|
210
243
|
this.tui.requestRender();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (data.length === 1 && data >= " " && data !== "\u007f") {
|
|
247
|
+
this.updateSearch(`${this.searchTerm}${data}`);
|
|
248
|
+
return;
|
|
211
249
|
}
|
|
212
250
|
return;
|
|
213
251
|
}
|
|
@@ -233,20 +271,25 @@ class WorkingTreeDiffPanel implements Component {
|
|
|
233
271
|
}
|
|
234
272
|
|
|
235
273
|
render(width: number): string[] {
|
|
274
|
+
this.clampSelection();
|
|
275
|
+
const filteredFiles = this.filteredFiles;
|
|
236
276
|
const selected = this.selected;
|
|
237
|
-
if (this.files.length === 0
|
|
277
|
+
if (this.files.length === 0) {
|
|
238
278
|
return this.renderModal(width, "Osdy Pi Diff", [
|
|
239
279
|
this.theme.fg("muted", "q or esc to close"),
|
|
240
280
|
this.theme.fg("warning", "No tracked changes available."),
|
|
241
281
|
]);
|
|
242
282
|
}
|
|
243
283
|
if (this.mode === "select") {
|
|
244
|
-
const innerWidth = Math.max(20,
|
|
284
|
+
const innerWidth = Math.max(20, width - 4);
|
|
245
285
|
const listStart = Math.max(
|
|
246
286
|
0,
|
|
247
|
-
Math.min(
|
|
287
|
+
Math.min(
|
|
288
|
+
this.selectedIndex - 4,
|
|
289
|
+
Math.max(0, filteredFiles.length - 10),
|
|
290
|
+
),
|
|
248
291
|
);
|
|
249
|
-
const listEntries =
|
|
292
|
+
const listEntries = filteredFiles.slice(listStart, listStart + 10);
|
|
250
293
|
const listLines = listEntries.map((entry, index) => {
|
|
251
294
|
const fileIndex = listStart + index;
|
|
252
295
|
const isSelected = fileIndex === this.selectedIndex;
|
|
@@ -264,13 +307,22 @@ class WorkingTreeDiffPanel implements Component {
|
|
|
264
307
|
return this.renderModal(width, "Osdy Pi Diff · Select file", [
|
|
265
308
|
this.theme.fg(
|
|
266
309
|
"muted",
|
|
267
|
-
"
|
|
310
|
+
"Type to filter · ↑↓ move · enter/right open · esc clear/close",
|
|
268
311
|
),
|
|
312
|
+
`${this.theme.fg("accent", "Search:")} ${this.theme.fg("toolOutput", this.searchTerm || "")}${this.searchTerm.length === 0 ? this.theme.fg("muted", "(all files)") : ""}`,
|
|
269
313
|
this.theme.fg(
|
|
270
314
|
"mdLink",
|
|
271
|
-
`Files ${this.selectedIndex + 1}/${this.files.length}`,
|
|
315
|
+
`Files ${filteredFiles.length === 0 ? 0 : this.selectedIndex + 1}/${filteredFiles.length} · total ${this.files.length}`,
|
|
272
316
|
),
|
|
273
|
-
...listLines
|
|
317
|
+
...(listLines.length > 0
|
|
318
|
+
? listLines
|
|
319
|
+
: [this.theme.fg("warning", "No files match the current search.")]),
|
|
320
|
+
]);
|
|
321
|
+
}
|
|
322
|
+
if (!selected) {
|
|
323
|
+
return this.renderModal(width, "Osdy Pi Diff", [
|
|
324
|
+
this.theme.fg("muted", "esc to go back"),
|
|
325
|
+
this.theme.fg("warning", "No file selected."),
|
|
274
326
|
]);
|
|
275
327
|
}
|
|
276
328
|
const patchLines =
|
|
@@ -315,5 +367,15 @@ export async function showWorkingTreeDiffPanel(
|
|
|
315
367
|
await ctx.ui.custom<void>(
|
|
316
368
|
(tui, theme, _keybindings, done) =>
|
|
317
369
|
new WorkingTreeDiffPanel(tui, theme, files, () => done()),
|
|
370
|
+
{
|
|
371
|
+
overlay: true,
|
|
372
|
+
overlayOptions: {
|
|
373
|
+
anchor: "center",
|
|
374
|
+
width: 112,
|
|
375
|
+
minWidth: 56,
|
|
376
|
+
maxHeight: "88%",
|
|
377
|
+
margin: 1,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
318
380
|
);
|
|
319
381
|
}
|