mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 16:35:33 +00:00
183 lines
No EOL
8.1 KiB
TypeScript
183 lines
No EOL
8.1 KiB
TypeScript
// Defines a 'polyfill' of sorts for NPM's events module
|
|
/// <reference path="../includes/events.ts" />
|
|
/// <reference path="../includes/lmMsgs.d.ts" />
|
|
/// <reference path="keyboardProcessor.ts" />
|
|
/// <reference path="prediction/languageProcessor.ts" />
|
|
|
|
namespace com.keyman.text {
|
|
export class InputProcessor {
|
|
public static readonly DEFAULT_OPTIONS: ProcessorInitOptions = {
|
|
baseLayout: 'us'
|
|
}
|
|
|
|
private kbdProcessor: KeyboardProcessor;
|
|
private lngProcessor: prediction.LanguageProcessor;
|
|
|
|
constructor(options?: ProcessorInitOptions) {
|
|
if(!options) {
|
|
options = InputProcessor.DEFAULT_OPTIONS;
|
|
}
|
|
|
|
this.kbdProcessor = new KeyboardProcessor(options);
|
|
this.lngProcessor = new prediction.LanguageProcessor();
|
|
}
|
|
|
|
public get languageProcessor(): prediction.LanguageProcessor {
|
|
return this.lngProcessor;
|
|
}
|
|
|
|
public get keyboardProcessor(): KeyboardProcessor {
|
|
return this.kbdProcessor;
|
|
}
|
|
|
|
public get keyboardInterface(): text.KeyboardInterface {
|
|
return this.keyboardProcessor.keyboardInterface;
|
|
}
|
|
|
|
public get activeKeyboard(): keyboards.Keyboard {
|
|
return this.keyboardInterface.activeKeyboard;
|
|
}
|
|
|
|
public set activeKeyboard(keyboard: keyboards.Keyboard) {
|
|
this.keyboardInterface.activeKeyboard = keyboard;
|
|
|
|
// All old deadkeys and keyboard-specific cache should immediately be invalidated
|
|
// on a keyboard change.
|
|
this.resetContext();
|
|
}
|
|
|
|
public get activeModel(): prediction.ModelSpec {
|
|
return this.languageProcessor.activeModel;
|
|
}
|
|
|
|
/**
|
|
* Simulate a keystroke according to the touched keyboard button element
|
|
*
|
|
* Handles default output and keyboard processing for both OSK and physical keystrokes.
|
|
*
|
|
* @param {Object} e The abstracted KeyEvent to use for keystroke processing
|
|
*/
|
|
processKeyEvent(keyEvent: KeyEvent) {
|
|
let formFactor = keyEvent.device.formFactor;
|
|
|
|
// Determine the current target for text output and create a "mock" backup
|
|
// of its current, pre-input state. Current, long-existing assumption - it's DOM-backed.
|
|
let outputTarget = keyEvent.Ltarg;
|
|
let fromOSK = keyEvent.isSynthetic;
|
|
|
|
// The default OSK layout for desktop devices does not include nextlayer info, relying on modifier detection here.
|
|
// It's the OSK equivalent to doModifierPress on 'desktop' form factors.
|
|
if((formFactor == FormFactor.Desktop || !this.activeKeyboard || this.activeKeyboard.usesDesktopLayoutOnDevice(keyEvent.device)) && fromOSK) {
|
|
// If it's a desktop OSK style and this triggers a layer change,
|
|
// a modifier key was clicked. No output expected, so it's safe to instantly exit.
|
|
if(this.keyboardProcessor.selectLayer(keyEvent)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Will handle keystroke-based non-layer change modifier & state keys, mapping them through the physical keyboard's version
|
|
// of state management.
|
|
if(!fromOSK && this.keyboardProcessor.doModifierPress(keyEvent, !fromOSK)) {
|
|
return true;
|
|
}
|
|
|
|
// If suggestions exist AND space is pressed, accept the suggestion and do not process the keystroke.
|
|
// If a suggestion was just accepted AND backspace is pressed, revert the change and do not process the backspace.
|
|
// We check the first condition here, while the prediction UI handles the second through the try__() methods below.
|
|
if(this.languageProcessor.isActive) {
|
|
// The following code relies on JS's logical operator "short-circuit" properties to prevent unwanted triggering of the second condition.
|
|
|
|
// Can the suggestion UI revert a recent suggestion? If so, do that and swallow the backspace.
|
|
if((keyEvent.kName == "K_BKSP" || keyEvent.Lcode == Codes.keyCodes["K_BKSP"]) && this.languageProcessor.tryRevertSuggestion()) {
|
|
return;
|
|
// Can the suggestion UI accept an existing suggestion? If so, do that and swallow the space character.
|
|
} else if((keyEvent.kName == "K_SPACE" || keyEvent.Lcode == Codes.keyCodes["K_SPACE"]) && this.languageProcessor.tryAcceptSuggestion('space')) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// // ...end I3363 (Build 301)
|
|
|
|
let preInputMock = Mock.from(outputTarget);
|
|
let ruleBehavior = this.keyboardProcessor.processKeystroke(keyEvent, outputTarget);
|
|
|
|
// Swap layer as appropriate.
|
|
if(keyEvent.kNextLayer) {
|
|
this.keyboardProcessor.selectLayer(keyEvent);
|
|
}
|
|
|
|
// Should we swallow any further processing of keystroke events for this keydown-keypress sequence?
|
|
if(ruleBehavior != null) {
|
|
let alternates: Alternate[];
|
|
|
|
// If we're performing a 'default command', it's not a standard 'typing' event - don't do fat-finger stuff.
|
|
// Also, don't do fat-finger stuff if predictive text isn't enabled.
|
|
if(this.languageProcessor.isActive && !ruleBehavior.triggersDefaultCommand) {
|
|
// Note - we don't yet do fat-fingering with longpress keys.
|
|
if(keyEvent.keyDistribution && keyEvent.kbdLayer) {
|
|
let activeLayout = this.activeKeyboard.layout(keyEvent.device.formFactor);
|
|
alternates = [];
|
|
|
|
for(let pair of keyEvent.keyDistribution) {
|
|
let mock = Mock.from(preInputMock);
|
|
|
|
let altKey = activeLayout.getLayer(keyEvent.kbdLayer).getKey(pair.keyId);
|
|
if(!altKey) {
|
|
console.warn("Potential fat-finger key could not be found in layer!");
|
|
continue;
|
|
}
|
|
|
|
let altEvent = altKey.constructKeyEvent(this.keyboardProcessor, mock, keyEvent.device);
|
|
let alternateBehavior = this.keyboardProcessor.processKeystroke(altEvent, mock);
|
|
if(alternateBehavior) {
|
|
// TODO: if alternateBehavior.beep == true, set 'p' to 0. It's a disallowed key sequence,
|
|
// so a user should never have intended to type it. Should probably renormalize
|
|
// the distribution afterward, though...
|
|
|
|
let transform: Transform = alternateBehavior.transcription.transform;
|
|
|
|
// Ensure that the alternate's token id matches that of the current keystroke, as we only
|
|
// record the matched rule's context (since they match)
|
|
transform.id = ruleBehavior.transcription.token;
|
|
alternates.push({sample: transform, 'p': pair.p});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we've done all the keystroke processing needed, ensure any extra effects triggered
|
|
// by the actual keystroke occur.
|
|
ruleBehavior.finalize(this.keyboardProcessor);
|
|
|
|
// -- All keystroke (and 'alternate') processing is now complete. Time to finalize everything! --
|
|
|
|
// Notify the ModelManager of new input - it's predictive text time!
|
|
ruleBehavior.transcription.alternates = alternates;
|
|
// Yes, even for ruleBehavior.triggersDefaultCommand. Those tend to change the context.
|
|
ruleBehavior.predictionPromise = this.languageProcessor.predict(ruleBehavior.transcription);
|
|
|
|
// Text did not change (thus, no text "input") if we tabbed or merely moved the caret.
|
|
if(!ruleBehavior.triggersDefaultCommand) {
|
|
// For DOM-aware targets, this will trigger a DOM event page designers may listen for.
|
|
outputTarget.doInputEvent();
|
|
}
|
|
}
|
|
|
|
/* I732 END - 13/03/2007 MCD: End Positional Layout support in OSK */
|
|
|
|
// TODO: rework the return value to be `ruleBehavior` instead. Functions that call this one are
|
|
// the ones that should worry about event handler returns, etc. Not this one.
|
|
//
|
|
// They should also be the ones to handle the TODOs seen earlier in this function -
|
|
// once THOSE are properly relocated. (They're too DOM-heavy to remain in web-core.)
|
|
|
|
// Only return true (for the eventual event handler's return value) if we didn't match a rule.
|
|
return ruleBehavior;
|
|
}
|
|
|
|
public resetContext() {
|
|
this.keyboardProcessor.resetContext();
|
|
this.languageProcessor.invalidateContext();
|
|
}
|
|
}
|
|
} |