@@ -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