@navios/commander-tui 1.0.0

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.
Files changed (348) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +275 -0
  3. package/coverage/__tests__/utils/factories.ts.html +1147 -0
  4. package/coverage/__tests__/utils/index.html +131 -0
  5. package/coverage/__tests__/utils/render-utils.tsx.html +202 -0
  6. package/coverage/base.css +224 -0
  7. package/coverage/block-navigation.js +87 -0
  8. package/coverage/clover.xml +959 -0
  9. package/coverage/components/filter/filter_bar.tsx.html +322 -0
  10. package/coverage/components/filter/index.html +116 -0
  11. package/coverage/components/log/index.html +131 -0
  12. package/coverage/components/log/index.ts.html +88 -0
  13. package/coverage/components/log/log_message.tsx.html +391 -0
  14. package/coverage/components/prompt/index.html +116 -0
  15. package/coverage/components/prompt/prompt_renderer.tsx.html +1123 -0
  16. package/coverage/components/screen/index.html +131 -0
  17. package/coverage/components/screen/loading_message.tsx.html +217 -0
  18. package/coverage/components/screen/progress_message.tsx.html +265 -0
  19. package/coverage/components/sidebar/index.html +146 -0
  20. package/coverage/components/sidebar/sidebar.tsx.html +391 -0
  21. package/coverage/components/sidebar/sidebar_item.tsx.html +235 -0
  22. package/coverage/components/sidebar/sidebar_separator.tsx.html +124 -0
  23. package/coverage/context/index.html +131 -0
  24. package/coverage/context/index.ts.html +88 -0
  25. package/coverage/context/logger_context.tsx.html +412 -0
  26. package/coverage/coverage-final.json +55 -0
  27. package/coverage/favicon.png +0 -0
  28. package/coverage/filter/filter_engine.ts.html +424 -0
  29. package/coverage/filter/index.html +116 -0
  30. package/coverage/hooks/index.html +131 -0
  31. package/coverage/hooks/index.ts.html +88 -0
  32. package/coverage/hooks/use_theme.ts.html +121 -0
  33. package/coverage/index.html +356 -0
  34. package/coverage/keyboard/index.html +116 -0
  35. package/coverage/keyboard/keyboard_manager.ts.html +784 -0
  36. package/coverage/prettify.css +1 -0
  37. package/coverage/prettify.js +2 -0
  38. package/coverage/schemas/index.html +161 -0
  39. package/coverage/schemas/index.ts.html +94 -0
  40. package/coverage/schemas/logger-options.ts.html +124 -0
  41. package/coverage/schemas/prompt-options.ts.html +112 -0
  42. package/coverage/schemas/screen-options.ts.html +127 -0
  43. package/coverage/services/index.html +146 -0
  44. package/coverage/services/logger.ts.html +1192 -0
  45. package/coverage/services/prompt.ts.html +568 -0
  46. package/coverage/services/screen.ts.html +1804 -0
  47. package/coverage/sort-arrow-sprite.png +0 -0
  48. package/coverage/sorter.js +210 -0
  49. package/coverage/themes/dark.ts.html +604 -0
  50. package/coverage/themes/high-contrast.ts.html +619 -0
  51. package/coverage/themes/index.html +176 -0
  52. package/coverage/themes/index.ts.html +97 -0
  53. package/coverage/themes/light.ts.html +601 -0
  54. package/coverage/themes/utils.ts.html +334 -0
  55. package/coverage/tokens/index.html +161 -0
  56. package/coverage/tokens/index.ts.html +94 -0
  57. package/coverage/tokens/logger.ts.html +115 -0
  58. package/coverage/tokens/prompt.ts.html +115 -0
  59. package/coverage/tokens/screen.ts.html +115 -0
  60. package/coverage/types/file.types.ts.html +265 -0
  61. package/coverage/types/filter.types.ts.html +238 -0
  62. package/coverage/types/index.html +236 -0
  63. package/coverage/types/index.ts.html +151 -0
  64. package/coverage/types/keyboard.types.ts.html +364 -0
  65. package/coverage/types/log.types.ts.html +268 -0
  66. package/coverage/types/message.types.ts.html +445 -0
  67. package/coverage/types/prompt.types.ts.html +403 -0
  68. package/coverage/types/screen.types.ts.html +451 -0
  69. package/coverage/types/theme.types.ts.html +841 -0
  70. package/coverage/utils/colors/file-colors.ts.html +112 -0
  71. package/coverage/utils/colors/helpers.ts.html +235 -0
  72. package/coverage/utils/colors/index.html +221 -0
  73. package/coverage/utils/colors/index.ts.html +145 -0
  74. package/coverage/utils/colors/log-colors.ts.html +253 -0
  75. package/coverage/utils/colors/progress-colors.ts.html +160 -0
  76. package/coverage/utils/colors/prompt-colors.ts.html +175 -0
  77. package/coverage/utils/colors/sidebar-colors.ts.html +241 -0
  78. package/coverage/utils/colors/table-colors.ts.html +118 -0
  79. package/coverage/utils/filetype.ts.html +277 -0
  80. package/coverage/utils/format.ts.html +241 -0
  81. package/coverage/utils/index.html +161 -0
  82. package/coverage/utils/index.ts.html +118 -0
  83. package/coverage/utils/stdout-printer.ts.html +523 -0
  84. package/dist/src/components/file/file_log.d.ts +35 -0
  85. package/dist/src/components/file/file_log.d.ts.map +1 -0
  86. package/dist/src/components/file/index.d.ts +2 -0
  87. package/dist/src/components/file/index.d.ts.map +1 -0
  88. package/dist/src/components/filter/filter_bar.d.ts +10 -0
  89. package/dist/src/components/filter/filter_bar.d.ts.map +1 -0
  90. package/dist/src/components/filter/index.d.ts +2 -0
  91. package/dist/src/components/filter/index.d.ts.map +1 -0
  92. package/dist/src/components/help/help_overlay.d.ts +10 -0
  93. package/dist/src/components/help/help_overlay.d.ts.map +1 -0
  94. package/dist/src/components/help/index.d.ts +2 -0
  95. package/dist/src/components/help/index.d.ts.map +1 -0
  96. package/dist/src/components/index.d.ts +27 -0
  97. package/dist/src/components/index.d.ts.map +1 -0
  98. package/dist/src/components/log/debug_log.d.ts +3 -0
  99. package/dist/src/components/log/debug_log.d.ts.map +1 -0
  100. package/dist/src/components/log/error_log.d.ts +3 -0
  101. package/dist/src/components/log/error_log.d.ts.map +1 -0
  102. package/dist/src/components/log/fatal_log.d.ts +3 -0
  103. package/dist/src/components/log/fatal_log.d.ts.map +1 -0
  104. package/dist/src/components/log/index.d.ts +9 -0
  105. package/dist/src/components/log/index.d.ts.map +1 -0
  106. package/dist/src/components/log/info_log.d.ts +3 -0
  107. package/dist/src/components/log/info_log.d.ts.map +1 -0
  108. package/dist/src/components/log/log_message.d.ts +33 -0
  109. package/dist/src/components/log/log_message.d.ts.map +1 -0
  110. package/dist/src/components/log/success_log.d.ts +3 -0
  111. package/dist/src/components/log/success_log.d.ts.map +1 -0
  112. package/dist/src/components/log/trace_log.d.ts +3 -0
  113. package/dist/src/components/log/trace_log.d.ts.map +1 -0
  114. package/dist/src/components/log/warning_log.d.ts +3 -0
  115. package/dist/src/components/log/warning_log.d.ts.map +1 -0
  116. package/dist/src/components/prompt/index.d.ts +3 -0
  117. package/dist/src/components/prompt/index.d.ts.map +1 -0
  118. package/dist/src/components/prompt/prompt_renderer.d.ts +6 -0
  119. package/dist/src/components/prompt/prompt_renderer.d.ts.map +1 -0
  120. package/dist/src/components/screen/group_renderer.d.ts +19 -0
  121. package/dist/src/components/screen/group_renderer.d.ts.map +1 -0
  122. package/dist/src/components/screen/index.d.ts +13 -0
  123. package/dist/src/components/screen/index.d.ts.map +1 -0
  124. package/dist/src/components/screen/loading_message.d.ts +6 -0
  125. package/dist/src/components/screen/loading_message.d.ts.map +1 -0
  126. package/dist/src/components/screen/message_renderer.d.ts +8 -0
  127. package/dist/src/components/screen/message_renderer.d.ts.map +1 -0
  128. package/dist/src/components/screen/progress_message.d.ts +8 -0
  129. package/dist/src/components/screen/progress_message.d.ts.map +1 -0
  130. package/dist/src/components/screen/screen_bridge.d.ts +20 -0
  131. package/dist/src/components/screen/screen_bridge.d.ts.map +1 -0
  132. package/dist/src/components/screen/table_message.d.ts +6 -0
  133. package/dist/src/components/screen/table_message.d.ts.map +1 -0
  134. package/dist/src/components/screen_manager_bridge.d.ts +11 -0
  135. package/dist/src/components/screen_manager_bridge.d.ts.map +1 -0
  136. package/dist/src/components/sidebar/index.d.ts +6 -0
  137. package/dist/src/components/sidebar/index.d.ts.map +1 -0
  138. package/dist/src/components/sidebar/sidebar.d.ts +18 -0
  139. package/dist/src/components/sidebar/sidebar.d.ts.map +1 -0
  140. package/dist/src/components/sidebar/sidebar_item.d.ts +14 -0
  141. package/dist/src/components/sidebar/sidebar_item.d.ts.map +1 -0
  142. package/dist/src/components/sidebar/sidebar_separator.d.ts +2 -0
  143. package/dist/src/components/sidebar/sidebar_separator.d.ts.map +1 -0
  144. package/dist/src/context/logger_context.d.ts +60 -0
  145. package/dist/src/context/logger_context.d.ts.map +1 -0
  146. package/dist/src/filter/filter_engine.d.ts +20 -0
  147. package/dist/src/filter/filter_engine.d.ts.map +1 -0
  148. package/dist/src/filter/index.d.ts +4 -0
  149. package/dist/src/filter/index.d.ts.map +1 -0
  150. package/dist/src/hooks/index.d.ts +2 -0
  151. package/dist/src/hooks/index.d.ts.map +1 -0
  152. package/dist/src/hooks/use_theme.d.ts +7 -0
  153. package/dist/src/hooks/use_theme.d.ts.map +1 -0
  154. package/dist/src/index.d.ts +88 -0
  155. package/dist/src/index.d.ts.map +1 -0
  156. package/dist/src/keyboard/create_bindings.d.ts +31 -0
  157. package/dist/src/keyboard/create_bindings.d.ts.map +1 -0
  158. package/dist/src/keyboard/index.d.ts +12 -0
  159. package/dist/src/keyboard/index.d.ts.map +1 -0
  160. package/dist/src/keyboard/keyboard_manager.d.ts +69 -0
  161. package/dist/src/keyboard/keyboard_manager.d.ts.map +1 -0
  162. package/dist/src/services/index.d.ts +5 -0
  163. package/dist/src/services/index.d.ts.map +1 -0
  164. package/dist/src/services/logger.d.ts +61 -0
  165. package/dist/src/services/logger.d.ts.map +1 -0
  166. package/dist/src/services/prompt.d.ts +37 -0
  167. package/dist/src/services/prompt.d.ts.map +1 -0
  168. package/dist/src/services/screen.d.ts +165 -0
  169. package/dist/src/services/screen.d.ts.map +1 -0
  170. package/dist/src/services/screen_manager.d.ts +124 -0
  171. package/dist/src/services/screen_manager.d.ts.map +1 -0
  172. package/dist/src/themes/dark.d.ts +7 -0
  173. package/dist/src/themes/dark.d.ts.map +1 -0
  174. package/dist/src/themes/high-contrast.d.ts +7 -0
  175. package/dist/src/themes/high-contrast.d.ts.map +1 -0
  176. package/dist/src/themes/index.d.ts +18 -0
  177. package/dist/src/themes/index.d.ts.map +1 -0
  178. package/dist/src/themes/light.d.ts +6 -0
  179. package/dist/src/themes/light.d.ts.map +1 -0
  180. package/dist/src/themes/utils.d.ts +22 -0
  181. package/dist/src/themes/utils.d.ts.map +1 -0
  182. package/dist/src/types/file.types.d.ts +40 -0
  183. package/dist/src/types/file.types.d.ts.map +1 -0
  184. package/dist/src/types/filter.types.d.ts +31 -0
  185. package/dist/src/types/filter.types.d.ts.map +1 -0
  186. package/dist/src/types/index.d.ts +81 -0
  187. package/dist/src/types/index.d.ts.map +1 -0
  188. package/dist/src/types/keyboard.types.d.ts +83 -0
  189. package/dist/src/types/keyboard.types.d.ts.map +1 -0
  190. package/dist/src/types/log.types.d.ts +32 -0
  191. package/dist/src/types/log.types.d.ts.map +1 -0
  192. package/dist/src/types/message.types.d.ts +91 -0
  193. package/dist/src/types/message.types.d.ts.map +1 -0
  194. package/dist/src/types/prompt.types.d.ts +89 -0
  195. package/dist/src/types/prompt.types.d.ts.map +1 -0
  196. package/dist/src/types/screen.types.d.ts +85 -0
  197. package/dist/src/types/screen.types.d.ts.map +1 -0
  198. package/dist/src/types/theme.types.d.ts +216 -0
  199. package/dist/src/types/theme.types.d.ts.map +1 -0
  200. package/dist/src/utils/colors/file-colors.d.ts +10 -0
  201. package/dist/src/utils/colors/file-colors.d.ts.map +1 -0
  202. package/dist/src/utils/colors/helpers.d.ts +29 -0
  203. package/dist/src/utils/colors/helpers.d.ts.map +1 -0
  204. package/dist/src/utils/colors/index.d.ts +13 -0
  205. package/dist/src/utils/colors/index.d.ts.map +1 -0
  206. package/dist/src/utils/colors/log-colors.d.ts +15 -0
  207. package/dist/src/utils/colors/log-colors.d.ts.map +1 -0
  208. package/dist/src/utils/colors/progress-colors.d.ts +25 -0
  209. package/dist/src/utils/colors/progress-colors.d.ts.map +1 -0
  210. package/dist/src/utils/colors/prompt-colors.d.ts +22 -0
  211. package/dist/src/utils/colors/prompt-colors.d.ts.map +1 -0
  212. package/dist/src/utils/colors/sidebar-colors.d.ts +50 -0
  213. package/dist/src/utils/colors/sidebar-colors.d.ts.map +1 -0
  214. package/dist/src/utils/colors/table-colors.d.ts +12 -0
  215. package/dist/src/utils/colors/table-colors.d.ts.map +1 -0
  216. package/dist/src/utils/filetype.d.ts +19 -0
  217. package/dist/src/utils/filetype.d.ts.map +1 -0
  218. package/dist/src/utils/format.d.ts +9 -0
  219. package/dist/src/utils/format.d.ts.map +1 -0
  220. package/dist/src/utils/index.d.ts +20 -0
  221. package/dist/src/utils/index.d.ts.map +1 -0
  222. package/dist/src/utils/stdout-printer.d.ts +10 -0
  223. package/dist/src/utils/stdout-printer.d.ts.map +1 -0
  224. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  225. package/dist/tsconfig.tsbuildinfo +1 -0
  226. package/dist/tsdown.config.d.mts +5 -0
  227. package/dist/tsdown.config.d.mts.map +1 -0
  228. package/dist/vitest.config.d.mts +3 -0
  229. package/dist/vitest.config.d.mts.map +1 -0
  230. package/lib/index.cjs +6349 -0
  231. package/lib/index.cjs.map +1 -0
  232. package/lib/index.d.cts +1720 -0
  233. package/lib/index.d.cts.map +1 -0
  234. package/lib/index.d.mts +1720 -0
  235. package/lib/index.d.mts.map +1 -0
  236. package/lib/index.mjs +6264 -0
  237. package/lib/index.mjs.map +1 -0
  238. package/lib/screen_manager_bridge-BpDgVu3e.cjs +3357 -0
  239. package/lib/screen_manager_bridge-BpDgVu3e.cjs.map +1 -0
  240. package/lib/screen_manager_bridge-CkV7637i.cjs +3 -0
  241. package/lib/screen_manager_bridge-DN2J6_k1.mjs +3 -0
  242. package/lib/screen_manager_bridge-Dfg4QUrl.mjs +3034 -0
  243. package/lib/screen_manager_bridge-Dfg4QUrl.mjs.map +1 -0
  244. package/package.json +43 -0
  245. package/project.json +60 -0
  246. package/src/__tests__/components/__snapshots__/filter_bar.spec.tsx.snap +2245 -0
  247. package/src/__tests__/components/__snapshots__/loading_message.spec.tsx.snap +1382 -0
  248. package/src/__tests__/components/__snapshots__/log_message.spec.tsx.snap +3169 -0
  249. package/src/__tests__/components/__snapshots__/progress_message.spec.tsx.snap +1743 -0
  250. package/src/__tests__/components/__snapshots__/prompt_renderer.spec.tsx.snap +3135 -0
  251. package/src/__tests__/components/__snapshots__/sidebar.spec.tsx.snap +2617 -0
  252. package/src/__tests__/components/filter_bar.spec.tsx +190 -0
  253. package/src/__tests__/components/loading_message.spec.tsx +110 -0
  254. package/src/__tests__/components/log_message.spec.tsx +166 -0
  255. package/src/__tests__/components/progress_message.spec.tsx +147 -0
  256. package/src/__tests__/components/prompt_renderer.spec.tsx +274 -0
  257. package/src/__tests__/components/sidebar.spec.tsx +305 -0
  258. package/src/__tests__/filter/filter_engine.spec.ts +325 -0
  259. package/src/__tests__/keyboard/keyboard_manager.spec.ts +557 -0
  260. package/src/__tests__/mocks/scm-mock.ts +5 -0
  261. package/src/__tests__/services/logger.spec.ts +630 -0
  262. package/src/__tests__/services/prompt.spec.ts +411 -0
  263. package/src/__tests__/services/screen.spec.ts +721 -0
  264. package/src/__tests__/setup.ts +43 -0
  265. package/src/__tests__/utils/factories.ts +354 -0
  266. package/src/__tests__/utils/filetype.spec.ts +195 -0
  267. package/src/__tests__/utils/format.spec.ts +178 -0
  268. package/src/__tests__/utils/render-utils.tsx +39 -0
  269. package/src/__tests__/utils/test-container.ts +48 -0
  270. package/src/components/file/file_log.tsx +241 -0
  271. package/src/components/file/index.ts +1 -0
  272. package/src/components/filter/filter_bar.tsx +79 -0
  273. package/src/components/filter/index.ts +1 -0
  274. package/src/components/help/help_overlay.tsx +100 -0
  275. package/src/components/help/index.ts +1 -0
  276. package/src/components/index.ts +15 -0
  277. package/src/components/log/index.ts +1 -0
  278. package/src/components/log/log_message.tsx +102 -0
  279. package/src/components/prompt/index.ts +2 -0
  280. package/src/components/prompt/prompt_renderer.tsx +346 -0
  281. package/src/components/screen/group_renderer.tsx +64 -0
  282. package/src/components/screen/index.ts +6 -0
  283. package/src/components/screen/loading_message.tsx +44 -0
  284. package/src/components/screen/message_renderer.tsx +108 -0
  285. package/src/components/screen/progress_message.tsx +60 -0
  286. package/src/components/screen/screen_bridge.tsx +149 -0
  287. package/src/components/screen/table_message.tsx +57 -0
  288. package/src/components/screen_manager_bridge.tsx +245 -0
  289. package/src/components/sidebar/index.ts +3 -0
  290. package/src/components/sidebar/sidebar.tsx +102 -0
  291. package/src/components/sidebar/sidebar_item.tsx +50 -0
  292. package/src/components/sidebar/sidebar_separator.tsx +13 -0
  293. package/src/context/index.ts +1 -0
  294. package/src/context/logger_context.tsx +109 -0
  295. package/src/factories/index.ts +1 -0
  296. package/src/factories/screen.factory.ts +22 -0
  297. package/src/filter/filter_engine.ts +113 -0
  298. package/src/filter/index.ts +1 -0
  299. package/src/hooks/index.ts +1 -0
  300. package/src/hooks/use_theme.ts +12 -0
  301. package/src/index.ts +64 -0
  302. package/src/keyboard/create_bindings.ts +457 -0
  303. package/src/keyboard/index.ts +2 -0
  304. package/src/keyboard/keyboard_manager.ts +233 -0
  305. package/src/overrides/console.logger.override.ts +61 -0
  306. package/src/overrides/index.ts +1 -0
  307. package/src/schemas/index.ts +3 -0
  308. package/src/schemas/logger-options.ts +13 -0
  309. package/src/schemas/prompt-options.ts +9 -0
  310. package/src/schemas/screen-options.ts +14 -0
  311. package/src/services/index.ts +4 -0
  312. package/src/services/logger.ts +369 -0
  313. package/src/services/prompt.ts +169 -0
  314. package/src/services/screen.ts +590 -0
  315. package/src/services/screen_manager.tsx +390 -0
  316. package/src/themes/dark.ts +173 -0
  317. package/src/themes/high-contrast.ts +178 -0
  318. package/src/themes/index.ts +4 -0
  319. package/src/themes/light.ts +172 -0
  320. package/src/themes/utils.ts +83 -0
  321. package/src/tokens/index.ts +3 -0
  322. package/src/tokens/logger.ts +10 -0
  323. package/src/tokens/prompt.ts +10 -0
  324. package/src/tokens/screen.ts +10 -0
  325. package/src/types/file.types.ts +60 -0
  326. package/src/types/filter.types.ts +51 -0
  327. package/src/types/index.ts +22 -0
  328. package/src/types/keyboard.types.ts +93 -0
  329. package/src/types/log.types.ts +61 -0
  330. package/src/types/message.types.ts +120 -0
  331. package/src/types/prompt.types.ts +106 -0
  332. package/src/types/screen.types.ts +124 -0
  333. package/src/types/theme.types.ts +252 -0
  334. package/src/utils/colors/file-colors.ts +9 -0
  335. package/src/utils/colors/helpers.ts +50 -0
  336. package/src/utils/colors/index.ts +20 -0
  337. package/src/utils/colors/log-colors.ts +56 -0
  338. package/src/utils/colors/progress-colors.ts +25 -0
  339. package/src/utils/colors/prompt-colors.ts +30 -0
  340. package/src/utils/colors/sidebar-colors.ts +52 -0
  341. package/src/utils/colors/table-colors.ts +11 -0
  342. package/src/utils/filetype.ts +64 -0
  343. package/src/utils/format.ts +52 -0
  344. package/src/utils/index.ts +11 -0
  345. package/src/utils/stdout-printer.ts +255 -0
  346. package/tsconfig.json +14 -0
  347. package/tsdown.config.mts +34 -0
  348. package/vitest.config.mts +10 -0
