Skip to content
This repository was archived by the owner on Apr 1, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions browser/src/Services/InputManager/KeyBindingTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export type ActionFunction = () => boolean

export type ActionOrCommand = string | ActionFunction

export type FilterFunction = () => boolean

export type KeyBinding = {
action: ActionOrCommand
filter?: FilterFunction
}

export type KeyBindingTree = {
children: { [keyBinding: string]: KeyBindingTree }
bindings: KeyBinding[]
}

export const create = (): KeyBindingTree => {
return {
children: {},
bindings: [],
}
}

export const isPotentialChord = (keys: string[], tree: KeyBindingTree): boolean => {
return false
}

export const getKeyBindingsForChord = (keys: string): KeyBinding[] => {
return []
}

export const setKeyBindingsForChord = (
keys: string,
binding: KeyBinding[],
tree: KeyBindingTree,
): KeyBindingTree => {
return tree
}

export const isTerminal = (keys: string[], tree: KeyBindingTree): boolean => {
return false
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
import * as Oni from "oni-api"
import { Event, IEvent } from "oni-types"

import { commandManager } from "./CommandManager"
import { commandManager } from "./../CommandManager"

export type ActionFunction = () => boolean

export type ActionOrCommand = string | ActionFunction

export type FilterFunction = () => boolean

import { IKeyChord, parseKeysFromVimString } from "./../Input/KeyParser"

export interface KeyBinding {
action: ActionOrCommand
filter?: FilterFunction
}

export interface KeyBindingMap {
[key: string]: KeyBinding[]
}
import * as KeyBindingTree from "./KeyBindingTree"

const MAX_DELAY_BETWEEN_KEY_CHORD = 250 /* milliseconds */
const MAX_CHORD_SIZE = 4

import { KeyboardResolver } from "./../Input/Keyboard/KeyboardResolver"
import { KeyboardResolver } from "./../../Input/Keyboard/KeyboardResolver"

import {
getMetaKeyResolver,
ignoreMetaKeyResolver,
remapResolver,
} from "./../Input/Keyboard/Resolvers"
} from "./../../Input/Keyboard/Resolvers"

export interface KeyPressInfo {
keyChord: string
Expand Down Expand Up @@ -64,6 +56,27 @@ export class InputManager implements Oni.Input.InputManager {
private _boundKeys: KeyBindingMap = {}
private _resolver: KeyboardResolver
private _keys: KeyPressInfo[] = []
private _onUnhandledKeyEvent = new Event<string>()

private _bindingTree: KeyBindingTree.KeyBindingTree = KeyBindingTree.create()

/**
* Event that is dispatched when a potential chorded input was
* picked up, but the chord ended up not being completed
*
* If there were multiple keys in the chord that were not handled,
* this event will be dispatched multiple times, in order.
*
* An example would be:
* `input.bind("abc")`
*
* And then the user typing: `abd`. Initially, the 'a' and 'b'
* characters would be swallowed, but upon the 'd' keypress,
* the 'a' and 'b' key events would be dispatched.
*/
public get onUnhandledKey(): IEvent<string> {
return this._onUnhandledKeyEvent
}

constructor() {
this._resolver = new KeyboardResolver()
Expand All @@ -87,10 +100,19 @@ export class InputManager implements Oni.Input.InputManager {
}

const normalizedKeyChord = keyChord.toLowerCase()
const currentBinding = this._boundKeys[normalizedKeyChord] || []

// const currentBinding = this._boundKeys[normalizedKeyChord] || []
const newBinding = { action, filter: filterFunction }

this._boundKeys[normalizedKeyChord] = [...currentBinding, newBinding]
const currentBindings = getKeyBindingsForChord(keyChord)

this._bindingTree = setKeyBindingsForChord(
keyChord,
[...currentBindings, newBinding],
this._bindingTree,
)

// this._boundKeys[normalizedKeyChord] = [...currentBinding, newBinding]

return () => {
const existingBindings = this._boundKeys[normalizedKeyChord]
Expand All @@ -107,12 +129,15 @@ export class InputManager implements Oni.Input.InputManager {
return
}

const normalizedKeyChord = keyChord.toLowerCase()
this._boundKeys[normalizedKeyChord] = []
this._bindingTree = setKeyBindingsForChord(keyChord, [], this._bindingTree)

// const normalizedKeyChord = keyChord.toLowerCase()
// this._boundKeys[normalizedKeyChord] = []
}

public unbindAll() {
this._boundKeys = {}
this._bindingTree = { children: [] }
// this._boundKeys = {}
}

/**
Expand Down
56 changes: 56 additions & 0 deletions browser/test/Input/InputManagerTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,62 @@ describe("InputManager", () => {
})
})

describe("onUnhandledKey", () => {
let inputManager: InputManager
let unhandledKeys: string[]

beforeEach(() => {
inputManager = new InputManager()
unhandledKeys = []
inputManager.onUnhandledKey.subscribe(key => unhandledKeys.push(key))
})

it("doesn't dispatch if key wasn't bound", () => {
const val = inputManager.handleKey("a", 1)
assert.strictEqual(val, false)
assert.deepEqual(unhandledKeys, [])
})

it("doesn't dispatch if chord was successfully executed", () => {
let hitCount = 0
inputManager.bind("abc", () => {
hitCount++
return true
})

let h1 = inputManager.handleKey("a")
let h2 = inputManager.handleKey("b")
let h3 = inputManager.handleKey("c")

assert.strictEqual(h1, true)
assert.strictEqual(h2, true)
assert.strictEqual(h3, true)

assert.strictEqual(hitCount, 1)
assert.deepEqual(unhandledKeys, [])
})

it("dispatches key if chord was missed", () => {
let hitCount = 0
inputManager.bind("abc", () => {
hitCount++
return true
})

let h1 = inputManager.handleKey("a")
let h2 = inputManager.handleKey("b")
let h3 = inputManager.handleKey("d")

assert.strictEqual(h1, true)
assert.strictEqual(h2, true)
assert.strictEqual(h3, false)

assert.strictEqual(hitCount, 1)

assert.deepEqual(["a", "b"], unhandledKeys)
})
})

describe("getRecentKeyPresses", () => {
const createKeyPressInfo = (keyChord: string, time: number): KeyPressInfo => ({
keyChord,
Expand Down
48 changes: 48 additions & 0 deletions browser/test/Services/InputManager/KeyBindingTreeTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* KeyBindingTreeTests.ts
*/

import * as assert from "assert"

import * as KeyBindingTree from "./../../../src/Services/InputManager/KeyBindingTree"

describe("KeyBindingTreeTests", () => {
describe("create", () => {
it("has no bindings or children", () => {
const kbt = KeyBindingTree.create()

assert.deepEqual(kbt.children, {})
assert.deepEqual(kbt.bindings, [])
})
})

describe("isPotentialChord", () => {
it("returns false for unbound key", () => {
assert.ok(false)
})

it("returns true if part of chord, but false if complete", () => {
assert.ok(false)
})
})

describe("getKeyBindingsForChord", () => {
it("returns empty array if no bindings", () => {
assert.ok(false)
})

it("gives value for single item chord", () => {
assert.ok(false)
})
})

describe("isTerminal", () => {
it("returns true for single item with no chorded bindings", () => {
assert.ok(false)
})

it("returns false for key that has potential bindings", () => {
assert.ok(false)
})
})
})