-
-
Notifications
You must be signed in to change notification settings - Fork 137
spec: Start of text/sentence selects shift layer #3621
Description
This is part of a broader specification for Caps Lock support on touch devices. Development of this touches multiple components; each component will be implemented in a separate PR, referencing this issue.
Related features
- spec: Caps Lock layer for touch layouts #3620: spec: Caps Lock layer for touch layouts
- spec: Suggestions respect capitalization #3720: spec: Suggestions respect capitalization
- spec(web,developer): multitap and flick for touch keyboards 🐵 #5029: spec: multitap and flick for touch keyboards
Introduction
At the start of text, it would be helpful for the keyboard to select an appropriate layer. For the purposes of this discussion, this feature should only apply to a standard text control, and not be activated for email, numeric or other types of input controls.
For most Latin script languages, an appropriate layer for start of text is the shift layer. This is certainly not true for other scripts or for a subset of Latin script languages.
Similarly, at the start of a sentence, for most Latin script languages, the shift layer should be selected.
There is some ambiguity as to whether this functionality should be specified in a keyboard or in a model. An advantage of specifying this with a keyboard is that no model is required for reasonable behaviour, which is very helpful for keyboards such as Cameroon QWERTY which cover hundreds of languages. However, this analytical behaviour may belong more logically within a syntactic model.
As I believe a keyboard-based implementation will be necessary in any case, a model implementation could therefore be implemented later if time constrained.
Virtual Key Modifiers
The caps layer will set the CAPS virtual key modifier for keystroke rules. This is something for keyboard developers to be aware of.
Keyboard Implementation
The keyboard will have a separate entry point called newContext which references a group that does work whenever a new context is entered.
begin [keystroke] > use(main)
begin newContext > use(newContext)
begin postKeystroke > use(postKeystroke)
group(main) using keys
...
group(newContext) readonly
nomatch > use(selectNextLayer)
group(postKeystroke) readonly
if(&layerChanged = "0") > use(selectNextLayer)
group(selectNextLayer) readonly
store(sentencePunctuation) '.?!'
nul > layer(shift)
any(sentencePunctuation) ' ' optany(' ') > layer(shift)
The newContext entry point will be called whenever the user enters a new context, for example, by focusing an edit control, or by moving the insertion point, or tabbing to a new cell in a table. The newContext entry point will not be called after a normal keystroke input event.
Before the call, the default layer will be selected. This permits the same group to be shared for post-processing by the keyboard itself, as shown in the example above, without interfering unduly with the user's (or even the keyboard's) selection of layers where the context does not apply.
A constraint is that only control statements context, layer(), set(), reset(), return (return this is not currently supported in Web so would be a future req.), and use() can be used in the output part of any rules when calling newContext or postKeystroke, and the group referenced by the newContext entry point and any groups it calls must not be a using keys group. This must be tested at compile time.
All groups callable from the newContext and postKeystroke entry points must be readonly. The readonly groups indicate that no output is made in that group, although changes to system stores and option stores are permitted. This new keyword is implemented by the compiler; it injects a context statement at the start of every rule output in the group that does not already have one.
(If you wish to have an exclusion rule, you may want to use the context statement on the right hand side of the rule to indicate a no-op; otherwise, you'd probably just exclude the 'context' statement.)
The advantage of this pattern is that we are abstracting the language-specific knowledge away from Keyman itself, and keeping it within the keyboard. It is a fairly simple extension to the keyboard format, matches the existing design, so is easy to understand, and lends itself to future enhancements. It also helps backward-compatibility, because keyboards that use these features will continue to work on older versions of Keyman; they just won't see the new features there.
Precedence on &layerChanged
-
entry into keyboard.gs() sets &layerChanged=0, notes initial layer
- Similarly, entering a new context always automatically sets &layerChanged=0, as the old layer is no longer valid.
-
… > layer(x), if current layer != initial layer, set &layerChanged=1 else set &layerChanged = 0
-
exit from keyboard.gs()
-
Touch layout processes nextLayer.
-
if current layer != initial layer, set &layerChanged=1 else set &layerChanged=0
-
Touch layout nextLayer is specified to override rule-based layer changes
-
finally, run keyboard.gpk() (postKeystroke)
- Which can also set &layerChanged=1 via … > layer(x)
- The reason we need this postKeystroke entry point is to allow us to override the nextlayer set by the touch keyboard if we want to, for the purposes of sentence detection. Otherwise, we could hack it in with the existing groups model, even if a little ugly.
-
Note: the newContext group has no (meaningful) access to &layerChanged
-
If we want to prevent a layer change, we may need additional rules, such as in the example below. Note that 'context' in this example does not emit the context, but just is essentially a no-op. Example:
group(postKeystroke) readonly "... " > context c Don't cause a layer change if "..." is in the context "Mr. " > context if(&layerChange='0') ". " > layer(shift)
Backspace
begin postKeystroke resolves earlier discussions on backspace interactions. See Keyman 14.0 Capitalization Support for Touch Layouts design document for further details.
C2.1 Changes to Compiler
The newContext and postKeystroke entry points will be assigned to new system store values, in order to generate backwardly-compatible .kmx files.
The compiler will generate a new this.gn(t,e) and this.gpk(t,e) entry point in the compiled JavaScript (example below is with debug symbols).
this.gn=function(t,e) { // newContext
return this.g_selectNextLayer(t,e);
};
this.gpk=function(t,e) { // postKeystroke
return this.g_selectNextLayer(t,e);
};
The KMW compiler needs to enable tests for the &layerChanged system store.
The compiler must check that groups called from newContext and postKeystroke are readonly (generates error). Groups called from newContext should not be using keys groups (generates warning).
The compiler should inject context to the start of every rule in a readonly group, if not already present. While conceptually, the readonly term could be omitted if the context statement was already present in every rule, we will call this an error because of a group signature mismatch. Note that readonly is a compile-side only flag, and will not be visible in compiled keyboards.
The intermediate .kmx generation will need to support the additional entry points, and the two new system stores.
C2.2 Changes to Developer IDE
The editor will need to recognise the newContext and postKeystroke tokens as well as the &layerChanged system store, and handle them appropriately; documentation updates accordingly.
C2.3 Changes to KeymanWeb
The newContext entry point must be called whenever the user enters a new context, e.g. by focusing an edit control or moving the insertion point. The newContext entry point must not be called otherwise, i.e. before or after a normal keystroke event.
The postKeystroke entry point must be called after all processing for a keystroke has completed, including any engine ‘defaults’.
KeymanWeb will extend KIFS to support the new &layerChanged system store.
C2.4 Changes to Keyman Engine for Windows, macOS, Linux
-
Support for
postKeystroke,newContext. -
&layerChangedwill be defined but always"0".
Model Implementation
As Eddie suggested, perhaps both could be available. If a model contains support, then we call that after the keyboard's newContext entry point, making the assumption that the model will be more sophisticated, overriding the keyboard's implementation.
TBD - this needs more design. This may be revisited in 16.0.
Separation of Concerns: Model should not drive layer selection or keyboard interactions. These are the responsibility of the keyboard. A model could provide information about context (e.g. StartOfSentence, StartOfWord, etc.). But these should be provided as hints to the keyboard.