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 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 wizard-style in-app diff viewer to inspect the current per-file diff without leaving Pi.
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 behaves like a simple wizard and opens with a wider centered modal by default:
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 a centered modal
131
- 2. open its patch in a second centered view
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.files[this.selectedIndex];
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
- if (this.files.length === 0) return;
185
+ const files = this.filteredFiles;
186
+ if (files.length === 0) return;
170
187
  this.selectedIndex = Math.max(
171
188
  0,
172
- Math.min(this.files.length - 1, this.selectedIndex + delta),
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
- const modalWidth = Math.max(56, Math.min(width - 6, 112));
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 || !selected) {
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, Math.min(width - 10, 80));
284
+ const innerWidth = Math.max(20, width - 4);
245
285
  const listStart = Math.max(
246
286
  0,
247
- Math.min(this.selectedIndex - 4, this.files.length - 10),
287
+ Math.min(
288
+ this.selectedIndex - 4,
289
+ Math.max(0, filteredFiles.length - 10),
290
+ ),
248
291
  );
249
- const listEntries = this.files.slice(listStart, listStart + 10);
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
- "↑↓ or j/k move · enter/right open · esc/q close",
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osdy-pi",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Osdy Pi package: themes, header, and custom editor for Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",