Merge remote-tracking branch 'upstream/master' into feat/developer/10622-xml-parse-line-numbers-epic-ldml

This commit is contained in:
Steven R. Loomis 2025-05-08 13:59:33 -05:00
commit 2ea74f8d71
63 changed files with 804 additions and 322 deletions

View file

@ -1,5 +1,45 @@
# Keyman Version History
## 19.0.34 alpha 2025-05-08
* docs(common): update `builder_echo` documentation for TC blocks (#13855)
* fix(windows): add breadcrumb to download failures (#13826)
* fix(web): sustain active batched-layout mode when keyboard is switched (#13860)
* maint(common): remove obsolete code from docker-images `build.sh` (#13861)
* maint(common): output start/end messages if not running on TC (#13868)
## 19.0.33 alpha 2025-05-07
* maint(common): automatically set --debug option for local-environment builder-script uses (#13827)
* docs(web): add a Web engine keystroke lifecycle internal doc (#13759)
* maint(common): use function to detect if running on TC (#13844)
* docs(common): document new `builder_is_running_on_teamcity` function (#13852)
## 19.0.32 alpha 2025-05-06
* change(web): replace USVString with string in predictive-text engine (#13785)
* chore(web): replace --ci flag with builder_is_ci_build (#13819)
* maint(web): simplify worker-main test.sh (#13820)
* change(web): lint kmwstring web-utility (#13800)
* fix(web): fix extraction of gesture-engine fixture used in DOM-based unit tests (#13805)
* fix(web): add robustness for bad touch-layout fontsize specs (#13774)
* fix(web): fix var declaration affected by linter strictness + length-style fix (#13839)
* chore(ios): Update inuktitut_pirurvik to 1.4.1 (#13840)
* chore(common): override check when running on TC (#13779)
* change(linux): add ICU dependency needed to build on Arch Linux (#13842)
* maint(common): add support for TC blocks (#13810)
## 19.0.31 alpha 2025-05-05
* fix(mac): add version metadata after building tests (#13818)
* maint(common): remove `eslint:recommended` configuration for .ts (#13825)
* maint(common): Update write_download_info() to be cross-platform (#13813)
* change(ios): limit reports about devices without predefined scaling entries (#13806)
* change(web): lint engine/common/web-utils (#13797)
* chore(web): replace kmwFromCharCode with its ES6 equivalent (#13716)
* chore(web): add unit tests for SMP-aware string-handling helper methods (#13719)
* refactor(web): refactor string-helper library to eliminate its module side-effect (#13726)
## 19.0.30 alpha 2025-05-02
* chore(android): Use `builder_is_ci_build` rather than `--ci` flag (#13772)

View file

@ -1 +1 @@
19.0.31
19.0.35

View file

@ -82,7 +82,7 @@ function do_configure() {
function do_test() {
local MOCHA_FLAGS=
if [[ "${TEAMCITY_GIT_PATH:-}" != "" ]]; then
if builder_is_running_on_teamcity; then
# we're running in TeamCity
MOCHA_FLAGS="-reporter mocha-teamcity-reporter"
fi

View file

@ -5,18 +5,6 @@
/****************************** Lexical Models ******************************/
/**
* A JavaScript string with the restriction that it must only
* contain Unicode scalar values.
*
* This means that any lone high surrogate must be paired with
* a low surrogate, if it exists. Lone surrogate code units are
* forbidden.
*
* See also: https://developer.mozilla.org/en-US/docs/Web/API/USVString
*/
export type USVString = string;
export type CasingForm = 'lower' | 'initial' | 'upper';
/**
@ -67,7 +55,7 @@ export interface LexiconTraversal {
* - If `char` = 'e', the child represents a prefix of 'the'.
* - Then `traversal` allows traversing the part of the lexicon prefixed by 'the'.
*/
children(): Generator<{char: USVString, traversal: () => LexiconTraversal}>;
children(): Generator<{char: string, traversal: () => LexiconTraversal}>;
/**
* Allows direct access to the traversal state that results when appending one
@ -81,7 +69,7 @@ export interface LexiconTraversal {
* That is, if a model "keys" `è` to `e`, there will be no `è` child.
* @param char
*/
child(char: USVString): LexiconTraversal | undefined;
child(char: string): LexiconTraversal | undefined;
/**
* Any entries directly keyed by the currently-represented lookup prefix. Entries and
@ -178,7 +166,7 @@ export interface LexicalModel {
* @param text The original input text.
* @returns The 'keyed' form of that text.
*/
toKey?(text: USVString): USVString;
toKey?(text: string): string;
/**
* Generates predictive suggestions corresponding to the state of context after the proposed
@ -248,7 +236,7 @@ export interface LexicalModel {
* @param context
* @deprecated
*/
wordbreak?(context: Context): USVString;
wordbreak?(context: Context): string;
/**
* Lexical models _may_ provide a LexiconTraversal object usable to enhance
@ -279,7 +267,7 @@ export interface Transform {
*
* Corresponds to `s` in com.keyman.KeyboardInterface.output.
*/
insert: USVString;
insert: string;
/**
* The number of code units to delete to the left of the cursor.
@ -380,7 +368,7 @@ export interface Context {
* buffer. If there is nothing to the left of the buffer, this is
* an empty string.
*/
readonly left: USVString;
readonly left: string;
/**
* Up to maxRightContextCodeUnits code units of Unicode scalar value
@ -390,7 +378,7 @@ export interface Context {
*
* This property may be missing entirely.
*/
readonly right?: USVString;
readonly right?: string;
/**
* Whether the insertion point is at the start of the buffer.

View file

@ -7,7 +7,7 @@
import { KMXPlus, LdmlKeyboardTypes, LexicalModelTypes } from "@keymanapp/common-types";
export let u: LexicalModelTypes.USVString;
export let u: string;
export let l: LexicalModelTypes.Transform;
export let s: LexicalModelTypes.Suggestion;
export let st: LexicalModelTypes.SuggestionTag;

View file

@ -14,7 +14,7 @@ var
KeymanRoot: string;
ReportPath: string;
begin
if GetEnvironmentVariable('TEAMCITY_VERSION') <> '' then
if GetEnvironmentVariable('TEAMCITY_GIT_PATH') <> '' then
begin
KeymanRoot := ExcludeTrailingPathDelimiter(GetEnvironmentVariable('KEYMAN_ROOT'));
ReportPath := ExtractRelativePath(KeymanRoot, ExtractFilePath(ParamStr(0)) + 'dunitx-results.xml');

View file

@ -634,6 +634,10 @@ Wraps the `echo` command with color and a script identifier prefix.
```bash
builder_echo [mode] message
or
builder_echo start blockname message
or
builder_echo end blockname status message
```
### Parameters
@ -647,6 +651,12 @@ mode will be `white`.
* `warning`: A warning message, represented with yellow text
* `error`: An error message, represented with red text (consider [`builder_die`])
* `debug`: A debug string, represented with teal text (consider [`builder_echo_debug`])
* `start`: The beginning of a foldable block when running on TeamCity. The
blockname will show up as the title of the collapsable block,
the message as heading inside of the block. If not running on TeamCity
the message is output as heading.
* `end`: The end of the foldable block `blockname` when running on TeamCity.
Ignored if not running on TeamCity identical to `builder_echo status message`.
Or color identifiers:
* `white`: Normal white text, the default if **mode** is omitted
@ -666,6 +676,9 @@ mode will be `white`.
* `setmark`: A marker for a section heading, represented with purple text
* **message**: a string (surround with quote marks)
* **blockname**: a string with the name of the collapsable block (surround with quote marks)
* **status**: one of the `mode` values representing the result of an action
(`success`, `warning`, or `error`)
### Description
@ -867,6 +880,21 @@ fi
--------------------------------------------------------------------------------
## `builder_is_running_on_teamcity` function
Returns `true` (aka 0) if the script runs on TeamCity (i.e. if the
`TEAMCITY_GIT_PATH` environment variable is set).
### Usage
```bash
if builder_is_running_on_teamcity; then
...
fi
```
--------------------------------------------------------------------------------
## `builder_parse` function
Initializes a build.sh script, parses command line. Will abort the script if

View file

@ -18,7 +18,7 @@ conf = configuration_data()
ibus = dependency('ibus-1.0', version: '>= 1.2.0')
gtk = dependency('gtk+-3.0', version: '>= 2.4')
json_glib = dependency('json-glib-1.0', version: '>= 1.0')
icu = dependency('icu-i18n')
icu = [dependency('icu-i18n'), dependency('icu-uc')]
systemd = dependency('libsystemd', 'libelogind', required: false)
if systemd.found()

View file

@ -12,9 +12,22 @@ if [[ "$1" == "--coverage" ]]; then
coverage="-m coverage run --source=. --data-file=build/.coverage"
fi
if [[ -n "${TEAMCITY_VERSION}" ]]; then
if [[ -n "${TEAMCITY_GIT_PATH}" ]]; then
if ! pip3 list --format=columns | grep -q teamcity-messages; then
pip3 install teamcity-messages
if [[ -n "${TEAMCITY_PLATFORM}" ]] || [[ -n "${DOCKER_RUNNING}" ]]; then
# Ubuntu 24.04+ prevents mixing pip and system packages and wants us
# to use a virtualenv. However, that causes other problems, so we
# override this check only if we're running in a TC agent or docker
# so that we don't accidentally break the local system.
# Note: it is intentional that we use TEAMCITY_GIT_PATH as well
# as TEAMCITY_PLATFORM here so that it is possible to simulate
# running in a TC agent and still not break your local system
# (run in a docker container or explicitly set TEAMCITY_PLATFORM
# to cause the script to install the package).
PIP_ARGS="--break-system-packages"
fi
# shellcheck disable=SC2086
pip3 install --user ${PIP_ARGS:-} teamcity-messages
fi
test_module=teamcity.unittestpy
else

View file

@ -1,6 +1,6 @@
Shortname,ID,Name,Region,9.0 Web Keyboard,Version,Language ID,Language Name
fv,fv_inuvialuktun,Inuvialuktun,Arctic,fv_inuvialuktun_kmw-9.0.js,9.1.1,ikt-Latn,Inuinnaqtun (Latin)
i,inuktitut_pirurvik,Kiputtijjut | ᑭᐳᑦᑎᔾᔪᑦ,Arctic,inuktitut_pirurvik-1.1.js,1.4,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
i,inuktitut_pirurvik,Kiputtijjut | ᑭᐳᑦᑎᔾᔪᑦ,Arctic,inuktitut_pirurvik-1.1.js,1.4.1,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
i,inuktitut_latin,Qaliujaaqpait | ᖃᓕᐅᔮᖅᐸᐃᑦ,Arctic,inuktitut_latin-1.1.js,1.4,ike-Latn,Eastern Canadian Inuktitut (Latin)
i,inuktitut_naqittaut,Qaniujaaqpait | ᖃᓂᐅᔮᖅᐸᐃᑦ,Arctic,inuktitut_naqittaut-1.3.js,1.6,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
fv,fv_uummarmiutun,Uummarmiutun,Arctic,fv_uummarmiutun_kmw-9.0.js,9.1.1,ikt-Latn,Inuinnaqtun (Latin)

1 Shortname ID Name Region 9.0 Web Keyboard Version Language ID Language Name
2 fv fv_inuvialuktun Inuvialuktun Arctic fv_inuvialuktun_kmw-9.0.js 9.1.1 ikt-Latn Inuinnaqtun (Latin)
3 i inuktitut_pirurvik Kiputtijjut | ᑭᐳᑦᑎᔾᔪᑦ Arctic inuktitut_pirurvik-1.1.js 1.4 1.4.1 ike-Cans Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
4 i inuktitut_latin Qaliujaaqpait | ᖃᓕᐅᔮᖅᐸᐃᑦ Arctic inuktitut_latin-1.1.js 1.4 ike-Latn Eastern Canadian Inuktitut (Latin)
5 i inuktitut_naqittaut Qaniujaaqpait | ᖃᓂᐅᔮᖅᐸᐃᑦ Arctic inuktitut_naqittaut-1.3.js 1.6 ike-Cans Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
6 fv fv_uummarmiutun Uummarmiutun Arctic fv_uummarmiutun_kmw-9.0.js 9.1.1 ikt-Latn Inuinnaqtun (Latin)

View file

@ -16,9 +16,9 @@ including this file...
See [build-utils.md](build-utils.md)
### jq-win64
### jq/jq-win64
jq-win64.exe is used by various builds for json query.
jq resp. jq-win64.exe is used by various builds for json query.
Official source: <https://stedolan.github.io/jq/>.
[License](jq-license.txt)

View file

@ -17,6 +17,6 @@ function do_build() {
echo "PASS: parent: builder_has_option --debug is true"
}
builder_parse build:child -d
builder_parse build:child --debug
builder_run_action build do_build
builder_run_child_actions build

View file

@ -89,6 +89,12 @@ fi
builder_parse_test() {
local expected="$1"
local expected_options="$2"
# When testing in a local environment, --debug will be added by default (see #11106)
if [[ $VERSION_ENVIRONMENT == "local" ]]; then
expected_options="${expected_options} --debug"
fi
shift
shift
local parameters="$@"
@ -97,8 +103,8 @@ builder_parse_test() {
if [[ "$expected" != "${_builder_chosen_action_targets[@]}" ]]; then
builder_die " Test: builder_parse $parameters action:target != \"$expected\""
fi
if [[ "$expected_options" != "${_builder_chosen_options[@]}" ]]; then
builder_die " Test: builder_parse $parameters, options != \"$expected\""
if [[ "${expected_options}" != "${_builder_chosen_options[@]}" ]]; then
builder_die " Test: builder_parse $parameters, options != \"${expected_options}\""
fi
}
@ -184,10 +190,173 @@ fi
echo -e "${COLOR_BLUE}## Testing output of: builder_parse --feature xyzzy --bar abc --baz def test${COLOR_RESET}"
parse_output=$(builder_parse --feature xyzzy --bar abc --baz def test)
expected="$(builder_echo setmark "test.sh parameters: <--feature xyzzy --bar abc --baz def test>")"
if [[ $VERSION_ENVIRONMENT == "local" ]]; then
# When run in a local-dev environment, an extra line appears about the automatic --debug option.
expected="$(builder_echo grey "Local build environment detected: setting --debug")"$'\n'"$(builder_echo setmark "test.sh parameters: <--feature xyzzy --bar abc --baz def test --debug>")"
fi
if [[ "${parse_output[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for '--feature xyzzy --bar abc --baz def test':\n Actual : ${parse_output[*]}\n Expected: ${expected}"
fi
#----------------------------------------------------------------------
function builder_echo_tests() {
echo -e "${COLOR_BLUE}## Testing builder_echo (heading)${COLOR_RESET}"
local _old_builder_debug_internal=${_builder_debug_internal}
# regular build, no internal debug
_builder_debug_internal=false
expected="$(echo -e "${BUILDER_BOLD}${COLOR_BRIGHT_WHITE}[resources/build/test]${COLOR_RESET} ${COLOR_BLUE}description${COLOR_RESET}")"
result=$(builder_echo heading "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo heading description' (no debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
# regular build, with internal debug
_builder_debug_internal=true
expected="$(echo -e "${BUILDER_BOLD}${COLOR_BRIGHT_WHITE}[resources/build/test]${COLOR_RESET} ${COLOR_BLUE}description${COLOR_RESET}")"
result=$(builder_echo heading "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo start foo description' (debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
_builder_debug_internal=${_old_builder_debug_internal}
}
#----------------------------------------------------------------------
function builder_echo_start_end_tests() {
echo -e "${COLOR_BLUE}## Testing builder_echo (start/end)${COLOR_RESET}"
local _old_builder_debug_internal=${_builder_debug_internal}
local _OLD_TEAMICITY_GIT_PATH=${TEAMCITY_GIT_PATH:-}
local _old_builder_is_child=${_builder_is_child}
# regular build, no internal debug, no child build
TEAMCITY_GIT_PATH=""
_builder_debug_internal=false
_builder_is_child=1
expected="$(builder_echo heading "description")"
result=$(builder_echo start "foo" "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo start foo description' (no debug, no child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo success "description")"
result=$(builder_echo end "foo" success "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo success description' (no debug, no child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo error "description")"
result=$(builder_echo end "foo" error "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo error description' (no debug, no child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
# regular build, no internal debug, is child build
TEAMCITY_GIT_PATH=""
_builder_debug_internal=false
_builder_is_child=0
expected=""
result=$(builder_echo start "foo" "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo start foo description' (no debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected=""
result=$(builder_echo end "foo" success "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo success description' (no debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo error "description")"
result=$(builder_echo end "foo" error "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo error description' (no debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
# regular build, with internal debug, no child build
_builder_debug_internal=true
_builder_is_child=1
expected="$(builder_echo heading "description")"
result=$(builder_echo start "foo" "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo start foo description' (debug, no child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo success "description")"
result=$(builder_echo end "foo" success "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo success description' (debug, no child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo error "description")"
result=$(builder_echo end "foo" error "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo error description' (debug, no child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
# regular build, with internal debug, is child build
_builder_debug_internal=true
_builder_is_child=0
expected="$(builder_echo heading "description")"
result=$(builder_echo start "foo" "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo start foo description' (debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo success "description")"
result=$(builder_echo end "foo" success "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo success description' (debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo error "description")"
result=$(builder_echo end "foo" error "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo error description' (debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
# simulate running on TeamCity
TEAMCITY_GIT_PATH="foo"
# TC build, no internal debug
_builder_debug_internal=false
expected="##teamcity[blockOpened name='|[resources/build/test|] foo']
$(builder_echo heading "description")"
result=$(builder_echo start "foo" "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo start foo description' on TC (no debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo success "description")
##teamcity[blockClosed name='|[resources/build/test|] foo']"
result=$(builder_echo end "foo" success "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo success description' on TC (no debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo error "description")
##teamcity[blockClosed name='|[resources/build/test|] foo']"
result=$(builder_echo end "foo" error "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo error description' on TC (no debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
# TC build, with internal debug
_builder_debug_internal=true
expected="##teamcity[blockOpened name='|[resources/build/test|] foo']
$(builder_echo heading "description")"
result=$(builder_echo start "foo" "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo start foo description' on TC (debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo success "description")
##teamcity[blockClosed name='|[resources/build/test|] foo']"
result=$(builder_echo end "foo" success "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo success description' on TC (debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
expected="$(builder_echo error "description")
##teamcity[blockClosed name='|[resources/build/test|] foo']"
result=$(builder_echo end "foo" error "description")
if [[ "${result[*]}" != "${expected}" ]]; then
builder_die "FAIL: Wrong output for 'builder_echo end foo error description' on TC (debug):\n Actual : ${result[*]}\n Expected: ${expected}"
fi
TEAMCITY_GIT_PATH="${_OLD_TEAMICITY_GIT_PATH}"
_builder_debug_internal=${_old_builder_debug_internal}
_builder_is_child=${_old_builder_is_child}
}
builder_echo_tests
builder_echo_start_end_tests
# Run tests based in separate scripts to facilitate their operation
#----------------------------------------------------------------------

View file

@ -1,4 +1,5 @@
#!/usr/bin/env bash
# shellcheck disable=SC2310
# Note: these two lines can be uncommented for debugging and profiling build
# scripts:
@ -169,35 +170,66 @@ function builder_heading() {
builder_echo() {
local color=white message= mark=
if [[ $# -gt 1 && $1 =~ ^(white|grey|green|success|blue|heading|yellow|warning|red|error|purple|brightwhite|teal|debug|setmark)$ ]]; then
color="$1"
shift
local color=white message= mark= block= action= do_output=true
if [[ $# -gt 1 ]]; then
if [[ $1 =~ ^(white|grey|green|success|blue|heading|yellow|warning|red|error|purple|brightwhite|teal|debug|setmark)$ ]]; then
color="$1"
shift
elif [[ $1 == "start" ]]; then
# builder_echo start block message
action="$1"
block="$2"
shift 2
color="heading"
if ! builder_is_running_on_teamcity && builder_is_child_build; then
do_output=${_builder_debug_internal:-false}
fi
elif [[ $1 == "end" ]]; then
# builder_echo end block status message
action="$1"
block="$2"
color="$3"
shift 3
if [[ "${color}" != "error" ]] && ! builder_is_running_on_teamcity && builder_is_child_build; then
do_output=${_builder_debug_internal:-false}
fi
fi
fi
message="$*"
if [[ ! -z ${COLOR_RED+x} ]]; then
case $color in
white) color="$COLOR_WHITE" ;;
grey) color="$COLOR_GREY" ;;
green|success) color="$COLOR_GREEN" ;;
blue|heading) color="$COLOR_BLUE" ;;
yellow|warning) color="$COLOR_YELLOW" ;;
red|error) color="$COLOR_RED" ;;
purple) color="$COLOR_PURPLE" ;;
brightwhite) color="$COLOR_BRIGHTWHITE" ;;
teal|debug) color="$COLOR_TEAL" ;;
setmark) mark="$HEADING_SETMARK" color="$COLOR_PURPLE" ;;
esac
if [[ "${action}" == "start" ]] && builder_is_running_on_teamcity; then
echo -e "##teamcity[blockOpened name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
fi
if builder_is_dep_build; then
echo -e "$mark$COLOR_GREY[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET"
if ${do_output}; then
if [[ ! -z ${COLOR_RED+x} ]]; then
case $color in
white) color="$COLOR_WHITE" ;;
grey) color="$COLOR_GREY" ;;
green|success) color="$COLOR_GREEN" ;;
blue|heading) color="$COLOR_BLUE" ;;
yellow|warning) color="$COLOR_YELLOW" ;;
red|error) color="$COLOR_RED" ;;
purple) color="$COLOR_PURPLE" ;;
brightwhite) color="$COLOR_BRIGHTWHITE" ;;
teal|debug) color="$COLOR_TEAL" ;;
setmark) mark="$HEADING_SETMARK" color="$COLOR_PURPLE" ;;
esac
if builder_is_dep_build; then
echo -e "$mark$COLOR_GREY[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET"
else
echo -e "$mark$BUILDER_BOLD$COLOR_BRIGHT_WHITE[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET"
fi
else
echo -e "$mark$BUILDER_BOLD$COLOR_BRIGHT_WHITE[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET"
# Cope with the case of pre-init message and just emit plain text
echo -e "$message"
fi
else
# Cope with the case of pre-init message and just emit plain text
echo -e "$message"
fi
if [[ "${action}" == "end" ]] && builder_is_running_on_teamcity; then
echo -e "##teamcity[blockClosed name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
fi
}
@ -393,9 +425,7 @@ _builder_execute_child() {
local script="$THIS_SCRIPT_PATH/${_builder_target_paths[$target]}/build.sh"
if $_builder_debug_internal; then
builder_echo heading "## $action$target starting..."
fi
builder_echo start "$action$target" "## $action$target starting..."
# Build array of specified inheritable options
local child_options=()
@ -424,12 +454,10 @@ _builder_execute_child() {
$builder_debug \
$_builder_offline \
&& (
if $_builder_debug_internal; then
builder_echo success "## $action$target completed successfully"
fi
builder_echo end "$action$target" success "## $action$target completed successfully"
) || (
result=$?
builder_echo error "## $action$target failed with exit code $result"
builder_echo end "$action$target" error "## $action$target failed with exit code $result"
exit $result
) || exit $? # Required due to above subshell masking exit
}
@ -619,19 +647,19 @@ builder_start_action() {
# verify whether a target output is present.
if builder_is_dep_build &&
! builder_is_full_dep_build &&
_builder_dep_output_exists $_builder_matched_action; then
builder_echo "skipping $_builder_matched_action_name, up-to-date"
_builder_dep_output_exists "${_builder_matched_action}"; then
builder_echo "skipping ${_builder_matched_action_name}, up-to-date"
return 1
fi
builder_echo blue "## $_builder_matched_action_name starting..."
if [ -n "${_builder_current_action}" ]; then
builder_echo start "${_builder_matched_action_name}" "## ${_builder_matched_action_name} starting..."
if [[ -n "${_builder_current_action}" ]]; then
_builder_warn_if_incomplete
fi
_builder_current_action="$_builder_matched_action"
_builder_current_action="${_builder_matched_action}"
# Build dependencies as required
_builder_do_build_deps "$_builder_matched_action"
_builder_do_build_deps "${_builder_matched_action}"
return 0
else
return 1
@ -1048,6 +1076,7 @@ _builder_get_default_description() {
:module) description="this module" ;;
:tools) description="build tools for this project" ;;
--debug) description="debug build" ;;
--release) description="release build (prevents --debug in local-env builds)" ;;
esac
echo "$description"
}
@ -1269,6 +1298,7 @@ _builder_parse_expanded_parameters() {
_builder_build_deps=--deps
builder_verbose=
builder_debug=
local is_release=false
local _params=($@)
_builder_chosen_action_targets=()
_builder_chosen_options=()
@ -1385,10 +1415,15 @@ _builder_parse_expanded_parameters() {
_builder_chosen_options+=(--verbose)
builder_verbose=--verbose
;;
--debug|-d)
--debug)
_builder_chosen_options+=(--debug)
builder_debug=--debug
;;
--release)
_builder_chosen_options+=(--release)
# As of #13827, this is only checked when detecting if --debug should be auto-applied.
is_release=true
;;
--deps|--no-deps|--force-deps)
_builder_build_deps=$key
;;
@ -1472,11 +1507,21 @@ _builder_parse_expanded_parameters() {
else
# This is a top-level invocation, so we want to track which dependencies
# have been built, so they don't get built multiple times.
export _builder_deps_built=`mktemp`
# Per #11106, local builds use --debug by default.
# Second condition prevents the block (and message) from executing when --debug is already specified explicitly.
if [[ $VERSION_ENVIRONMENT == "local" ]] && [[ $builder_debug != --debug ]] && ! $is_release; then
builder_echo grey "Local build environment detected: setting --debug"
_params+=(--debug)
_builder_chosen_options+=(--debug)
builder_debug=--debug
fi
builder_echo setmark "$(basename "$0") parameters: <${_params[@]}>"
if [[ ${#builder_extra_params[@]} -gt 0 ]]; then
builder_echo grey "$(basename "$0") extra parameters: <${builder_extra_params[@]}>"
fi
export _builder_deps_built=`mktemp`
fi
if builder_is_debug_build; then
@ -1660,14 +1705,14 @@ builder_finish_action() {
# file or directory exist now?
if _builder_dep_output_defined $matched_action && ! _builder_dep_output_exists "$matched_action"; then
builder_echo warning "Expected output: '${_builder_dep_path[$matched_action]}'."
builder_echo warning "## $action_name completed successfully, but output does not exist"
builder_echo end "$action_name" warning "## $action_name completed successfully, but output does not exist"
else
builder_echo success "## $action_name completed successfully"
builder_echo end "$action_name" success "## $action_name completed successfully"
fi
elif [[ $result == failure ]]; then
builder_echo error "## $action_name failed"
builder_echo end "$action_name" error "## $action_name failed"
else
builder_echo error "## $action_name failed with message: $result"
builder_echo end "$action_name" error "## $action_name failed with message: $result"
fi
# Remove $action$target from the array; it is no longer a current action
@ -2095,6 +2140,15 @@ builder_is_target_excluded_by_platform() {
return 1
}
# Returns 0 if the script is running on TeamCity
builder_is_running_on_teamcity() {
if [[ -z "${TEAMCITY_GIT_PATH:-}" ]]; then
return 1
else
return 0
fi
}
################################################################################
# Final initialization
################################################################################

View file

@ -28,62 +28,13 @@ builder_describe \
builder_parse "$@"
_add_build_args() {
local var=$1
local default_var=$2
local name=$3
local value
value="${!var:=${!default_var:-}}"
build_args+=(--build-arg="${var}=${value}")
if [[ -n "${build_version:-}" ]]; then
build_version="${build_version}-${name:-}${value}"
else
build_version="${name}${value}"
fi
}
_convert_parameters_to_build_args() {
build_args=()
build_version=
local required_node_version keyman_default_distro
# shellcheck disable=SC2034
required_node_version="$(_print_expected_node_version)"
# shellcheck disable=SC2034
keyman_default_distro="ubuntu"
_add_build_args DISTRO keyman_default_distro ""
_add_build_args DISTRO_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER ""
_add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java
_add_build_args REQUIRED_NODE_VERSION required_node_version node
_add_build_args REQUIRED_EMSCRIPTEN_VERSION KEYMAN_MIN_VERSION_EMSCRIPTEN emsdk
if [[ -n "${BASE_VERSION:-}" ]]; then
build_args+=(--build-arg="BASE_VERSION=${BASE_VERSION}")
fi
}
_check_for_default_values() {
if [[ -z "${DISTRO_VERSION:-}" ]] && [[ -z "${JAVA_VERSION:-}" ]]; then
is_default_values=true
else
is_default_values=false
fi
}
_is_default_values() {
${is_default_values}
}
build_action() {
local platform=$1
builder_echo debug "Building image for ${platform}"
if [[ "${platform}" == "base" ]]; then
# shellcheck disable=SC2154 # set by _convert_parameters_to_build_args
# shellcheck disable=SC2154 # set by convert_parameters_to_args
docker pull "${DISTRO}:${DISTRO_VERSION}"
elif [[ "${platform}" == "linux" ]]; then
cp "${KEYMAN_ROOT}/linux/debian/control" "${platform}"
@ -116,8 +67,8 @@ test_action() {
"${platform}" -- ./build.sh configure,build,test:"${platform}"
}
_check_for_default_values
_convert_parameters_to_build_args
check_for_default_values
convert_parameters_to_args
if builder_has_action build; then
build_action base

View file

@ -337,7 +337,7 @@ check-markdown() {
builder_do_typescript_tests() {
local MOCHA_FLAGS=
if [[ "${TEAMCITY_GIT_PATH:-}" != "" ]]; then
if builder_is_running_on_teamcity; then
# we're running in TeamCity
MOCHA_FLAGS="-reporter mocha-teamcity-reporter"
fi

View file

@ -15,7 +15,7 @@ builder_describe "Check markdown internal links" \
"build"
builder_describe_outputs \
configure /node_modules \
configure "${THIS_SCRIPT_PATH}/node_modules" \
build build/index.js
builder_parse "$@"

View file

@ -9,6 +9,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
# ################################ Main script ################################
@ -41,8 +42,7 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \
":samples Builds all needed resources for the KMW sample-page set" \
":tools Builds engine-related development resources" \
":test-pages=src/test/manual Builds resources needed for the KMW manual testing pages" \
":_all (Meta build target used when targets are not specified)" \
"--ci+ Set to utilize CI-based test configurations & reporting."
":_all (Meta build target used when targets are not specified)"
# Possible TODO?
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \
@ -135,14 +135,6 @@ build_action() {
"${KEYMAN_ROOT}/web/build/test/dom/cases/attachment/"
}
test_action() {
TEST_OPTS=
if builder_has_option --ci; then
TEST_OPTS=--ci
fi
./test.sh "${TEST_OPTS}"
}
coverage_action() {
builder_echo "Creating coverage report..."
@ -213,7 +205,7 @@ builder_run_action build:_all build_action
# Run tests
builder_run_child_actions test
builder_run_action test:_all test_action
builder_run_action test:_all ./test.sh
function do_test_help() {
check-markdown "$KEYMAN_ROOT/web/docs/engine"

View file

@ -63,8 +63,7 @@ function build_action() {
# - also useful when validating this script on a local dev machine!
# - build: then do the ACTUAL build.
# one option:
# - --ci: For app/browser, outputs 'release' config filesize profiling logs
./build.sh configure clean build --ci
./build.sh configure clean build
# Upload the sentry-configuration engine used by the mobile apps to sentry
# Also, clean 'em first.
@ -79,19 +78,7 @@ function build_action() {
}
function test_action() {
# Testing step: run ALL unit tests, including those of the submodules.
OPTIONS=
if ! builder_is_debug_build; then
OPTIONS=--ci
fi
# No --reporter option exists yet for the headless modules.
"${KEYMAN_ROOT}/web/src/engine/keyboard/build.sh" test ${OPTIONS}
"${KEYMAN_ROOT}/web/src/engine/osk/gesture-processor/build.sh" test ${OPTIONS}
./build.sh test ${OPTIONS}
./build.sh test
}
function post_test_action() {

View file

@ -1,6 +1,9 @@
#!/usr/bin/env bash
#
# Needed for builder_is_ci_build check.
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
BUNDLE_CMD="node $KEYMAN_ROOT/web/src/tools/es-bundling/build/common-bundle.mjs"
# Compiles all build products corresponding to the specified target.
@ -101,7 +104,7 @@ function test-headless() {
fi
TEST_OPTS=
if builder_has_option --ci; then
if builder_is_ci_build; then
TEST_OPTS="--reporter mocha-teamcity-reporter"
fi
if [[ -n "$TEST_EXTENSIONS" ]]; then

View file

@ -0,0 +1,301 @@
# The Keyman Engine for Web keystroke lifecycle
The phases of Keyman Engine for Web keystroke processings are as follows:
1. Keystroke pre-processing: event interception + interpretation (pre-processing)
2. Keystroke processing: keystroke main-group rule evaluation
3. Keystroke post-processing: keystroke post-keystroke evaluation + side-effects
4. Keystroke resolution: UI state updating
Keyman Engine for Web currently has two different builds, each used in specific
contexts:
* app/browser - for direct use on a page or site within a web browser
* app/webview - for integration with a host app, such as Keyman itself
The overall phases for the keystroke lifecycle are the same for both; the
implementation is merely a bit different for each within specific phases. These
differences are documented after the common structure and patterns for each
phase.
Also note that Keyman Engine for Web supports keystroke processing for different
keyboard specification formats:
- JS
- KMX (via web-core)
- KMX+ / LDML (via web-core)
The `KeyboardProcessor` classes each handle keystroke processing for one
specification format while adhering to a common interface exposed and accessible
to the other layers of the keystroke lifecycle.
```mermaid
---
title: Keystroke lifecycle flowchart
---
%% For rendering, use e.g. https://mermaid.live
%%{init: {"flowchart": {"htmlLabels": false}} }%%
sequenceDiagram;
actor User as User + UI
participant Keystroke interpretation functionality
Note over User, KeymanEngine: Start: Keystroke pre-processing
User ->> Keystroke interpreter: Types a key
Keystroke interpreter ->> Keystroke interpreter: Preprocess keystroke events
Keystroke interpreter ->> KeymanEngine: Raise preprocessed event
Note over User, KeymanEngine: End: Keystroke pre-processing
KeymanEngine ->>+ InputProcessor: Forward keystroke data
Note over InputProcessor, KeyboardProcessor: Start: Keystroke processing
InputProcessor ->> InputProcessor: Validate context, keyboard
InputProcessor ->> InputProcessor: Snapshot context state
InputProcessor ->>+ KeyboardProcessor: Process keystroke
KeyboardProcessor ->> KeyboardProcessor: Determine effects
KeyboardProcessor -->> User: Update raw context
KeyboardProcessor ->>- InputProcessor: Return effects
opt Predictive Text
loop For each nearby key
InputProcessor ->>+ KeyboardProcessor: Fat-finger (alternate) keystrokes
KeyboardProcessor ->> KeyboardProcessor: Determine effects
KeyboardProcessor ->>- InputProcessor: Return effects
end
end
Note over InputProcessor, KeyboardProcessor: End: Keystroke processing
Note over InputProcessor, KeyboardProcessor: Start: Keystroke post-processing
InputProcessor ->> InputProcessor: Apply keystroke effects
InputProcessor ->> InputProcessor: Cache context state, keystroke effects
InputProcessor -->> User: Update OSK layer
InputProcessor ->>+ KeyboardProcessor: Process post-keystroke effects
KeyboardProcessor ->>- InputProcessor: Return effects
InputProcessor -->> User: Update OSK layer
opt Predictive Text
InputProcessor --)+ Predictive-text engine: Request corrections, predictions
InputProcessor -->> User: Invalidate outdated predictions
end
Note over InputProcessor, KeyboardProcessor: End: Keystroke post-processing
opt app/browser
InputProcessor ->> InputProcessor: Trigger DOM `input` event
end
Note over Keystroke interpreter, InputProcessor: Start: Keystroke resolution
InputProcessor ->>- KeymanEngine: Return effects
KeymanEngine ->> Keystroke interpreter: Store state token for multitaps
KeymanEngine ->> Keystroke interpreter: Determine TAB, ENTER, etc effects
Note over Keystroke interpreter, InputProcessor: End: Keystroke resolution
opt Predictive Text
Predictive-text engine ->> Predictive-text engine: Determine corrections,<br>predictions (async)
Note over User, Predictive-text engine: After async delay
Predictive-text engine --)- User: Return new predictions, update banner
end
```
## Keystroke pre-processing
Both the `app/browser` and `app/webview` products are capable of handling
keystrokes from the on-screen keyboard as well as hardware keystrokes. For
hardware keystrokes, this varies between the two, while the majority of
on-screen keyboard keystrokes are handled identically.
### `app/browser` - hardware keystrokes
The `app/browser` version of the engine is designed to integrate with the active
page of the user's browser. As a result of this, contexts for text manipulation
are directly implemented within Web elements that the engine has direct access
to. It can thus attach DOM event handlers (for
[`keydown`](https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event),
[`keyup`](https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event),
and
[`keypress`](https://developer.mozilla.org/en-US/docs/Web/API/Element/keypress_event))
in order to process hardware keystrokes. Its `HardwareEventKeyboard` class is
the primary module for handling hardware keystrokes - it establishes the needed
engine event hooks and methods used to fully handle and preprocess their
keystroke data for further use later in the keystroke lifecycle.
The bulk of DOM key events trigger keystroke processing on key-down, though modifier keys also receive key-up handling. Key-press events are handled selectively for held backspaces and possibly certain keystrokes for mnemonic keyboards. In all cases, DOM keyboard event objects are preprocessed by the centralized `preprocessKeyboardEvent` method, which converts the DOM event [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) format into the Web engine's `KeyEvent` format, which is the form required by the keyboard rules within compiled JS keyboards. This includes mnemonic keystroke preprocessing and remapping in addition to AltGr emulation.
### `app/webview` - hardware keystrokes
The `app/webview` version of the engine, which is designed to be used while embedded in a platform-specific host app, does not have direct access to standard hardware keystroke events, as those are handled by the host app's OS and by native code handlers run outside the host app's WebView containing the app/webview Web engine. It is the responsibility of the host app to handle hardware keystroke events and preprocess them on behalf of the Web engine, then forward them to the Web engine via JS call into the WebView. The `PassthroughKeyboard` class within `app/webview` space provides the method `raiseKeyEvent` as an internal API for this purpose, which converts the mobile-app format for hardware keystroke into the internal `KeyEvent` format. `raiseKeyEvent` also handles mnemonic keystroke processing and remapping.
Also note that this variant does not model the user's text context with Web elements - it is entirely managed through the `Mock` type.
### On-screen keyboard
For both `app/browser` and `app/webview`, the on-screen keyboard delegates mouse
and touch processing to [our internal gesture engine](./gestures/index.md) for
most keyboards. (Keyboards that instead display "help pages", such as
`sil_euro_latin` on desktop, have specialized handling.) The gesture engine is
responsible for interpreting all DOM mouse and touch events for OSKs based on
the touch-layout format, which are managed by the `VisualKeyboard` class. The
gesture engine will raise events about the gesture lifecycle that, when
appropriate, will emit `KeyElement` instances - elements of the constructed OSK
corresponding to elements of the OSK directly representing keyboard keys.
OSK keystroke preprocessing operates a little differently from hardware
keystrokes. The `ActiveLayout` object for the current form-factor of the
`Keyboard` maintains a "base" `KeyEvent` object, per key, that is
lazy-constructed (by the `.baseKeyEvent` property) when first needed - the meat
of which is performed by the `ActiveKeyBase.constructBaseKeyEvent` method. This
is only evaluated up to once per key per activation of the keyboard. As
keystrokes are received during OSK use, `VisualKeyboard.keyEventFromSpec` calls
the `Keyboard.constructKeyEvent` method to clone that base `KeyEvent` object for
the incoming keystroke and then adjust it to match current modifier and
state-key settings.
### Passing `KeyEvent` on for processing
Regardless of the path taken to generate the `KeyEvent` object, _all_ keystroke
handling modules of Keyman Engine for Web then raise that `KeyEvent` object via
their `keyevent` event as specified by the `KeyEventSourceInterface` type. This
event also permits a callback that is used by the final phase of the lifecycle -
keystroke resolution - as needed. The main `KeymanEngine` base class listens
for these events and forwards them for next phase of the lifecycle - keystroke
processing - to the `InputProcessor` module, which passes control to whichever
`KeystrokeProcessor` type is active for the keyboard when appropriate.
## Keystroke processing
Once the `KeyEvent` object for a keystroke has been passed into the
`InputProcessor` module and its `processKeyEvent` method, the
keystroke-processing stage has been reached. There are a number of subprocesses
that trigger here in sequence - keystrokes don't go _straight_ to evaluation
against keyboard rules.
### Pre-keystroke state management
First, the engine checks the keyboard associated with the `KeyEvent`, ensuring
that it is still in place. (See [#7543](https://github.com/keymanapp/keyman/issues/7543) for more details.) With that assertion
in place, it then performs a check needed to support multitaps - it checks to
see if the key event has an associated "base context" and reverts the current
context to match it if so. Once those checks are fulfilled, the engine then
checks to see if the key event corresponds to a pure layer-switching or
modifier/state-key interaction. If those are detected, rule-evaluation is
bypassed.
On a similar note, backspace and spacebar keystrokes receive special handling
next in associated with predictive-text functionality. When autocorrect is
enabled, the spacebar in particular can be used to accept the current suggestion
instead of acting as whitespace; if this is triggered, its `KeyEvent` will be
swallowed - no rule processing will occur for the it. The backspace may trigger
display of a "reversion" suggestion when applicable but will not be swallowed.
Once this phase is reached, the engine will then snapshot the current state of
the context, then trigger standard rule processing against the original context
by calling the current `KeyboardProcessor`'s `processKeystroke` method. Any
operations against the context will be persisted and take full effect; DOM input
elements (for `app/browser`) are updated during rule evaluation.
### Determining direct keystroke effects
The `KeyboardProcessor` classes, which each embody how one keyboard
specification pattern interprets keystrokes, are responsible for determining the
effects that should arise when evaluating the incoming keystroke with the
current context state. While the exact mechanisms of this may vary depending
upon the type of keyboard - be it JS, KMX, or KMX+ (LDML), the role is the same
- interpreting the keystroke in context.
### JS keyboards
Before proceeding further, `KeyboardProcessor` does a context check to see
whether or not the context contains anything considered deletable - in the case
of a BKSP, we need to ensure that the keystroke passes through to the engine's
host cleanly, as it may have special implications for the site or host app.
With that out of the way, the JS keyboard's main group is then provided with the
`KeyEvent` object in order to evaluate the keyboard's rules against the
keystroke. This involves constructing a `RuleBehavior` object to track any and
all needed side-effects for the keystroke. Such changes are generally not
applied to the engine at large at this time in order to prevent unwanted side
effects that may arise to and from predictive text operations that follow
shortly afterward.
The `KeyboardInterface` class defines the internal API used by keyboards for
their evaluation of keyboard rules.
Once the keyboard's script returns control to the main engine, the engine then
determines the net effects to the context that resulted from applying the
keyboard rule and preserves that as a `Transcription`, part of includes a
`Transform` that most directly represents the changes to text surrounding the
text insertion point.
For JS keyboards, if no keyboard rule is defined for a non-frame key on the
`default` or `shift` layer, there is an additional set of implied "default"
keyboard rules that is applied. (A keyboard's source must _explicitly_ specify
to not emit anything for such keys if such an outcome is desired.) This occurs
regardless of the keystroke's source, as such effects are necessary to
incorporate for predictive-text operations. If no such rule exists, the
processor will add a note to the `RuleBehavior` object that default processing
could not be provided and is thus needed from the engine's host.
### KMX keyboards & KMX+ (LDML) keyboards
For KMX and KMX+ keyboards, keystrokes are evaluated by using the embedded WASM
version of Keyman Core.
TODO: add link to specific mechanisms here once available.
### "Alternate" processing (predictive-text)
Once the incoming `KeyEvent` has been processed, if predictive-text is active
and the `KeyEvent` includes fat-finger oriented data, the engine will then build
a set of "alternates" - of "phantom" keystrokes - and process _them_ against
headless clones of the original context in the same manner as the original
keystroke.
Once all relevant "alternates" have been evaluated, the engine will then signal
the predictive-text engine with all relevant data, including the original,
snapshotted state of the context. It does not actually trigger predictive-text
at this time; processing of alternates is done before keystroke post-processing
in order to avoid cross-effects from JS-keyboard variable store manipulations
and similar mechanisms. Fat-finger "alternate" `KeyEvent`s are evaluated as if
they occurred under the exact same conditions as the keystroke that was actually
detected.
### Keystroke post-processing
At this stage, the side-effects desired for the original keystroke, as saved to
its `RuleBehavior`, are then applied. Note that this includes changes to the
`&layer` store - this update will also trigger an event that updates the state
of the OSK to match the desired layer.
Once that is done, the engine will then save data relevant to the original
context state, the `KeyEvent`, and the `RuleBehavior` to the "context cache"
supporting multitap context reversion. These effects include layer-change
effects that result from JS keyboard rules, which modify the KMN-style keyboard
stores `&oldlayer` and `&newlayer` in preparation for `begin PostKeystroke`
group processing, which is then executed (and which can trigger another `&layer`
& OSK-layer update).
Once post-keystroke effects have been determined, the engine then signals
predictive-text to generate new predictions associated with the keystroke. For
`app/browser` variants of the engine, changes to the context will then also
trigger the DOM `input` event on the context's element in case third parties
wish to leverage such events for their website's pages.
### Keystroke resolution
At this stage, `InputProcessor` passes control back to the main `KeymanEngine`
module, which then triggers the "on rule finalization" setting if configured.
This setting is mostly used for page-integration within `app/browser`,
supporting DOM element `change` event emulation whenever focus shifts away from
the active context element.
For OSK keystrokes, an identifier associated with the `KeyEvent`'s context
snapshot will be made accessible through the `keyevent` event's callback
parameter - a feature utilized by multitap gesture support.
For hardware-keyboard keystrokes, the `keyevent` callback is used to determine
whether or not native handling for the keystroke should be triggered - such as
for TAB and ENTER keys, which may have semantic command-like functionality
depending on the state of the Web engine's host - be that a web page or an
Android/iOS app utilizing the keyboard. ENTER keys are often used to submit
forms, and this helps ensure we trigger such functionality when appropriate.

View file

@ -1,6 +1,6 @@
# Keystroke Processing
In addition to handling keystroke events produced from hardware keyboards, Keyman Engine for Web manages an on-screen keyboard that can also be used to generate such events. This document will enumerate the various components of data management and control flow used to handle and process keystrokes.
In addition to handling keystroke events produced from hardware keyboards, Keyman Engine for Web manages an on-screen keyboard that can also be used to generate such events. This document will enumerate the various components of data management and control flow used to handle and process keystrokes. For a more thorough examination of the keystroke lifecycle, see the [keystroke-lifecycle](./keystroke-lifecycle.md) page.
## Input: KeyEvent - the pre-processed keystroke

View file

@ -19,8 +19,7 @@ builder_describe "Builds the Keyman Engine for Web's website-integrating version
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -18,8 +18,7 @@ builder_describe "Builds the Keyman Engine for Web's desktop form-factor keyboar
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -18,8 +18,7 @@ builder_describe "Builds the Keyman Engine for Web's puppetable version designed
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -19,8 +19,7 @@ builder_describe "Builds the Keyman Engine for Web (KMW) attachment engine." \
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -1,4 +0,0 @@
module.exports = {
// Temporarily ignored due to noise it would cause within the refactor
ignorePatterns: ["**/kmwstring.ts"]
};

View file

@ -22,8 +22,7 @@ builder_describe \
"Compiles the web-oriented utility function module." \
"@/common/web/keyman-version" \
"@/web/src/tools/es-bundling" \
clean configure build test \
"--ci For use with action ${BUILDER_TERM_START}test${BUILDER_TERM_END} - emits CI-friendly test reports"
clean configure build test
builder_describe_outputs \
configure "/node_modules" \
@ -49,7 +48,7 @@ function do_test() {
builder_heading "Running web-utils test suite"
local FLAGS=
if builder_has_option --ci; then
if builder_is_ci_build; then
echo "Replacing user-friendly test reports with CI-friendly versions."
FLAGS="$FLAGS --reporter mocha-teamcity-reporter"
fi

View file

@ -55,9 +55,9 @@ export function charCodeAt(s: string, index: number) {
}
}
let first = str.charCodeAt(codeUnitIndex);
const first = str.charCodeAt(codeUnitIndex);
if(first >= 0xD800 && first <= 0xDBFF && str.length > codeUnitIndex + 1) {
let second = str.charCodeAt(codeUnitIndex + 1);
const second = str.charCodeAt(codeUnitIndex + 1);
if(second >= 0xDC00 && second <= 0xDFFF) {
return ((first - 0xD800) << 10) + (second - 0xDC00) + 0x10000;
}
@ -248,7 +248,7 @@ export function substring(s: string, indexA: number, indexB?: number) {
indexBCodeUnit = str.length;
} else {
if(indexA > indexB) {
let c = indexA;
const c = indexA;
indexA = indexB;
indexB = c;
}

View file

@ -20,8 +20,7 @@ builder_describe "Builds DOM-utility modules used by the Keyman Engine for Web (
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -17,8 +17,7 @@ builder_describe "Builds DOM-based OutputTarget subclasses used by the Keyman En
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -18,8 +18,7 @@ builder_describe "Builds specialized event-related modules utilized by Keyman En
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -19,8 +19,7 @@ builder_describe "Builds configuration subclasses used by the Keyman Engine for
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -17,8 +17,7 @@ builder_describe "Builds configuration subclasses used by the Keyman Engine for
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
builder_describe_outputs \
configure "/node_modules" \

View file

@ -17,8 +17,7 @@ builder_describe "Builds Keyman Engine modules for keyboard cloud-querying & cac
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -25,8 +25,7 @@ builder_describe \
configure \
clean \
build \
test \
"--ci For use with action $(builder_term test) - emits CI-friendly test reports"
test
builder_describe_outputs \
configure /node_modules \

View file

@ -24,8 +24,7 @@ builder_describe "Builds the Keyman Engine for Web's common top-level base class
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -21,8 +21,7 @@ builder_describe "Builds the Keyman Engine for Web's On-Screen Keyboard package
"clean" \
"configure" \
"build" \
"test" \
"--ci+ Set to utilize CI-based test configurations & reporting."
"test"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \
@ -60,10 +59,10 @@ do_build() {
do_test() {
test-headless-typescript "${SUBPROJECT_NAME}"
./gesture-recognizer/build.sh test
./gesture-processor/build.sh test
}
builder_run_action configure do_configure
builder_run_action clean do_clean
builder_run_action build do_build
builder_run_action test
builder_run_action test do_test

View file

@ -25,8 +25,7 @@ builder_describe "Builds the gesture-recognition model for Web-based on-screen k
"build" \
"test" \
":module" \
":tools tools for testing & developing test resources for this module" \
"--ci sets the --ci option for child scripts (i.e, the $(builder_term test) action)"
":tools tools for testing & developing test resources for this module"
builder_describe_outputs \
configure /node_modules \

View file

@ -23,6 +23,7 @@
-->
<body>
<!-- CSS classes for demo configuration should only be applied here, to `host-fixture`. -->
<!-- START: recognizer host fixture -->
<div id="host-fixture" class="screen2 bounds1 safe-loose">
<div id='faux-viewport' class="faux-screen">
<div class="faux-screen-border"></div>
@ -40,7 +41,7 @@
</div>
<p></p>
</div>
<!-- end `host-fixture` -->
<!-- END: recognizer host fixture -->
</body>
</html>

View file

@ -10,7 +10,25 @@ export class ParsedLengthStyle implements LengthStyle {
public readonly special: 'em' | 'rem';
public constructor(style: LengthStyle | string) {
const parsed: LengthStyle = (typeof style == 'string') ? ParsedLengthStyle.parseLengthStyle(style) : style;
let parsed: LengthStyle = (typeof style == 'string') ? ParsedLengthStyle.parseLengthStyle(style) : style;
const fallback: LengthStyle = {
val: 1, // 100%
absolute: false
};
if(typeof style == 'number') {
// Addresses #13766 for compiled layouts with numbers in the touch-layout `fontsize` field
// Do not actually emit console warning; we handle those as "errors" in the mobile apps, and
// this is more an issue with the compiled keyboard.
parsed = fallback;
}
if(isNaN(parsed.val)) {
console.error("NaN appeared while trying to calculate style scaling effects");
parsed = fallback
}
// While Object.assign would be nice (and previously, was used), it will break
// on old but still supported versions of Android if their Chrome isn't updated.
@ -38,6 +56,11 @@ export class ParsedLengthStyle implements LengthStyle {
}
public scaledBy(scalar: number): ParsedLengthStyle {
if(isNaN(scalar)) {
console.error("NaN appeared while trying to calculate style scaling effects");
return this;
}
return new ParsedLengthStyle({
val: scalar * this.val,
absolute: this.absolute

View file

@ -521,7 +521,13 @@ export default abstract class OSKView
// Set default OSK font size (Build 344, KMEW-90)
// If the layer group specifies a fontsize value, we need
// to apply that to the banner as well.
const layerFontSize = this.vkbd?.layerGroup?.spec.fontsize;
const layerFontSizeRaw = this.vkbd?.layerGroup?.spec.fontsize;
// Addresses issue with touch-layouts specifying a unitless fontsize; this
// coerces them to `pt` style sizing, like how word-processors present font-size.
//
// Number() returns NaN if not 100% a number. "12px", "12pt", "12%" all return NaN.
const fsRaw = Number(layerFontSizeRaw);
const layerFontSize = isNaN(fsRaw) ? layerFontSizeRaw : (fsRaw + 'pt');
if(layerFontSize) {
const parsedSize = new ParsedLengthStyle(layerFontSize);
@ -619,14 +625,18 @@ export default abstract class OSKView
return;
}
const vkbd = this.vkbd;
try {
this.deferLayout = true;
if(this.vkbd) {
this.vkbd.deferLayout = true;
if(vkbd) {
vkbd.deferLayout = true;
}
closure();
} finally {
this.deferLayout = false;
if(vkbd) {
vkbd.deferLayout = false;
}
if(this.vkbd) {
this.vkbd.deferLayout = false;
}
@ -775,6 +785,9 @@ export default abstract class OSKView
this.banner.appendStyles();
if(this.vkbd) {
// Layout-deferral mode may be active during a keyboard-swap; the newly-incoming keyboard
// should inherit that value!
this.vkbd.deferLayout = this.deferLayout;
// Create the key preview (for phones)
this.vkbd.createKeyTip();

View file

@ -804,9 +804,12 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
}
get layoutHeight(): ParsedLengthStyle {
// currently: equivalent to if(is touch keyboard)
if (this.usesFixedHeightScaling) {
let baseHeight = this.height;
let baseHeight = this.height; // initially undefined!
baseHeight -= this._borderWidth * 2;
// For robustness, as there are legal cases where this.height may be undefined.
baseHeight = (isNaN(baseHeight) || baseHeight < 0) ? 0 : baseHeight;
return ParsedLengthStyle.inPixels(baseHeight);
} else {
return ParsedLengthStyle.forScalar(1);

View file

@ -21,8 +21,7 @@ builder_describe "Builds predictive-text components used within Keyman Engine fo
":wordbreakers Builds the wordbreakers provided for lexical model use" \
":worker-main Builds the predictive-text worker interface module" \
":worker-thread Builds the predictive-text worker" \
":_all (Meta build target used when targets are not specified)" \
"--ci+ Set to utilize CI-based test configurations & reporting."
":_all (Meta build target used when targets are not specified)"
# Possible TODO?
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \
@ -57,4 +56,4 @@ builder_run_child_actions build:templates
builder_run_child_actions build:worker-thread
builder_run_child_actions build:worker-main
builder_run_child_actions test
builder_run_child_actions test

View file

@ -9,6 +9,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
################################ Main script ################################
@ -19,8 +20,7 @@ builder_describe "Builds the predictive-text model template implementation modul
"clean" \
"configure" \
"build" \
"test" \
"--ci"
"test"
builder_describe_outputs \
configure /node_modules \
@ -38,7 +38,7 @@ function do_build() {
function do_test() {
local FLAGS=
if builder_has_option --ci; then
if builder_is_ci_build; then
FLAGS="-reporter mocha-teamcity-reporter"
fi

View file

@ -214,6 +214,6 @@ export function getLastPreCaretToken(wordBreaker: LexicalModelTypes.WordBreaking
// While it is currently identical to getLastWord, this may change in the future.
// It's best not to write ourselves into a corner on this one, as disambiguating later
// would likely be pretty painful.
export function wordbreak(wordBreaker: LexicalModelTypes.WordBreakingFunction, context: LexicalModelTypes.Context): LexicalModelTypes.USVString {
export function wordbreak(wordBreaker: LexicalModelTypes.WordBreakingFunction, context: LexicalModelTypes.Context): string {
return getLastPreCaretToken(wordBreaker, context);
}

View file

@ -42,7 +42,6 @@ import LexiconTraversal = LexicalModelTypes.LexiconTraversal;
import Suggestion = LexicalModelTypes.Suggestion;
import TextWithProbability = LexicalModelTypes.TextWithProbability;
import Transform = LexicalModelTypes.Transform;
import USVString = LexicalModelTypes.USVString;
import WithOutcome = LexicalModelTypes.WithOutcome;
import WordBreakingFunction = LexicalModelTypes.WordBreakingFunction;
@ -109,7 +108,7 @@ class Traversal implements LexiconTraversal {
this.totalWeight = totalWeight;
}
child(char: USVString): LexiconTraversal | undefined {
child(char: string): LexiconTraversal | undefined {
// May result for blank tokens resulting immediately after whitespace.
if(char == '') {
return this;
@ -128,7 +127,7 @@ class Traversal implements LexiconTraversal {
}
// Handles one code unit at a time.
private _child(char: USVString): Traversal | undefined {
private _child(char: string): Traversal | undefined {
const root = this.root;
const totalWeight = this.totalWeight;
const nextPrefix = this.prefix + char;
@ -154,7 +153,7 @@ class Traversal implements LexiconTraversal {
}
}
*children(): Generator<{char: USVString, traversal: () => LexiconTraversal}> {
*children(): Generator<{char: string, traversal: () => LexiconTraversal}> {
let root = this.root;
// We refer to the field multiple times in this method, and it doesn't change.
@ -299,7 +298,7 @@ export default class TrieModel implements LexicalModel {
};
}
toKey(text: USVString): USVString {
toKey(text: string): string {
return this._trie.toKey(text);
}

View file

@ -24,7 +24,6 @@ import { LexicalModelTypes } from '@keymanapp/common-types';
import Configuration = LexicalModelTypes.Configuration;
import Reversion = LexicalModelTypes.Reversion;
import Suggestion = LexicalModelTypes.Suggestion;
import USVString = LexicalModelTypes.USVString;
/**
* Tokens are signed 31-bit integers!
@ -87,7 +86,7 @@ interface CurrentWordMessage {
* Contains the 'current word' left of the caret given the Context
* of its source message - the 'wordbreak' message with matching Token value.
*/
word: USVString;
word: string;
}
/**

View file

@ -9,6 +9,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
################################ Main script ################################
@ -18,8 +19,7 @@ builder_describe "Builds the predictive-text wordbreaker implementation module"
"clean" \
"configure" \
"build" \
"test" \
"--ci"
"test"
builder_describe_outputs \
configure src/main/default/data.inc.ts \
@ -47,7 +47,7 @@ function do_build() {
function do_test() {
local FLAGS=
if builder_has_option --ci; then
if builder_is_ci_build; then
FLAGS="-reporter mocha-teamcity-reporter"
fi

View file

@ -12,6 +12,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
BUNDLE_CMD="node $KEYMAN_ROOT/web/src/tools/es-bundling/build/common-bundle.mjs"
@ -25,8 +26,7 @@ builder_describe "Builds the lm-layer module" \
"clean" \
"configure" \
"build" \
"test" \
"--ci Sets $(builder_term test) action to use CI-based test configurations & reporting"
"test"
builder_describe_outputs \
configure /node_modules \
@ -55,11 +55,7 @@ function do_build() {
# Note - the actual test setup is done in a separate test script, but it's easy
# enough to route the calls through.
function do_test() {
local TEST_OPTIONS=
if builder_has_option --ci; then
TEST_OPTIONS=--ci
fi
./unit_tests/test.sh test:headless test:browser ${TEST_OPTIONS}
./unit_tests/test.sh test:headless test:browser
}
builder_run_action configure do_configure

View file

@ -28,7 +28,6 @@ import Distribution = LexicalModelTypes.Distribution;
import Reversion = LexicalModelTypes.Reversion;
import Suggestion = LexicalModelTypes.Suggestion;
import Transform = LexicalModelTypes.Transform;
import USVString = LexicalModelTypes.USVString;
import PromiseStore from "./promise-store.js";
import { OutgoingMessage } from '@keymanapp/lm-message-types';
@ -62,7 +61,7 @@ export default class LMLayer {
/** Call this when the LMLayer has sent us the 'ready' message! */
private _declareLMLayerReady: (conf: Configuration) => void;
private _predictPromises: PromiseStore<Suggestion[]>;
private _wordbreakPromises: PromiseStore<USVString>;
private _wordbreakPromises: PromiseStore<string>;
private _acceptPromises: PromiseStore<Reversion>;
private _revertPromises: PromiseStore<Suggestion[]>;
private _nextToken: number;
@ -152,7 +151,7 @@ export default class LMLayer {
});
}
wordbreak(context: Context): Promise<USVString> {
wordbreak(context: Context): Promise<string> {
let token = this._nextToken++;
return new Promise((resolve, reject) => {
this._wordbreakPromises.make(token, resolve, reject);

View file

@ -20,56 +20,22 @@ FLAGS=""
builder_describe "Runs all tests for the language-modeling / predictive-text layer module" \
"configure" \
"test+" \
":libraries Runs unit tests for in-repo libraries used by this module"\
":headless Runs this module's headless user tests" \
":browser Runs this module's browser-based user tests" \
"--ci Uses CI-based test configurations & emits CI-friendly test reports"
":browser Runs this module's browser-based user tests"
# TODO: consider dependencies? ideally this will be test.inc.sh?
builder_parse "$@"
TEST_OPTS=
if builder_has_option --ci && builder_is_debug_build; then
builder_die "Options --ci and --debug are incompatible."
elif builder_has_option --ci; then
TEST_OPTS=--ci
fi
if builder_start_action configure; then
verify_npm_setup
builder_finish_action success configure
fi
if builder_start_action test:libraries; then
# Note: these do not yet provide TeamCity-friendly-formatted test reports.
# They do not have builder-based scripts, being run directly via npm package script.
# So, for now, we add a text header to clarify what is running at each stage, in
# addition to fair bit of `pushd` and `popd`.
echo
echo "### Running $(builder_term web/src/engine/predictive-text/wordbreakers) tests"
"$KEYMAN_ROOT/web/src/engine/predictive-text/wordbreakers/build.sh" test $TEST_OPTS
pushd "$KEYMAN_ROOT/web/src/engine/predictive-text/templates/"
echo
echo "### Running $(builder_term web/src/engine/predictive-text/templates/) tests"
"$KEYMAN_ROOT/web/src/engine/predictive-text/templates/build.sh" test $TEST_OPTS
popd
pushd "$KEYMAN_ROOT/web/src/engine/predictive-text/worker-thread"
echo
echo "### Running ${BUILDER_TERM_START}web/src/engine/predictive-text/worker-thread${BUILDER_TERM_END} tests"
./build.sh test $TEST_OPTS
popd
builder_finish_action success test:libraries
fi
if builder_start_action test:headless; then
MOCHA_FLAGS=$FLAGS
if builder_has_option --ci; then
if builder_is_ci_build; then
MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter"
fi
@ -78,38 +44,11 @@ if builder_start_action test:headless; then
builder_finish_action success test:headless
fi
# If we are running a TeamCity test build, for now, only run BrowserStack
# tests when on a PR branch with a title including "(web)" or with the label
# test-browserstack. This is because the BrowserStack tests are currently
# unreliable, and the false positive failures are masking actual failures.
#
# We do not run BrowserStack tests on master, beta, or stable-x.y test
# builds.
if [[ $VERSION_ENVIRONMENT == test ]] && builder_has_action test:browser; then
if builder_pull_get_details; then
if ! ([[ $builder_pull_title =~ \(web\) ]] || builder_pull_has_label test-browserstack); then
echo "Auto-skipping $(builder_term test:browser) for unrelated CI test build"
exit 0
fi
fi
fi
get_browser_set_for_OS ( ) {
if [[ $BUILDER_OS == mac ]]; then
BROWSERS="--browsers Firefox,Chrome,Safari"
elif [[ $BUILDER_OS == win ]]; then
BROWSERS="--browsers Chrome"
else
BROWSERS="--browsers Firefox,Chrome"
fi
}
if builder_start_action test:browser; then
WTR_CONFIG=
WTR_DEBUG=
if builder_has_option --ci; then
if builder_is_ci_build; then
WTR_CONFIG=.CI
fi

View file

@ -10,6 +10,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
WORKER_OUTPUT=build/obj
WORKER_OUTPUT_FILENAME=build/lib/worker-main.js
@ -30,7 +31,7 @@ builder_describe \
"@/web/src/tools/es-bundling" \
"@../wordbreakers" \
"@../templates" \
configure clean build test --ci
configure clean build test
builder_describe_outputs \
configure /node_modules \
@ -47,11 +48,6 @@ function do_configure() {
}
function do_build() {
EXT_FLAGS=
if builder_has_option --ci; then
EXT_FLAGS=--ci
fi
# Declaration bundling.
tsc --emitDeclarationOnly --outFile $INTERMEDIATE/worker-main.d.ts
@ -98,7 +94,7 @@ function do_test() {
local WTR_CONFIG=
local WTR_DEBUG=
if builder_has_option --ci; then
if builder_is_ci_build; then
MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter"
WTR_CONFIG=.CI
fi

View file

@ -12,7 +12,6 @@ import Distribution = LexicalModelTypes.Distribution;
import LexicalModel = LexicalModelTypes.LexicalModel;
import Suggestion = LexicalModelTypes.Suggestion;
import Transform = LexicalModelTypes.Transform;
import USVString = LexicalModelTypes.USVString;
function textToCharTransforms(text: string, transformId?: number) {
let perCharTransforms: Transform[] = [];
@ -95,7 +94,7 @@ export class TrackedContextToken {
* @param tokenText
* @param transformId
*/
updateWithBackspace(tokenText: USVString, transformId: number) {
updateWithBackspace(tokenText: string, transformId: number) {
// It's a backspace transform; time for special handling!
//
// For now, with 14.0, we simply compress all remaining Transforms for the token into
@ -114,7 +113,7 @@ export class TrackedContextToken {
this.clearReplacements();
}
update(transformDistribution: Distribution<Transform>, tokenText?: USVString) {
update(transformDistribution: Distribution<Transform>, tokenText?: string) {
// Preserve existing text if new text isn't specified.
tokenText = tokenText || (tokenText === '' ? '' : this.raw);
@ -211,7 +210,7 @@ export class TrackedContextState {
}
toRawTokenization() {
let sequence: USVString[] = [];
let sequence: string[] = [];
for(let token of this.tokens) {
// Hide any tokens representing wordbreaks. (Thinking ahead to phrase-level possibilities)
@ -353,7 +352,7 @@ export class ContextTracker extends CircularArray<TrackedContextState> {
transformSequenceDistribution?: Distribution<Transform[]>
): ContextMatchResult {
// Map the previous tokenized state to an edit-distance friendly version.
let matchContext: USVString[] = matchState.toRawTokenization();
let matchContext: string[] = matchState.toRawTokenization();
// Inverted order, since 'match' existed before our new context.
let mapping = ClassicalDistanceCalculation.computeDistance(

View file

@ -9,7 +9,6 @@ import LexicalModel = LexicalModelTypes.LexicalModel;
import LexiconTraversal = LexicalModelTypes.LexiconTraversal;
import ProbabilityMass = LexicalModelTypes.ProbabilityMass;
import Transform = LexicalModelTypes.Transform;
import USVString = LexicalModelTypes.USVString;
type RealizedInput = ProbabilityMass<Transform>[]; // NOT Distribution - they're masses from separate distributions.
@ -48,15 +47,15 @@ export class SearchNode {
calculation: ClassicalDistanceCalculation<string, EditToken<string>, TraversableToken<string>>;
currentTraversal: LexiconTraversal;
toKey: (wordform: USVString) => USVString = str => str;
toKey: (wordform: string) => string = str => str;
priorInput: RealizedInput;
// Internal lazy-cache for .inputSamplingCost, as it's a bit expensive to re-compute.
private _inputCost?: number;
constructor(rootTraversal: LexiconTraversal, toKey?: (arg0: USVString) => USVString);
constructor(rootTraversal: LexiconTraversal, toKey?: (arg0: string) => string);
constructor(node: SearchNode);
constructor(rootTraversal: LexiconTraversal | SearchNode, toKey?: (arg0: USVString) => USVString) {
constructor(rootTraversal: LexiconTraversal | SearchNode, toKey?: (arg0: string) => string) {
toKey = toKey || (x => x);
if(rootTraversal instanceof SearchNode) {
@ -294,11 +293,11 @@ export class SearchResult {
return this.resultNode.priorInput;
}
get matchSequence(): TraversableToken<USVString>[] {
get matchSequence(): TraversableToken<string>[] {
return this.resultNode.calculation.matchSequence;
};
get matchString(): USVString {
get matchString(): string {
return this.resultNode.resultKey;
}

View file

@ -15,7 +15,6 @@ import LexicalModelPunctuation = LexicalModelTypes.LexicalModelPunctuation;
import Reversion = LexicalModelTypes.Reversion;
import Suggestion = LexicalModelTypes.Suggestion;
import Transform = LexicalModelTypes.Transform;
import USVString = LexicalModelTypes.USVString;
export class ModelCompositor {
private lexicalModel: LexicalModel;
@ -194,7 +193,7 @@ export class ModelCompositor {
}
// Responsible for applying casing rules to suggestions.
private applySuggestionCasing(suggestion: Suggestion, baseWord: USVString, casingForm: CasingForm) {
private applySuggestionCasing(suggestion: Suggestion, baseWord: string, casingForm: CasingForm) {
// Step 1: does the suggestion replace the whole word? If not, we should extend the suggestion to do so.
let unchangedLength = KMWString.length(baseWord) - suggestion.transform.deleteLeft;

View file

@ -19,8 +19,7 @@ builder_describe "Builds the Keyman Engine for Web's sample page setups." \
"configure Does nothing for this project" \
"clean" \
"build" \
"test Does nothing for this project" \
"--ci Does nothing for this project"
"test Does nothing for this project"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -18,8 +18,7 @@ builder_describe "Builds the Keyman Engine for Web's sample page setups." \
"configure Does nothing for this project" \
"clean" \
"build" \
"test Does nothing for this project" \
"--ci Does nothing for this project"
"test Does nothing for this project"
# Possible TODO?s
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \

View file

@ -61,7 +61,7 @@ const cfg = require('karma').config;
// Note: if karma.conf.js is invalid, then this will die with exit code 1
// without logging the error. The easiest way to see the error is to
// run `node_modules/bin/karma start karma.conf.js`
const karmaConfig = cfg.parseConfig(path.resolve('./karma.conf.js'), process.env.TEAMCITY_PROJECT_NAME ? {'reporters': ['teamcity']} : {});
const karmaConfig = cfg.parseConfig(path.resolve('./karma.conf.js'), process.env.TEAMCITY_GIT_PATH ? {'reporters': ['teamcity']} : {});
let server = new Server(karmaConfig, function(exitCode) {
console.log('Karma has exited with ' + exitCode)

View file

@ -19,7 +19,6 @@ builder_describe "Builds the Keyman Engine for Web's development & unit-testing
"clean" \
"build" \
"test" \
"--ci Does nothing for this script" \
":bulk_rendering=testing/bulk_rendering Builds the bulk-rendering tool used to validate changes to OSK display code" \
":es-bundling Builds code used to esbundle Web engine artifacts for browser consumption" \
":recorder=testing/recorder Builds the KMW recorder tool used for development of unit-test resources" \

View file

@ -20,8 +20,7 @@ builder_describe "Runs the Keyman Engine for Web unit-testing suites" \
"@./src/tools/testing/recorder test:integrated" \
"test+" \
":dom Runs DOM-oriented unit tests (reduced footprint, nothing browser-specific)" \
":integrated Runs KMW's integration test suite" \
"--ci Set to utilize CI-based test configurations & reporting. May not be set with $(builder_term --debug)."
":integrated Runs KMW's integration test suite"
builder_parse "$@"
@ -29,7 +28,7 @@ builder_parse "$@"
# Select the right CONFIG file.
WTR_CONFIG=
if builder_has_option --ci; then
if builder_is_ci_build; then
WTR_CONFIG=.CI
fi

View file

@ -59,6 +59,7 @@ uses
OnlineUpdateCheckMessages,
RegistryKeys,
Upload_Settings,
UtilCheckOnline,
utilkmshell;
procedure ErrorLogMessage(ErrorLogMessage: string);
@ -143,6 +144,7 @@ begin
else
begin
Params.Packages[i].Install := False; // Download failed but install other files
TKeymanSentryClient.Breadcrumb('error', 'Download failded for keyboard: '+ Params.Packages[i].DownloadURL, 'cli.download');
end;
end;
@ -157,6 +159,7 @@ begin
else
begin
ErrorLogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL);
TKeymanSentryClient.Breadcrumb('error', 'Download failded for keyboard: '+ Params.InstallURL, 'cli.download');
end;
end;
// If there is at least one keyboard package or version of keyman downloaded then
@ -172,6 +175,11 @@ var
DownloadBackGroundSavePath : String;
ucr: TUpdateCheckResponse;
begin
if not IsOnline then
begin
TKeymanSentryClient.Breadcrumb('default', 'TDownloadUpdate: Not Online, cant execute download process.', 'cli.download');
Exit;
end;
DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath);
if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then
begin

View file

@ -126,6 +126,7 @@ uses
KLog,
RegistryKeys,
utilexecute,
UtilCheckOnline,
utiluac;
const
@ -685,6 +686,12 @@ var
FResult: Boolean;
RootPath: string;
begin
// check if online
if not IsOnline then
begin
TKeymanSentryClient.Breadcrumb('default', 'Not Online, cant execute download process.', 'update');
Exit;
end;
// call separate process
RootPath := ExtractFilePath(ParamStr(0));
FResult := TUtilExecute.ShellCurrentUser(0, ParamStr(0),