Repository: evan-liu/karabiner.ts (ts)

Issue:
typeSequence() to type a string of keys
`typeSequence('console.log()←')`

Support single-character `key_code`, key alias, and shifted punctuations.

It is currently impossible to express it in TypeScript. So the parameter will only be able to be `string`; and a runtime/build-time error instead of complier error for unsupported characters. 

There can be a mapper parameter to support mapping customised characters to `ToEvent`. 

```
typeSequence('❌❌', {
  '❌': toKey('⎋', '⌘'),
})
```

Pull request description:
✨ Add toTypeSequence() to type a string of keys
Resolve #76 

Patch:
diff --git a/src/config/manipulator.ts b/src/config/manipulator.ts
index 2f80f9c..4ce1b0c 100644
--- a/src/config/manipulator.ts
+++ b/src/config/manipulator.ts
@@ -40,6 +40,7 @@ import { PointingButton } from '../karabiner/pointing-button'
 import { StickyModifierKeyCode } from '../karabiner/key-code'
 import { toArray } from '../utils/to-array'
 import { BuildContext } from '../utils/build-context'
+import { toTypeSequence } from './to-type-sequence'
 
 export interface ManipulatorBuilder {
   build(context?: BuildContext): Manipulator[]
@@ -193,6 +194,12 @@ export class BasicManipulatorBuilder implements ManipulatorBuilder {
     return this
   }
 
+  /** To type a string of keys */
+  toTypeSequence(src: string, map?: Record<string, ToEvent>): this {
+    this.addToEvent(toTypeSequence(src, map))
+    return this
+  }
+
   toIfAlone(event: ToEvent | ToEvent[]): this
   toIfAlone(
     key: ToKeyParam,
diff --git a/src/config/to-type-sequence.ts b/src/config/to-type-sequence.ts
new file mode 100644
index 0000000..d081ee3
--- /dev/null
+++ b/src/config/to-type-sequence.ts
@@ -0,0 +1,70 @@
+import { ToEvent } from '../karabiner/karabiner-config'
+import { toKey, ToKeyParam } from './to'
+import {
+  arrowKeyAliases,
+  controlOrSymbolKeyAliases,
+  KeyAlias,
+  NumberKeyValue,
+} from '../utils/key-alias'
+import { LetterKeyCode } from '../karabiner/key-code'
+
+const defaultMap: Record<string, ToEvent> = {}
+for (let i = 0; i <= 9; i++) {
+  defaultMap[i] = toKey(i as NumberKeyValue)
+}
+
+const letterKeys = 'abcdefghijklmnopqrstuvwxyz'.split('') as LetterKeyCode[]
+for (const k of letterKeys) {
+  defaultMap[k] = toKey(k)
+  defaultMap[k.toUpperCase()] = toKey(k, '⇧')
+}
+
+const keyAliases = [
+  ...Object.keys(arrowKeyAliases),
+  ...Object.keys(controlOrSymbolKeyAliases),
+]
+for (const k of keyAliases) {
+  defaultMap[k] = toKey(k as KeyAlias)
+}
+
+const shiftKeys = {
+  1: '!',
+  2: '@',
+  3: '#',
+  4: '$',
+  5: '%',
+  6: '^',
+  7: '&',
+  8: '*',
+  9: '(',
+  0: ')',
+  "'": '"',
+  '=': '+',
+  ';': ':',
+  ',': '<',
+  '.': '>',
+  '/': '?',
+  '-': '_',
+  '[': '{',
+  '\\': '|',
+  ']': '}',
+  '`': '~',
+}
+for (const [key, shiftKey] of Object.entries(shiftKeys)) {
+  defaultMap[shiftKey] = toKey(key as ToKeyParam, '⇧')
+}
+
+/** Create an array of ToEvent to type a string of keys */
+export function toTypeSequence(
+  src: string,
+  map?: Record<string, ToEvent>,
+): ToEvent[] {
+  return src.split('').map((k) => {
+    const event = map?.[k] || defaultMap[k]
+    if (!event)
+      throw new Error(
+        `${k} is unknown. Please provide a map with { ${k}: {ToEvent} }`,
+      )
+    return event
+  })
+}
diff --git a/src/index.ts b/src/index.ts
index 3f129e5..9fb116a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -28,6 +28,7 @@ export { mouseMotionToScroll } from './config/mouse-motion-to-scroll'
 
 // To
 export * from './config/to'
+export * from './config/to-type-sequence'
 
 // Condition
 export * from './config/condition'


Test patch:
diff --git a/src/config/to-type-sequence.test.ts b/src/config/to-type-sequence.test.ts
new file mode 100644
index 0000000..bca6a04
--- /dev/null
+++ b/src/config/to-type-sequence.test.ts
@@ -0,0 +1,28 @@
+import { expect, test } from 'vitest'
+import { map } from './from'
+import { toTypeSequence } from './to-type-sequence'
+import { toKey } from './to'
+
+test('toTypeSequence()', () => {
+  expect(map(1).toTypeSequence('1!aA>←').build()).toEqual([
+    {
+      type: 'basic',
+      from: { key_code: '1' },
+      to: [
+        { key_code: '1' },
+        { key_code: '1', modifiers: ['shift'] },
+        { key_code: 'a' },
+        { key_code: 'a', modifiers: ['shift'] },
+        { key_code: 'period', modifiers: ['shift'] },
+        { key_code: 'left_arrow' },
+      ],
+    },
+  ])
+
+  expect(toTypeSequence('12', { 2: toKey(3) })).toEqual([
+    { key_code: '1' },
+    { key_code: '3' },
+  ])
+
+  expect(() => toTypeSequence('⚠️')).toThrow()
+})