22import { useTerminalDimensions , type JSX } from "@opentui/solid"
33import { useBindings , useKeymapSelector } from "@opentui/keymap/solid"
44import { RGBA , VignetteEffect , type KeyEvent , type Renderable } from "@opentui/core"
5- import { resolveBindingSections , type BindingSectionsConfig , type BindingValue } from "@opentui/keymap/extras"
6- import type { Binding } from "@opentui/keymap"
5+ import { createBindingLookup , type BindingConfig } from "@opentui/keymap/extras"
76import type { TuiPlugin , TuiPluginApi , TuiPluginMeta , TuiPluginModule , TuiSlotPlugin } from "@opencode-ai/plugin/tui"
87
98const tabs = [ "overview" , "counter" , "help" ]
109const command = {
11- modal : "plugin.smoke.modal" ,
12- screen : "plugin.smoke.screen" ,
13- alert : "plugin.smoke.alert" ,
14- confirm : "plugin.smoke.confirm" ,
15- prompt : "plugin.smoke.prompt" ,
16- select : "plugin.smoke.select" ,
17- host : "plugin.smoke.host" ,
18- home : "plugin.smoke.home" ,
19- toast : "plugin.smoke.toast" ,
20- dialog_close : "plugin.smoke.dialog.close" ,
21- local_push : "plugin.smoke.local.push" ,
22- local_pop : "plugin.smoke.local.pop" ,
23- screen_home : "plugin.smoke.screen.home" ,
24- screen_left : "plugin.smoke.screen.left" ,
25- screen_right : "plugin.smoke.screen.right" ,
26- screen_up : "plugin.smoke.screen.up" ,
27- screen_down : "plugin.smoke.screen.down" ,
28- screen_modal : "plugin.smoke.screen.modal" ,
29- screen_local : "plugin.smoke.screen.local" ,
30- screen_host : "plugin.smoke.screen.host" ,
31- screen_alert : "plugin.smoke.screen.alert" ,
32- screen_confirm : "plugin.smoke.screen.confirm" ,
33- screen_prompt : "plugin.smoke.screen.prompt" ,
34- screen_select : "plugin.smoke.screen.select" ,
35- modal_accept : "plugin.smoke.modal.accept" ,
36- modal_close : "plugin.smoke.modal.close" ,
37- } as const
38-
39- const sectionNames = [ "global" , "dialog" , "local" , "screen" , "modal" ] as const
40- type SectionName = ( typeof sectionNames ) [ number ]
41- type SectionConfig = Record < string , BindingValue < Renderable , KeyEvent > >
42- type ResolvedSections = Record < SectionName , Binding < Renderable , KeyEvent > [ ] >
43- type SmokeKeymap = {
44- sections ?: Partial < Record < SectionName , SectionConfig > >
10+ modal : "smoke_modal" ,
11+ screen : "smoke_screen" ,
12+ alert : "smoke_alert" ,
13+ confirm : "smoke_confirm" ,
14+ prompt : "smoke_prompt" ,
15+ select : "smoke_select" ,
16+ host : "smoke_host" ,
17+ home : "smoke_home" ,
18+ toast : "smoke_toast" ,
19+ dialog_close : "smoke_dialog_close" ,
20+ local_push : "smoke_local_push" ,
21+ local_pop : "smoke_local_pop" ,
22+ screen_home : "smoke_screen_home" ,
23+ screen_left : "smoke_screen_left" ,
24+ screen_right : "smoke_screen_right" ,
25+ screen_up : "smoke_screen_up" ,
26+ screen_down : "smoke_screen_down" ,
27+ screen_modal : "smoke_screen_modal" ,
28+ screen_local : "smoke_screen_local" ,
29+ screen_host : "smoke_screen_host" ,
30+ screen_alert : "smoke_screen_alert" ,
31+ screen_confirm : "smoke_screen_confirm" ,
32+ screen_prompt : "smoke_screen_prompt" ,
33+ screen_select : "smoke_screen_select" ,
34+ modal_accept : "smoke_modal_accept" ,
35+ modal_close : "smoke_modal_close" ,
4536}
4637
47- type SmokeOptions = {
48- enabled ?: boolean
49- label ?: unknown
50- route ?: unknown
51- vignette ?: unknown
52- keymap ?: SmokeKeymap
53- }
38+ type SmokeBindings = BindingConfig < Renderable , KeyEvent >
5439
5540const defaultKeymap = {
56- global : {
57- [ command . modal ] : "ctrl+shift+m" ,
58- [ command . screen ] : "ctrl+shift+o" ,
59- } ,
60- dialog : {
61- [ command . dialog_close ] : "escape" ,
62- } ,
63- local : {
64- [ command . local_push ] : "enter,return" ,
65- [ command . local_pop ] : "escape,q,backspace" ,
66- } ,
67- screen : {
68- [ command . screen_home ] : "escape,ctrl+h" ,
69- [ command . screen_left ] : "left,h" ,
70- [ command . screen_right ] : "right,l" ,
71- [ command . screen_up ] : "up,k" ,
72- [ command . screen_down ] : "down,j" ,
73- [ command . screen_modal ] : "ctrl+shift+m" ,
74- [ command . screen_local ] : "x" ,
75- [ command . screen_host ] : "z" ,
76- [ command . screen_alert ] : "a" ,
77- [ command . screen_confirm ] : "c" ,
78- [ command . screen_prompt ] : "p" ,
79- [ command . screen_select ] : "s" ,
80- } ,
81- modal : {
82- [ command . modal_accept ] : "enter,return" ,
83- [ command . modal_close ] : "escape" ,
84- } ,
85- } satisfies Record < SectionName , SectionConfig >
41+ [ command . modal ] : "ctrl+shift+m" ,
42+ [ command . screen ] : "ctrl+shift+o" ,
43+ [ command . dialog_close ] : "escape" ,
44+ [ command . local_push ] : "enter,return" ,
45+ [ command . local_pop ] : "escape,q,backspace" ,
46+ [ command . screen_home ] : "escape,ctrl+h" ,
47+ [ command . screen_left ] : "left,h" ,
48+ [ command . screen_right ] : "right,l" ,
49+ [ command . screen_up ] : "up,k" ,
50+ [ command . screen_down ] : "down,j" ,
51+ [ command . screen_modal ] : "ctrl+shift+m" ,
52+ [ command . screen_local ] : "x" ,
53+ [ command . screen_host ] : "z" ,
54+ [ command . screen_alert ] : "a" ,
55+ [ command . screen_confirm ] : "c" ,
56+ [ command . screen_prompt ] : "p" ,
57+ [ command . screen_select ] : "s" ,
58+ [ command . modal_accept ] : "enter,return" ,
59+ [ command . modal_close ] : "escape" ,
60+ }
8661
8762const pick = ( value : unknown , fallback : string ) => {
8863 if ( typeof value !== "string" ) return fallback
@@ -95,11 +70,14 @@ const num = (value: unknown, fallback: number) => {
9570 return value
9671}
9772
73+ const record = ( value : unknown ) : value is Record < string , unknown > =>
74+ ! ! value && typeof value === "object" && ! Array . isArray ( value )
75+
9876type Cfg = {
9977 label : string
10078 route : string
10179 vignette : number
102- keymap : SmokeKeymap | undefined
80+ keybinds : SmokeBindings | undefined
10381}
10482
10583type Route = {
@@ -116,12 +94,12 @@ type State = {
11694 local : number
11795}
11896
119- const cfg = ( options : SmokeOptions | undefined ) => {
97+ const cfg = ( options : Record < string , unknown > | undefined ) => {
12098 return {
12199 label : pick ( options ?. label , "smoke" ) ,
122100 route : pick ( options ?. route , "workspace-smoke" ) ,
123101 vignette : Math . max ( 0 , num ( options ?. vignette , 0.35 ) ) ,
124- keymap : options ?. keymap ,
102+ keybinds : record ( options ?. keybinds ) ? ( options . keybinds as SmokeBindings ) : undefined ,
125103 }
126104}
127105
@@ -132,21 +110,8 @@ const names = (input: Cfg) => {
132110 }
133111}
134112
135- function createKeys ( input : SmokeKeymap | undefined ) : { sections : ResolvedSections } {
136- const sections = resolveBindingSections (
137- {
138- global : { ...defaultKeymap . global , ...input ?. sections ?. global } ,
139- dialog : { ...defaultKeymap . dialog , ...input ?. sections ?. dialog } ,
140- local : { ...defaultKeymap . local , ...input ?. sections ?. local } ,
141- screen : { ...defaultKeymap . screen , ...input ?. sections ?. screen } ,
142- modal : { ...defaultKeymap . modal , ...input ?. sections ?. modal } ,
143- } satisfies BindingSectionsConfig < Renderable , KeyEvent > ,
144- { sections : sectionNames } ,
145- ) . sections
146-
147- return {
148- sections,
149- }
113+ function createKeys ( input : SmokeBindings | undefined ) {
114+ return createBindingLookup ( { ...defaultKeymap , ...input } )
150115}
151116
152117type Keys = ReturnType < typeof createKeys >
@@ -376,7 +341,7 @@ const Screen = (props: {
376341 } ,
377342 } ,
378343 ] ,
379- bindings : props . keys . sections . dialog ,
344+ bindings : props . keys . gather ( "smoke .dialog" , [ command . dialog_close ] ) ,
380345 } ) )
381346
382347 useBindings ( ( ) => ( {
@@ -395,7 +360,7 @@ const Screen = (props: {
395360 } ,
396361 } ,
397362 ] ,
398- bindings : props . keys . sections . local ,
363+ bindings : props . keys . gather ( "smoke .local" , [ command . local_push , command . local_pop ] ) ,
399364 } ) )
400365
401366 useBindings ( ( ) => ( {
@@ -478,7 +443,20 @@ const Screen = (props: {
478443 } ,
479444 } ,
480445 ] ,
481- bindings : props . keys . sections . screen ,
446+ bindings : props . keys . gather ( "smoke.screen" , [
447+ command . screen_home ,
448+ command . screen_left ,
449+ command . screen_right ,
450+ command . screen_up ,
451+ command . screen_down ,
452+ command . screen_modal ,
453+ command . screen_local ,
454+ command . screen_host ,
455+ command . screen_alert ,
456+ command . screen_confirm ,
457+ command . screen_prompt ,
458+ command . screen_select ,
459+ ] ) ,
482460 } ) )
483461 const shortcuts = useKeymapSelector ( ( keymap ) => {
484462 const bindings = keymap . getCommandBindings ( {
@@ -687,7 +665,7 @@ const Modal = (props: {
687665 } ,
688666 } ,
689667 ] ,
690- bindings : props . keys . sections . modal ,
668+ bindings : props . keys . gather ( "smoke .modal" , [ command . modal_accept , command . modal_close ] ) ,
691669 } ) )
692670 const shortcuts = useKeymapSelector ( ( keymap ) => {
693671 const bindings = keymap . getCommandBindings ( {
@@ -766,25 +744,8 @@ const home = (api: TuiPluginApi, input: Cfg) => ({
766744 } ,
767745 home_prompt ( ctx , value ) {
768746 const skin = look ( ctx . theme . current )
769- type Prompt = ( props : {
770- workspaceID ?: string
771- visible ?: boolean
772- disabled ?: boolean
773- onSubmit ?: ( ) => void
774- hint ?: JSX . Element
775- right ?: JSX . Element
776- showPlaceholder ?: boolean
777- placeholders ?: {
778- normal ?: string [ ]
779- shell ?: string [ ]
780- }
781- } ) => JSX . Element
782- type Slot = (
783- props : { name : string ; mode ?: unknown ; children ?: JSX . Element } & Record < string , unknown > ,
784- ) => JSX . Element | null
785- const ui = api . ui as TuiPluginApi [ "ui" ] & { Prompt : Prompt ; Slot : Slot }
786- const Prompt = ui . Prompt
787- const Slot = ui . Slot
747+ const Prompt = api . ui . Prompt
748+ const Slot = api . ui . Slot
788749 const normal = [
789750 `[SMOKE] route check for ${ input . label } ` ,
790751 "[SMOKE] confirm home_prompt slot override" ,
@@ -1003,20 +964,29 @@ const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => {
1003964 } ,
1004965 } ,
1005966 ] ,
1006- bindings : keys . sections . global ,
967+ bindings : keys . gather ( "smoke.global" , [
968+ command . modal ,
969+ command . screen ,
970+ command . alert ,
971+ command . confirm ,
972+ command . prompt ,
973+ command . select ,
974+ command . host ,
975+ command . home ,
976+ command . toast ,
977+ ] ) ,
1007978 } )
1008979}
1009980
1010981const tui : TuiPlugin = async ( api , options , meta ) => {
1011- const input = options as SmokeOptions | undefined
1012- if ( input ?. enabled === false ) return
982+ if ( options ?. enabled === false ) return
1013983
1014984 await api . theme . install ( "./smoke-theme.json" )
1015985 api . theme . set ( "smoke-theme" )
1016986
1017- const value = cfg ( input )
987+ const value = cfg ( options )
1018988 const route = names ( value )
1019- const keys = createKeys ( value . keymap )
989+ const keys = createKeys ( value . keybinds )
1020990 const fx = new VignetteEffect ( value . vignette )
1021991 const post = fx . apply . bind ( fx )
1022992 api . renderer . addPostProcessFn ( post )
0 commit comments