@@ -0,0 +1,457 @@
1
+ import type { ScreenInstance, ScreenManager } from '../services/index.ts'
2
+ import type { KeyBinding, KeyboardContext, KeyEvent } from '../types/index.ts'
3
+
4
+ /**
5
+ * Interface for binding handlers that need manager/screen access.
6
+ */
7
+ export interface BindingHandlers {
8
+ manager: ScreenManager
9
+ getActiveScreen: () => ScreenInstance | null
10
+ toggleHelp: () => void
11
+ toggleFilter: () => void
12
+ closeFilter: () => void
13
+ filterAppendChar: (char: string) => void
14
+ filterDeleteChar: () => void
15
+ filterToggleLevel: (index: number) => void
16
+ filterCycleField: () => void
17
+ }
18
+
19
+ /**
20
+ * Create the default keybindings with access to manager and screens.
21
+ */
22
+ export function createDefaultBindings(handlers: BindingHandlers): KeyBinding[] {
23
+ const { manager, getActiveScreen, toggleHelp, toggleFilter, closeFilter } = handlers
24
+
25
+ const bindings: KeyBinding[] = []
26
+
27
+ // ============================================
28
+ // PROMPT INPUT MODE (highest priority)
29
+ // ============================================
30
+
31
+ // Escape to exit input mode
32
+ bindings.push({
33
+ key: 'escape',
34
+ handler: () => {
35
+ const screen = getActiveScreen()
36
+ if (screen?.isPromptInInputMode()) {
37
+ screen.promptExitInputMode()
38
+ return true
39
+ }
40
+ },
41
+ description: 'Exit input mode',
42
+ category: 'prompt',
43
+ when: { hasPrompt: true, inInputMode: true },
44
+ priority: 100,
45
+ })
46
+
47
+ // Enter to submit in input mode
48
+ bindings.push({
49
+ key: 'return',
50
+ handler: () => {
51
+ const screen = getActiveScreen()
52
+ if (screen?.isPromptInInputMode()) {
53
+ screen.promptSubmit()
54
+ return true
55
+ }
56
+ },
57
+ description: 'Submit input',
58
+ category: 'prompt',
59
+ when: { hasPrompt: true, inInputMode: true },
60
+ priority: 100,
61
+ })
62
+
63
+ // Backspace to delete in input mode
64
+ bindings.push({
65
+ key: 'backspace',
66
+ handler: () => {
67
+ const screen = getActiveScreen()
68
+ if (screen?.isPromptInInputMode()) {
69
+ screen.promptDeleteLastChar()
70
+ return true
71
+ }
72
+ },
73
+ description: 'Delete character',
74
+ category: 'prompt',
75
+ when: { hasPrompt: true, inInputMode: true },
76
+ priority: 100,
77
+ })
78
+
79
+ // ============================================
80
+ // PROMPT NAVIGATION (high priority)
81
+ // ============================================
82
+
83
+ // Choice prompt: up/down to navigate, enter to select
84
+ bindings.push({
85
+ key: ['up', 'k'],
86
+ handler: () => {
87
+ const screen = getActiveScreen()
88
+ const prompt = screen?.getActivePrompt()
89
+ if (prompt?.type === 'choice' || prompt?.type === 'multiChoice') {
90
+ screen?.promptNavigateUp()
91
+ return true
92
+ }
93
+ },
94
+ description: 'Navigate up',
95
+ category: 'prompt',
96
+ when: { hasPrompt: true, inInputMode: false },
97
+ priority: 90,
98
+ })
99
+
100
+ bindings.push({
101
+ key: ['down', 'j'],
102
+ handler: () => {
103
+ const screen = getActiveScreen()
104
+ const prompt = screen?.getActivePrompt()
105
+ if (prompt?.type === 'choice' || prompt?.type === 'multiChoice') {
106
+ screen?.promptNavigateDown()
107
+ return true
108
+ }
109
+ },
110
+ description: 'Navigate down',
111
+ category: 'prompt',
112
+ when: { hasPrompt: true, inInputMode: false },
113
+ priority: 90,
114
+ })
115
+
116
+ // Confirm prompt: left/right to toggle
117
+ bindings.push({
118
+ key: ['left', 'h'],
119
+ handler: () => {
120
+ const screen = getActiveScreen()
121
+ const prompt = screen?.getActivePrompt()
122
+ if (prompt?.type === 'confirm') {
123
+ screen?.promptNavigateLeft()
124
+ return true
125
+ }
126
+ },
127
+ description: 'Select confirm',
128
+ category: 'prompt',
129
+ when: { hasPrompt: true, inInputMode: false },
130
+ priority: 90,
131
+ })
132
+
133
+ bindings.push({
134
+ key: ['right', 'l'],
135
+ handler: () => {
136
+ const screen = getActiveScreen()
137
+ const prompt = screen?.getActivePrompt()
138
+ if (prompt?.type === 'confirm') {
139
+ screen?.promptNavigateRight()
140
+ return true
141
+ }
142
+ },
143
+ description: 'Select cancel',
144
+ category: 'prompt',
145
+ when: { hasPrompt: true, inInputMode: false },
146
+ priority: 90,
147
+ })
148
+
149
+ // Space to toggle in multiChoice
150
+ bindings.push({
151
+ key: 'space',
152
+ handler: () => {
153
+ const screen = getActiveScreen()
154
+ const prompt = screen?.getActivePrompt()
155
+ if (prompt?.type === 'multiChoice') {
156
+ screen?.promptToggleSelection()
157
+ return true
158
+ }
159
+ },
160
+ description: 'Toggle selection',
161
+ category: 'prompt',
162
+ when: { hasPrompt: true, inInputMode: false },
163
+ priority: 90,
164
+ })
165
+
166
+ // Enter to submit prompt (or enter input mode for choice with input)
167
+ bindings.push({
168
+ key: 'return',
169
+ handler: () => {
170
+ const screen = getActiveScreen()
171
+ const prompt = screen?.getActivePrompt()
172
+ if (!prompt) return false
173
+
174
+ if (prompt.type === 'choice') {
175
+ const entered = screen?.promptEnterInputMode()
176
+ if (!entered) {
177
+ screen?.promptSubmit()
178
+ }
179
+ return true
180
+ } else if (prompt.type === 'confirm') {
181
+ screen?.promptSubmit()
182
+ return true
183
+ } else if (prompt.type === 'multiChoice') {
184
+ if (screen?.canSubmitPrompt()) {
185
+ screen.promptSubmit()
186
+ }
187
+ return true
188
+ }
189
+ },
190
+ description: 'Submit selection',
191
+ category: 'prompt',
192
+ when: { hasPrompt: true, inInputMode: false },
193
+ priority: 90,
194
+ })
195
+
196
+ // ============================================
197
+ // HELP OVERLAY
198
+ // ============================================
199
+
200
+ bindings.push({
201
+ key: '?',
202
+ handler: () => {
203
+ toggleHelp()
204
+ return true
205
+ },
206
+ description: 'Toggle help',
207
+ category: 'general',
208
+ when: { hasPrompt: false, isFilterActive: false },
209
+ priority: 50,
210
+ })
211
+
212
+ // Escape to close help
213
+ bindings.push({
214
+ key: 'escape',
215
+ handler: () => {
216
+ toggleHelp()
217
+ return true
218
+ },
219
+ description: 'Close help',
220
+ category: 'general',
221
+ when: { isHelpVisible: true },
222
+ priority: 80,
223
+ })
224
+
225
+ // ============================================
226
+ // FILTER
227
+ // ============================================
228
+
229
+ bindings.push({
230
+ key: '/',
231
+ handler: () => {
232
+ toggleFilter()
233
+ return true
234
+ },
235
+ description: 'Toggle filter',
236
+ category: 'filter',
237
+ when: { hasPrompt: false, isHelpVisible: false },
238
+ priority: 50,
239
+ })
240
+
241
+ // Escape to close filter
242
+ bindings.push({
243
+ key: 'escape',
244
+ handler: () => {
245
+ closeFilter()
246
+ return true
247
+ },
248
+ description: 'Close filter',
249
+ category: 'filter',
250
+ when: { isFilterActive: true },
251
+ priority: 70,
252
+ })
253
+
254
+ // Tab to cycle filter fields
255
+ bindings.push({
256
+ key: 'tab',
257
+ handler: () => {
258
+ handlers.filterCycleField()
259
+ return true
260
+ },
261
+ description: 'Cycle filter fields',
262
+ category: 'filter',
263
+ when: { isFilterActive: true },
264
+ priority: 70,
265
+ })
266
+
267
+ // Backspace in filter
268
+ bindings.push({
269
+ key: 'backspace',
270
+ handler: () => {
271
+ handlers.filterDeleteChar()
272
+ return true
273
+ },
274
+ description: 'Delete character',
275
+ category: 'filter',
276
+ when: { isFilterActive: true },
277
+ priority: 70,
278
+ })
279
+
280
+ // Number keys 1-7 to toggle log levels in filter
281
+ for (let i = 1; i <= 7; i++) {
282
+ bindings.push({
283
+ key: String(i),
284
+ handler: () => {
285
+ handlers.filterToggleLevel(i - 1)
286
+ return true
287
+ },
288
+ description: `Toggle level ${i}`,
289
+ category: 'filter',
290
+ when: { isFilterActive: true },
291
+ priority: 70,
292
+ })
293
+ }
294
+
295
+ // ============================================
296
+ // GENERAL
297
+ // ============================================
298
+
299
+ bindings.push({
300
+ key: 'q',
301
+ handler: () => {
302
+ manager.unbind()
303
+ return true
304
+ },
305
+ description: 'Exit',
306
+ category: 'general',
307
+ when: { hasPrompt: false, isFilterActive: false, isHelpVisible: false },
308
+ priority: 10,
309
+ })
310
+
311
+ // Tab to switch focus
312
+ bindings.push({
313
+ key: 'tab',
314
+ handler: (_, ctx) => {
315
+ if (ctx.hasSidebar) {
316
+ manager.toggleFocus()
317
+ return true
318
+ }
319
+ },
320
+ description: 'Toggle focus',
321
+ category: 'navigation',
322
+ when: { hasPrompt: false, isFilterActive: false },
323
+ priority: 20,
324
+ })
325
+
326
+ // Backslash to toggle focus (alternate)
327
+ bindings.push({
328
+ key: '\\',
329
+ handler: (_, ctx) => {
330
+ if (ctx.hasSidebar) {
331
+ manager.toggleFocus()
332
+ return true
333
+ }
334
+ },
335
+ description: 'Toggle focus',
336
+ category: 'navigation',
337
+ when: { hasPrompt: false, isFilterActive: false },
338
+ priority: 20,
339
+ })
340
+
341
+ // ============================================
342
+ // SIDEBAR NAVIGATION
343
+ // ============================================
344
+
345
+ bindings.push({
346
+ key: ['up', 'k'],
347
+ handler: () => {
348
+ manager.navigateUp()
349
+ return true
350
+ },
351
+ description: 'Navigate up',
352
+ category: 'navigation',
353
+ when: { focusArea: 'sidebar', hasPrompt: false },
354
+ priority: 30,
355
+ })
356
+
357
+ bindings.push({
358
+ key: ['down', 'j'],
359
+ handler: () => {
360
+ manager.navigateDown()
361
+ return true
362
+ },
363
+ description: 'Navigate down',
364
+ category: 'navigation',
365
+ when: { focusArea: 'sidebar', hasPrompt: false },
366
+ priority: 30,
367
+ })
368
+
369
+ bindings.push({
370
+ key: 'return',
371
+ handler: () => {
372
+ manager.selectCurrent()
373
+ return true
374
+ },
375
+ description: 'Select screen',
376
+ category: 'navigation',
377
+ when: { focusArea: 'sidebar', hasPrompt: false },
378
+ priority: 30,
379
+ })
380
+
381
+ // ============================================
382
+ // SCREEN SHORTCUTS (1-9)
383
+ // ============================================
384
+
385
+ for (let i = 1; i <= 9; i++) {
386
+ bindings.push({
387
+ key: String(i),
388
+ handler: () => {
389
+ const screens = manager.getScreens()
390
+ if (i <= screens.length) {
391
+ manager.setActiveScreen(screens[i - 1]!)
392
+ manager.setSelectedIndex(i - 1)
393
+ return true
394
+ }
395
+ },
396
+ description: `Jump to screen ${i}`,
397
+ category: 'screen',
398
+ when: { hasPrompt: false, isFilterActive: false },
399
+ priority: 15,
400
+ })
401
+ }
402
+
403
+ // ============================================
404
+ // SCREEN ACTIONS
405
+ // ============================================
406
+
407
+ bindings.push({
408
+ key: 'c',
409
+ handler: () => {
410
+ const screen = getActiveScreen()
411
+ screen?.clear()
412
+ return true
413
+ },
414
+ description: 'Clear screen',
415
+ category: 'screen',
416
+ when: { focusArea: 'content', hasPrompt: false, isFilterActive: false },
417
+ priority: 15,
418
+ })
419
+
420
+ return bindings
421
+ }
422
+
423
+ /**
424
+ * Handle printable character input for prompts and filter.
425
+ * This should be called for any key not handled by bindings.
426
+ */
427
+ export function handlePrintableInput(
428
+ key: KeyEvent,
429
+ context: KeyboardContext,
430
+ handlers: BindingHandlers,
431
+ ): boolean {
432
+ // Only handle single printable characters without modifiers
433
+ if (!key.sequence || key.sequence.length !== 1 || key.ctrl || key.meta) {
434
+ return false
435
+ }
436
+
437
+ const charCode = key.sequence.charCodeAt(0)
438
+ // Printable ASCII (space to tilde)
439
+ if (charCode < 32 || charCode > 126) {
440
+ return false
441
+ }
442
+
443
+ // Handle filter input
444
+ if (context.isFilterActive) {
445
+ handlers.filterAppendChar(key.sequence)
446
+ return true
447
+ }
448
+
449
+ // Handle prompt input mode
450
+ if (context.hasPrompt && context.inInputMode) {
451
+ const screen = handlers.getActiveScreen()
452
+ screen?.promptAppendInput(key.sequence)
453
+ return true
454
+ }
455
+
456
+ return false
457
+ }
@@ -0,0 +1,2 @@
1
+ export * from './keyboard_manager.ts'
2
+ export * from './create_bindings.ts'
@@ -0,0 +1,233 @@
1
+ import type {
2
+ KeyBinding,
3
+ KeyBindingCategory,
4
+ KeyBindingsConfig,
5
+ KeyboardContext,
6
+ KeyEvent,
7
+ } from '../types/index.ts'
8
+
9
+ /**
10
+ * Manages keyboard bindings and dispatches key events to handlers.
11
+ */
12
+ export class KeyboardManager {
13
+ private bindings: KeyBinding[] = []
14
+ private disabled: Set<string> = new Set()
15
+
16
+ constructor(config?: KeyBindingsConfig) {
17
+ if (config?.bindings) {
18
+ this.bindings = [...config.bindings]
19
+ }
20
+
21
+ if (config?.disabled) {
22
+ this.disabled = new Set(config.disabled)
23
+ }
24
+
25
+ // Sort bindings by priority (higher first)
26
+ this.sortBindings()
27
+ }
28
+
29
+ /**
30
+ * Add bindings to the manager.
31
+ */
32
+ addBindings(bindings: KeyBinding[]): void {
33
+ this.bindings.push(...bindings)
34
+ this.sortBindings()
35
+ }
36
+
37
+ /**
38
+ * Remove a binding by key name.
39
+ */
40
+ removeBinding(key: string): void {
41
+ this.bindings = this.bindings.filter((b) => {
42
+ const keys = Array.isArray(b.key) ? b.key : [b.key]
43
+ return !keys.includes(key)
44
+ })
45
+ }
46
+
47
+ /**
48
+ * Disable a key (prevents it from being matched).
49
+ */
50
+ disableKey(key: string): void {
51
+ this.disabled.add(key)
52
+ }
53
+
54
+ /**
55
+ * Enable a previously disabled key.
56
+ */
57
+ enableKey(key: string): void {
58
+ this.disabled.delete(key)
59
+ }
60
+
61
+ /**
62
+ * Handle a key event, dispatching to the appropriate handler.
63
+ * Returns true if a handler consumed the event.
64
+ */
65
+ handleKey(key: KeyEvent, context: KeyboardContext): boolean {
66
+ const binding = this.findMatchingBinding(key, context)
67
+ if (binding) {
68
+ const result = binding.handler(key, context)
69
+ return result !== false
70
+ }
71
+ return false
72
+ }
73
+
74
+ /**
75
+ * Get all bindings for display in help overlay.
76
+ */
77
+ getBindingsForHelp(): KeyBinding[] {
78
+ return this.bindings.filter((b) => b.description)
79
+ }
80
+
81
+ /**
82
+ * Get bindings grouped by category.
83
+ */
84
+ getBindingsByCategory(): Record<KeyBindingCategory, KeyBinding[]> {
85
+ const grouped: Record<KeyBindingCategory, KeyBinding[]> = {
86
+ general: [],
87
+ navigation: [],
88
+ screen: [],
89
+ prompt: [],
90
+ filter: [],
91
+ }
92
+
93
+ for (const binding of this.bindings) {
94
+ if (binding.description) {
95
+ grouped[binding.category].push(binding)
96
+ }
97
+ }
98
+
99
+ return grouped
100
+ }
101
+
102
+ /**
103
+ * Find a matching binding for the given key and context.
104
+ */
105
+ private findMatchingBinding(key: KeyEvent, context: KeyboardContext): KeyBinding | null {
106
+ for (const binding of this.bindings) {
107
+ if (
108
+ this.keyMatches(key, binding) &&
109
+ this.conditionMatches(binding.when, context) &&
110
+ !this.isDisabled(binding)
111
+ ) {
112
+ return binding
113
+ }
114
+ }
115
+ return null
116
+ }
117
+
118
+ /**
119
+ * Check if a key event matches a binding's key specification.
120
+ */
121
+ private keyMatches(key: KeyEvent, binding: KeyBinding): boolean {
122
+ const keys = Array.isArray(binding.key) ? binding.key : [binding.key]
123
+
124
+ // Check key name or sequence
125
+ const keyMatches = keys.includes(key.name) || (key.sequence && keys.includes(key.sequence))
126
+
127
+ if (!keyMatches) return false
128
+
129
+ // Check modifiers
130
+ const ctrlMatches = (binding.ctrl ?? false) === (key.ctrl ?? false)
131
+ const metaMatches = (binding.meta ?? false) === (key.meta ?? false)
132
+ const shiftMatches = (binding.shift ?? false) === (key.shift ?? false)
133
+
134
+ return ctrlMatches && metaMatches && shiftMatches
135
+ }
136
+
137
+ /**
138
+ * Check if context matches a binding's condition.
139
+ */
140
+ private conditionMatches(condition: KeyBinding['when'], context: KeyboardContext): boolean {
141
+ if (!condition) return true
142
+
143
+ if (condition.hasPrompt !== undefined && condition.hasPrompt !== context.hasPrompt) {
144
+ return false
145
+ }
146
+
147
+ if (condition.inInputMode !== undefined && condition.inInputMode !== context.inInputMode) {
148
+ return false
149
+ }
150
+
151
+ if (condition.focusArea !== undefined && condition.focusArea !== context.focusArea) {
152
+ return false
153
+ }
154
+
155
+ if (
156
+ condition.isFilterActive !== undefined &&
157
+ condition.isFilterActive !== context.isFilterActive
158
+ ) {
159
+ return false
160
+ }
161
+
162
+ if (
163
+ condition.isHelpVisible !== undefined &&
164
+ condition.isHelpVisible !== context.isHelpVisible
165
+ ) {
166
+ return false
167
+ }
168
+
169
+ if (condition.hasSidebar !== undefined && condition.hasSidebar !== context.hasSidebar) {
170
+ return false
171
+ }
172
+
173
+ return true
174
+ }
175
+
176
+ /**
177
+ * Check if a binding is disabled.
178
+ */
179
+ private isDisabled(binding: KeyBinding): boolean {
180
+ const keys = Array.isArray(binding.key) ? binding.key : [binding.key]
181
+ return keys.some((k) => this.disabled.has(k))
182
+ }
183
+
184
+ /**
185
+ * Sort bindings by priority (higher first).
186
+ */
187
+ private sortBindings(): void {
188
+ this.bindings.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Format a key binding for display.
194
+ */
195
+ export function formatKeyBinding(binding: KeyBinding): string {
196
+ const keys = Array.isArray(binding.key) ? binding.key : [binding.key]
197
+ const parts: string[] = []
198
+
199
+ if (binding.ctrl) parts.push('Ctrl')
200
+ if (binding.meta) parts.push('Cmd')
201
+ if (binding.shift) parts.push('Shift')
202
+
203
+ // Format key names for display
204
+ const keyDisplay = keys
205
+ .map((k) => {
206
+ switch (k) {
207
+ case 'up':
208
+ return '↑'
209
+ case 'down':
210
+ return '↓'
211
+ case 'left':
212
+ return '←'
213
+ case 'right':
214
+ return '→'
215
+ case 'return':
216
+ return 'Enter'
217
+ case 'escape':
218
+ return 'Esc'
219
+ case 'space':
220
+ return 'Space'
221
+ case 'tab':
222
+ return 'Tab'
223
+ case '\\':
224
+ return '\\'
225
+ default:
226
+ return k
227
+ }
228
+ })
229
+ .join('/')
230
+
231
+ parts.push(keyDisplay)
232
+ return parts.join('+')
233
+ }