Skip to content

Commit bcd3c76

Browse files
fix(pi-fff): make FffEditor safe under composability subclassing (#436)
When pi-vim or pi-image-attachments wrap a previously-installed editor by extracting `probe.constructor` and constructing `new SubClass(tui, theme, keybindings)`, the 4th argument FffEditor relied on for `getMentionItems` gets dropped. The mention provider closes over `undefined` and throws `TypeError: getItems is not a function` the first time the user types `@`. Move the FffEditor class definition inside fffExtension() so the override captures `getMentionItems` via closure rather than via a constructor parameter. The factory now matches the standard `(tui, theme, keybindings)` shape, so any wrapper that subclasses FffEditor inherits a working mention provider regardless of construction args. Refs: earendil-works/pi#3935 Co-authored-by: Kyle Snow Schwartz <kylesnowschwartz@users.noreply.github.com>
1 parent ca7bf03 commit bcd3c76

1 file changed

Lines changed: 64 additions & 71 deletions

File tree

packages/pi-fff/src/index.ts

Lines changed: 64 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -279,76 +279,11 @@ function createFffMentionProvider(
279279
};
280280
}
281281

282-
// Simple editor wrapper that injects FFF @-mention autocomplete alongside base provider
283-
class FffEditor extends CustomEditor {
284-
private baseProvider: AutocompleteProvider | undefined;
285-
private getMentionItems: (
286-
query: string,
287-
signal: AbortSignal,
288-
) => Promise<AutocompleteItem[]>;
289-
290-
constructor(
291-
tui: any,
292-
theme: any,
293-
keybindings: any,
294-
getMentionItems: (
295-
query: string,
296-
signal: AbortSignal,
297-
) => Promise<AutocompleteItem[]>,
298-
) {
299-
super(tui, theme, keybindings);
300-
this.getMentionItems = getMentionItems;
301-
}
302-
303-
override setAutocompleteProvider(provider: AutocompleteProvider): void {
304-
this.baseProvider = provider;
305-
// Create composite provider that handles @-mentions and falls back to base
306-
const mentionProvider = createFffMentionProvider(this.getMentionItems);
307-
const compositeProvider: AutocompleteProvider = {
308-
getSuggestions: async (lines, cursorLine, cursorCol, options) => {
309-
// Try @-mention first
310-
const mentionResult = await mentionProvider.getSuggestions(
311-
lines,
312-
cursorLine,
313-
cursorCol,
314-
options,
315-
);
316-
if (mentionResult) return mentionResult;
317-
// Fall back to base provider
318-
return (
319-
this.baseProvider?.getSuggestions(
320-
lines,
321-
cursorLine,
322-
cursorCol,
323-
options,
324-
) ?? null
325-
);
326-
},
327-
applyCompletion: (lines, cursorLine, cursorCol, item, prefix) => {
328-
// Let mention provider handle @ completions, base provider for others
329-
if (prefix?.startsWith("@")) {
330-
return mentionProvider.applyCompletion!(
331-
lines,
332-
cursorLine,
333-
cursorCol,
334-
item,
335-
prefix,
336-
);
337-
}
338-
return (
339-
this.baseProvider?.applyCompletion?.(
340-
lines,
341-
cursorLine,
342-
cursorCol,
343-
item,
344-
prefix,
345-
) ?? { lines, cursorLine, cursorCol }
346-
);
347-
},
348-
};
349-
super.setAutocompleteProvider(compositeProvider);
350-
}
351-
}
282+
// FffEditor is defined inside fffExtension() so it can capture `getMentionItems`
283+
// via closure rather than via a 4th constructor parameter. This makes the class
284+
// safe to subclass via `new SubClass(tui, theme, keybindings)` -- the pattern
285+
// pi-vim and pi-image-attachments use to compose editors. See:
286+
// https://github.com/badlogic/pi-mono/issues/3935
352287

353288
// ---------------------------------------------------------------------------
354289
// Extension
@@ -450,6 +385,64 @@ export default function fffExtension(pi: ExtensionAPI) {
450385
});
451386
}
452387

388+
// Editor wrapper that injects FFF @-mention autocomplete alongside base provider.
389+
// Defined inside fffExtension() so the class methods capture `getMentionItems`
390+
// via closure. Subclasses constructed as `new Sub(tui, theme, keybindings)` by
391+
// composability wrappers (pi-vim, pi-image-attachments) still get a working
392+
// mention provider because the closure binding is preserved across subclassing.
393+
class FffEditor extends CustomEditor {
394+
private baseProvider: AutocompleteProvider | undefined;
395+
396+
override setAutocompleteProvider(provider: AutocompleteProvider): void {
397+
this.baseProvider = provider;
398+
// Create composite provider that handles @-mentions and falls back to base
399+
const mentionProvider = createFffMentionProvider(getMentionItems);
400+
const compositeProvider: AutocompleteProvider = {
401+
getSuggestions: async (lines, cursorLine, cursorCol, options) => {
402+
// Try @-mention first
403+
const mentionResult = await mentionProvider.getSuggestions(
404+
lines,
405+
cursorLine,
406+
cursorCol,
407+
options,
408+
);
409+
if (mentionResult) return mentionResult;
410+
// Fall back to base provider
411+
return (
412+
this.baseProvider?.getSuggestions(
413+
lines,
414+
cursorLine,
415+
cursorCol,
416+
options,
417+
) ?? null
418+
);
419+
},
420+
applyCompletion: (lines, cursorLine, cursorCol, item, prefix) => {
421+
// Let mention provider handle @ completions, base provider for others
422+
if (prefix?.startsWith("@")) {
423+
return mentionProvider.applyCompletion!(
424+
lines,
425+
cursorLine,
426+
cursorCol,
427+
item,
428+
prefix,
429+
);
430+
}
431+
return (
432+
this.baseProvider?.applyCompletion?.(
433+
lines,
434+
cursorLine,
435+
cursorCol,
436+
item,
437+
prefix,
438+
) ?? { lines, cursorLine, cursorCol }
439+
);
440+
},
441+
};
442+
super.setAutocompleteProvider(compositeProvider);
443+
}
444+
}
445+
453446
function applyEditorMode(ctx: {
454447
ui: {
455448
setEditorComponent: (
@@ -462,7 +455,7 @@ export default function fffExtension(pi: ExtensionAPI) {
462455
} else {
463456
ctx.ui.setEditorComponent(
464457
(tui: any, theme: any, keybindings: any) =>
465-
new FffEditor(tui, theme, keybindings, getMentionItems),
458+
new FffEditor(tui, theme, keybindings),
466459
);
467460
}
468461
}

0 commit comments

Comments
 (0)