mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 00:15:32 +00:00
Merge branch 'master' into fix/windows/15571-contextual-help-chm-file-has-blank-pages
Some checks failed
Keyman Build Summary / Summarize build status checks (push) Has been cancelled
Some checks failed
Keyman Build Summary / Summarize build status checks (push) Has been cancelled
This commit is contained in:
commit
2f61208dec
35 changed files with 990 additions and 252 deletions
|
|
@ -25,7 +25,7 @@ runs:
|
|||
using: 'composite'
|
||||
steps:
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: keyman-srcpkg
|
||||
path: artifacts/keyman-srcpkg
|
||||
|
|
@ -49,7 +49,7 @@ runs:
|
|||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Store binary packages
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: keyman-binarypkgs-${{ inputs.dist }}_${{ inputs.arch }}
|
||||
path: |
|
||||
|
|
|
|||
195
.github/labeler.yml
vendored
195
.github/labeler.yml
vendored
|
|
@ -5,86 +5,173 @@
|
|||
# common ones. The others are commented out. There is still some variance between
|
||||
# folder names and labels; consider this documentation of that ;-)
|
||||
|
||||
docs: docs/**
|
||||
docs:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'docs/**'
|
||||
|
||||
#
|
||||
# Add labels based on changed files using actions/labeler
|
||||
#
|
||||
|
||||
android/:
|
||||
- android/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/android/**
|
||||
android/app/: android/KMAPro/**
|
||||
android/engine/: android/KMEA/**
|
||||
android/samples/: android/Samples/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- android/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/android/**
|
||||
android/app/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- android/KMAPro/**
|
||||
android/engine/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- android/KMEA/**
|
||||
android/samples/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- android/Samples/**
|
||||
|
||||
common/:
|
||||
- common/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/common/**
|
||||
common/web/: common/web/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- common/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/common/**
|
||||
common/web/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- common/web/**
|
||||
|
||||
core/:
|
||||
- core/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/core/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- core/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/core/**
|
||||
|
||||
developer/:
|
||||
- developer/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/developer/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- developer/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/developer/**
|
||||
developer/compilers/:
|
||||
- developer/src/kmc/**
|
||||
- developer/src/kmcmplib/**
|
||||
- developer/src/kmc-*/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- developer/src/kmc/**
|
||||
- developer/src/kmcmplib/**
|
||||
- developer/src/kmc-*/**
|
||||
developer/ide/:
|
||||
- developer/src/server/**
|
||||
- developer/src/tike/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- developer/src/server/**
|
||||
- developer/src/tike/**
|
||||
|
||||
ios/:
|
||||
- ios/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/ios/**
|
||||
ios/app/: ios/keyman/**
|
||||
ios/engine/: ios/engine/**
|
||||
ios/samples/: ios/samples/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ios/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/ios/**
|
||||
ios/app/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ios/keyman/**
|
||||
ios/engine/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ios/engine/**
|
||||
ios/samples/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ios/samples/**
|
||||
|
||||
linux/:
|
||||
- linux/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/linux/**
|
||||
linux/config/: linux/keyman-config/**
|
||||
linux/engine/: linux/ibus-keyman/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- linux/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/linux/**
|
||||
linux/config/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- linux/keyman-config/**
|
||||
linux/engine/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- linux/ibus-keyman/**
|
||||
|
||||
mac/:
|
||||
- mac/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/mac/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- mac/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/mac/**
|
||||
# mac/config/:
|
||||
# mac/engine/: mac/**
|
||||
|
||||
oem/: oem/**
|
||||
oem/fv/: oem/firstvoices/**
|
||||
oem/fv/android/: oem/firstvoices/android/**
|
||||
oem/fv/ios/: oem/firstvoices/ios/**
|
||||
oem/fv/windows/: oem/firstvoices/windows/**
|
||||
oem/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- oem/**
|
||||
oem/fv/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- oem/firstvoices/**
|
||||
oem/fv/android/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- oem/firstvoices/android/**
|
||||
oem/fv/ios/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- oem/firstvoices/ios/**
|
||||
oem/fv/windows/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- oem/firstvoices/windows/**
|
||||
|
||||
resources/: resources/**
|
||||
resources/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- resources/**
|
||||
|
||||
web/:
|
||||
- web/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/web/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- web/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/web/**
|
||||
# web/bookmarklet/
|
||||
web/engine/: web/source/**
|
||||
web/ui/: web/source/kmwui*
|
||||
web/samples/: web/samples/**
|
||||
web/predictive-text/: web/src/engine/predictive-text/**
|
||||
web/engine/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- web/source/**
|
||||
web/ui/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- web/source/kmwui*
|
||||
web/samples/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- web/samples/**
|
||||
web/predictive-text/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- web/src/engine/predictive-text/**
|
||||
|
||||
windows/:
|
||||
- windows/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/windows/**
|
||||
windows/config/: windows/src/desktop/**
|
||||
windows/engine/: windows/src/engine/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- windows/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/windows/**
|
||||
windows/config/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- windows/src/desktop/**
|
||||
windows/engine/:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- windows/src/engine/**
|
||||
|
|
|
|||
6
.github/workflows/api-verification.yml
vendored
6
.github/workflows/api-verification.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Restore artifacts
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: |
|
||||
artifacts
|
||||
|
|
@ -55,7 +55,7 @@ jobs:
|
|||
|
||||
- name: Checkout
|
||||
if: steps.environment_step.outputs.SKIP_API_CHECK != 'true'
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: '${{ steps.environment_step.outputs.GIT_SHA }}'
|
||||
fetch-depth: 0
|
||||
|
|
@ -84,7 +84,7 @@ jobs:
|
|||
|
||||
- name: Archive .symbols file
|
||||
if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' && always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: libkeymancore.symbols
|
||||
path: ${{ github.workspace }}/keyman/linux/debian/tmp/DEBIAN/symbols
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout repository
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Build Docker images
|
||||
id: build
|
||||
run: |
|
||||
|
|
@ -67,7 +67,7 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout repository
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Test Docker images
|
||||
id: test
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ jobs:
|
|||
npm -v
|
||||
node -v
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
|
|
|
|||
2
.github/workflows/crowdin.yml
vendored
2
.github/workflows/crowdin.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v2.7.0
|
||||
|
|
|
|||
18
.github/workflows/deb-packaging.yml
vendored
18
.github/workflows/deb-packaging.yml
vendored
|
|
@ -43,7 +43,7 @@ jobs:
|
|||
PRERELEASE_TAG: ${{ steps.prerelease_tag.outputs.PRERELEASE_TAG }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ jobs:
|
|||
echo "- $(find . -name keyman_\*.dsc)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Store source package
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: keyman-srcpkg
|
||||
path: |
|
||||
|
|
@ -140,7 +140,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
sparse-checkout: '.github/actions/'
|
||||
|
|
@ -166,7 +166,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
sparse-checkout: '.github/actions/'
|
||||
|
|
@ -204,13 +204,13 @@ jobs:
|
|||
sudo rm -rf /usr/share/dotnet
|
||||
|
||||
- name: Download Source Artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: keyman-srcpkg
|
||||
path: artifacts
|
||||
|
||||
- name: Download Binary Artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: keyman-binarypkgs-*
|
||||
|
|
@ -281,7 +281,7 @@ jobs:
|
|||
echo "::endgroup::"
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: keyman-signedpkgs
|
||||
|
||||
|
|
@ -343,7 +343,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: keyman-*
|
||||
|
|
@ -362,7 +362,7 @@ jobs:
|
|||
echo "SKIP_API_CHECK=${{ github.event.client_payload.skipApiCheck }}" >> artifacts/env
|
||||
|
||||
- name: Cache artifacts
|
||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: |
|
||||
artifacts
|
||||
|
|
|
|||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
|
|
@ -8,7 +8,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update labels based on changed files
|
||||
uses: actions/labeler@v4
|
||||
uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Update labels based on PR title
|
||||
|
|
|
|||
2
.github/workflows/npm-publish.yml
vendored
2
.github/workflows/npm-publish.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
|
||||
|
|
|
|||
72
.github/workflows/pr-build-status.yml
vendored
72
.github/workflows/pr-build-status.yml
vendored
|
|
@ -1,40 +1,40 @@
|
|||
# GENERATED FILE - DO NOT EDIT!
|
||||
#
|
||||
# Keyman is copyright (C) SIL Global. MIT License.
|
||||
#
|
||||
# Do not modify the script in .github/workflows/pr-build-status.yml directly;
|
||||
# instead work on the sources in resources/build/pr-build-status and use the
|
||||
# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from
|
||||
# them.
|
||||
#
|
||||
# build.sh will append the relevant portions of
|
||||
# resources/build/pr-build-status/pr-build-status.mjs to the content in
|
||||
# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github
|
||||
# workflow file.
|
||||
#
|
||||
name: Keyman Build Summary
|
||||
on:
|
||||
status:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
- beta
|
||||
- stable-*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit:
|
||||
description: 'Commit sha'
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
run_pr_build_status:
|
||||
name: Summarize build status checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR build status
|
||||
id: run_pr_build_status_script
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
#
|
||||
# Keyman is copyright (C) SIL Global. MIT License.
|
||||
#
|
||||
# Do not modify the script in .github/workflows/pr-build-status.yml directly;
|
||||
# instead work on the sources in resources/build/pr-build-status and use the
|
||||
# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from
|
||||
# them.
|
||||
#
|
||||
# build.sh will append the relevant portions of
|
||||
# resources/build/pr-build-status/pr-build-status.mjs to the content in
|
||||
# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github
|
||||
# workflow file.
|
||||
#
|
||||
name: Keyman Build Summary
|
||||
on:
|
||||
status:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
- beta
|
||||
- stable-*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit:
|
||||
description: 'Commit sha'
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
run_pr_build_status:
|
||||
name: Summarize build status checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR build status
|
||||
id: run_pr_build_status_script
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
// This code is copied out of resources/build/pr-build-status/pr-build-status.mjs
|
||||
// where it is tested. It is copied inline here in order to avoid requiring the
|
||||
|
|
|
|||
47
HISTORY.md
47
HISTORY.md
|
|
@ -1,5 +1,43 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.222 alpha 2026-04-08
|
||||
|
||||
* fix(linux): fix memory problem (#15823)
|
||||
|
||||
## 19.0.221 alpha 2026-03-27
|
||||
|
||||
* fix(common): add missing `default` property to keyman-touch-layout.clean.spec.json (#15787)
|
||||
* chore(deps): bump picomatch from 4.0.3 to 4.0.4 in /developer/src/server/src/win32/trayicon/addon-src (#15786)
|
||||
* chore(deps): bump picomatch from 2.3.1 to 2.3.2 (#15784)
|
||||
* chore(deps-dev): bump flatted from 3.2.5 to 3.4.2 (#15773)
|
||||
* chore(deps): bump brace-expansion from 5.0.4 to 5.0.5 in /developer/src/server/src/win32/trayicon/addon-src (#15791)
|
||||
|
||||
## 19.0.220 alpha 2026-03-26
|
||||
|
||||
* fix(mac): improved adherence to backspace rules for compliant apps (#15561)
|
||||
|
||||
## 19.0.219 alpha 2026-03-25
|
||||
|
||||
* maint(common): Update GitHub actions for Node 24 (#15762)
|
||||
* fix(core): fix keydown/up handling for LDML keyboards (#15609)
|
||||
|
||||
## 19.0.218 alpha 2026-03-24
|
||||
|
||||
* fix(developer): define globalThis for compiled custom lexical models (#15777)
|
||||
|
||||
## 19.0.217 alpha 2026-03-17
|
||||
|
||||
* maint(mac): search for brew-installed rsync (#15765)
|
||||
|
||||
## 19.0.216 alpha 2026-03-16
|
||||
|
||||
* docs: tweak walkthrough content (#15712)
|
||||
* chore(deps): bump tar from 7.5.10 to 7.5.11 in /developer/src/server/src/win32/trayicon/addon-src (#15723)
|
||||
* docs(linux): add documentation how input methods work in GTK (#15749)
|
||||
* docs(core): improve keyhandling doc (#15736)
|
||||
* maint(resources): try meson 1.10.1 (#15754)
|
||||
* maint(ios): clean carthage before builds (#15757)
|
||||
|
||||
## 19.0.215 alpha 2026-03-13
|
||||
|
||||
* chore(linux): fix dependency of Debian test suite (#15735)
|
||||
|
|
@ -1288,6 +1326,15 @@
|
|||
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
|
||||
* chore: increment to alpha 19.0 (#13187)
|
||||
|
||||
## 18.0.249 stable 2026-03-27
|
||||
|
||||
* chore(linux): Update debian changelog (#15717)
|
||||
* chore(linux): fix dependency of Debian test suite (#15734)
|
||||
* chore(linux): Update debian changelog (#15731)
|
||||
* maint(ios): Upgrade Sentry to 8.58.0 to support XCode 26 (#15755)
|
||||
* fix(developer): define globalThis for compiled custom lexical models (#15778)
|
||||
* fix(common): add missing `default` property to keyman-touch-layout.clean.spec.json (#15792)
|
||||
|
||||
## 18.0.248 stable 2026-03-12
|
||||
|
||||
* fix(ios): Revert Sentry to 8.38.0 to fix FirstVoices crash on startup with 18.0.247 (#15726)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.216
|
||||
19.0.223
|
||||
|
|
@ -457,6 +457,10 @@ string") into the appropriate spec format.
|
|||
|
||||
# .keyman-touch-layout version history
|
||||
|
||||
## 2026-03-26 2.1.2 stable
|
||||
* Add missing 'default' property for longpress (sk) keys to clean spec. No other
|
||||
changes.
|
||||
|
||||
## 2024-02-23 2.1.1 stable
|
||||
* Loosen `layer.id` requirements to any non-whitespace characters, recommend
|
||||
only alphanumeric, -, _. clean spec enforces this recommendation.
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@
|
|||
"text": { "type": "string" },
|
||||
"layer": { "$ref": "#/definitions/layer-id" },
|
||||
"nextlayer": { "$ref": "#/definitions/layer-id" },
|
||||
"font": { "$ref": "#/definitions/font-spec" },
|
||||
"fontsize": { "$ref": "#/definitions/fontsize-spec" },
|
||||
"sp": { "$ref": "#/definitions/key-sp" },
|
||||
"font": { "$ref": "#/definitions/font-spec" },
|
||||
"sp": { "$ref" : "#/definitions/key-sp" },
|
||||
"pad": { "$ref" : "#/definitions/key-pad" },
|
||||
"width": { "$ref" : "#/definitions/key-width" },
|
||||
"sk": { "$ref": "#/definitions/subkeys" },
|
||||
|
|
@ -140,11 +140,12 @@
|
|||
"text": { "type": "string" },
|
||||
"layer": { "$ref": "#/definitions/layer-id" },
|
||||
"nextlayer": { "$ref": "#/definitions/layer-id" },
|
||||
"font": { "$ref": "#/definitions/font-spec" },
|
||||
"fontsize": { "$ref": "#/definitions/fontsize-spec" },
|
||||
"sp": { "$ref": "#/definitions/key-sp" },
|
||||
"sp": { "$ref" : "#/definitions/key-sp" },
|
||||
"pad": { "$ref" : "#/definitions/key-pad" },
|
||||
"width": { "$ref" : "#/definitions/key-width" }
|
||||
"width": { "$ref" : "#/definitions/key-width" },
|
||||
"fontsize": { "$ref": "#/definitions/fontsize-spec" },
|
||||
"font": { "$ref": "#/definitions/font-spec" },
|
||||
"default": { "type": "boolean" }
|
||||
},
|
||||
"required": ["id"],
|
||||
"additionalProperties": false
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ km_core_process_event(km_core_state *state,
|
|||
if(state == nullptr) {
|
||||
return KM_CORE_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
if (vk == KM_CORE_VKEY_BKSP && is_key_down) {
|
||||
state->set_backspace_handled_internally(false);
|
||||
}
|
||||
km_core_status status = state->processor().process_event(state, vk, modifier_state, is_key_down, event_flags);
|
||||
|
||||
if (state_should_invalidate_context(state, vk, modifier_state, is_key_down, event_flags)) {
|
||||
|
|
@ -78,6 +81,10 @@ km_core_process_event(km_core_state *state,
|
|||
|
||||
state->apply_actions_and_merge_app_context();
|
||||
|
||||
if (vk == KM_CORE_VKEY_BKSP) {
|
||||
state->set_backspace_handled_internally(!state->action_struct().emit_keystroke);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,21 +179,13 @@ ldml_processor::process_event(
|
|||
ldml_state.clear();
|
||||
|
||||
try {
|
||||
if (!is_key_down) {
|
||||
process_key_up(ldml_state);
|
||||
if (vk == KM_CORE_VKEY_BKSP) {
|
||||
process_backspace(ldml_state);
|
||||
} else if (is_key_down) {
|
||||
process_key_down(ldml_state);
|
||||
} else {
|
||||
switch (vk) {
|
||||
// Currently, only one VK gets spoecial treatment.
|
||||
// Special handling for backspace VK
|
||||
case KM_CORE_VKEY_BKSP:
|
||||
process_backspace(ldml_state);
|
||||
break;
|
||||
default:
|
||||
// all other VKs
|
||||
process_key_down(ldml_state);
|
||||
} // end of switch
|
||||
} // end of normal processing
|
||||
|
||||
process_key_up(ldml_state);
|
||||
}
|
||||
// all key-up and key-down events end up here.
|
||||
// commit the ldml state into the core state
|
||||
ldml_state.commit();
|
||||
|
|
@ -210,11 +202,31 @@ void
|
|||
ldml_processor::process_key_up(ldml_event_state &ldml_state)
|
||||
const {
|
||||
// TODO-LDML: Implement caps lock handling
|
||||
ldml_state.clear();
|
||||
|
||||
// Look up the key
|
||||
bool found = false;
|
||||
const std::u16string key_str = keys.lookup(ldml_state.get_vk(), ldml_state.get_modifier_state(), found);
|
||||
|
||||
if (!found) {
|
||||
ldml_state.emit_passthrough_keystroke();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ldml_processor::process_backspace(ldml_event_state &ldml_state) const {
|
||||
if (ldml_state.get_modifier_state() & K_MODIFIERFLAG) {
|
||||
// we never process modifier+bksp
|
||||
ldml_state.emit_passthrough_keystroke();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ldml_state.is_key_down()) {
|
||||
if (!ldml_state.get_state()->backspace_handled_internally()) {
|
||||
ldml_state.emit_passthrough_keystroke();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!!bksp_transforms) {
|
||||
// process with an empty string via the bksp transforms
|
||||
auto matchedContext = process_output(ldml_state, std::u32string(), bksp_transforms.get());
|
||||
|
|
|
|||
|
|
@ -176,6 +176,9 @@ public:
|
|||
* @return the number of context items consumed
|
||||
*/
|
||||
size_t context_to_string(std::u32string &str, bool include_markers = true);
|
||||
km_core_state* get_state() const { return state; }
|
||||
|
||||
uint8_t is_key_down() const { return _is_key_down; }
|
||||
|
||||
private:
|
||||
km_core_virtual_key _vk;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ state::state(km::core::abstract_processor & ap, km_core_option_item const *env)
|
|||
env->key,
|
||||
env->value);
|
||||
}
|
||||
_backspace_handled_internally = false;
|
||||
_imx_callback = nullptr;
|
||||
_imx_object = nullptr;
|
||||
memset(const_cast<km_core_actions*>(&_action_struct), 0, sizeof(km_core_actions));
|
||||
|
|
@ -117,4 +118,4 @@ void state::apply_actions_and_merge_app_context() {
|
|||
}
|
||||
|
||||
this->_action_struct.deleted_context = km::core::get_deleted_context(app_context_for_deletion, this->_action_struct.code_points_to_delete);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ protected:
|
|||
core::debug_items _debug_items;
|
||||
km_core_keyboard_imx_platform _imx_callback;
|
||||
void *_imx_object;
|
||||
bool _backspace_handled_internally;
|
||||
|
||||
public:
|
||||
state(core::abstract_processor & kb, km_core_option_item const *env);
|
||||
|
|
@ -174,6 +175,22 @@ public:
|
|||
km_core_actions const &actions
|
||||
);
|
||||
void apply_actions_and_merge_app_context();
|
||||
|
||||
/**
|
||||
* This is used to track whether the backspace key was handled internally
|
||||
* during the keydown event. This is needed so that we can return the same
|
||||
* value from the keyup event as we did for the keydown event.
|
||||
*
|
||||
* Backspace is the only key that we sometimes handle internally (if we
|
||||
* have enough context) and sometimes not. By the time we get the keyup
|
||||
* event the context already got updated and so we have no way of knowing
|
||||
* whether or not the keydown handled it internally. Therefore this
|
||||
* flag exists.
|
||||
*
|
||||
* Only used when processing KM_CORE_VKEY_BKSP with LDML keyboards.
|
||||
*/
|
||||
void set_backspace_handled_internally(bool handled) { _backspace_handled_internally = handled; }
|
||||
bool backspace_handled_internally() const { return _backspace_handled_internally; }
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace km
|
||||
|
|
|
|||
355
core/tests/unit/km_core_process_event.tests.cpp
Normal file
355
core/tests/unit/km_core_process_event.tests.cpp
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
#include <gtest/gtest.h>
|
||||
#include "path.hpp"
|
||||
#include "state.hpp"
|
||||
#include "kmx/kmx_processevent.h"
|
||||
|
||||
#include "emscripten_filesystem.h"
|
||||
#include "kmnkbd/action_items.hpp"
|
||||
#include "load_kmx_file.hpp"
|
||||
|
||||
using namespace km::core::kmx;
|
||||
km::core::path test_dir;
|
||||
|
||||
km_core_option_item test_env_opts[] = {KM_CORE_OPTIONS_END};
|
||||
|
||||
#define KEY_DOWN TRUE
|
||||
#define KEY_UP FALSE
|
||||
|
||||
struct TestData {
|
||||
const char* test_name;
|
||||
|
||||
const char* keyboard_name;
|
||||
km_core_virtual_key vkey;
|
||||
uint16_t modifier_state;
|
||||
|
||||
// Initial context
|
||||
km_core_cu const* context;
|
||||
|
||||
// Whether keydown handled the event. Only relevant for keyup tests.
|
||||
bool keydown_handled;
|
||||
|
||||
// Expected actions for keydown and keyup
|
||||
std::initializer_list<km_core_action_item> keydown_actions;
|
||||
std::initializer_list<km_core_action_item> keyup_actions;
|
||||
};
|
||||
|
||||
std::string GenerateTestName(const testing::TestParamInfo<TestData>& info) {
|
||||
return info.param.test_name;
|
||||
}
|
||||
|
||||
class ProcessEventTests : public testing::TestWithParam<TestData> {
|
||||
protected:
|
||||
km_core_keyboard* keyboard = nullptr;
|
||||
km_core_state* state = nullptr;
|
||||
KMX_ProcessEvent process_event;
|
||||
|
||||
void Initialize(TestData const& data) {
|
||||
km::core::path kmxfile = km::core::path(test_dir / data.keyboard_name);
|
||||
auto blob = km::tests::load_kmx_file(kmxfile.native().c_str());
|
||||
|
||||
EXPECT_EQ(km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), blob.data(), blob.size(), &this->keyboard), KM_CORE_STATUS_OK);
|
||||
EXPECT_EQ(km_core_state_create(this->keyboard, test_env_opts, &this->state), KM_CORE_STATUS_OK);
|
||||
EXPECT_TRUE(this->process_event.Load(blob.data(), blob.size()));
|
||||
if (data.context) {
|
||||
EXPECT_EQ(km_core_state_context_set_if_needed(this->state, data.context), KM_CORE_CONTEXT_STATUS_UPDATED);
|
||||
}
|
||||
((km::core::state*)this->state)->set_backspace_handled_internally(data.keydown_handled);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (this->state) {
|
||||
km_core_state_dispose(this->state);
|
||||
this->state = nullptr;
|
||||
}
|
||||
if (this->keyboard) {
|
||||
km_core_keyboard_dispose(this->keyboard);
|
||||
this->keyboard = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void print_all_action_items(km_core_state const* state) {
|
||||
size_t n = 0;
|
||||
auto act = km_core_state_action_items(state, &n);
|
||||
std::cout << "Action items:" << std::endl;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
print_action_item("", *act++);
|
||||
}
|
||||
std::cout << "---------------" << std::endl;
|
||||
}
|
||||
|
||||
TEST_P(ProcessEventTests, ReturnsExpectedActionsForKeyDown) {
|
||||
auto data = GetParam();
|
||||
Initialize(data);
|
||||
EXPECT_EQ(km_core_process_event(this->state, data.vkey, data.modifier_state,
|
||||
KEY_DOWN, KM_CORE_EVENT_FLAG_DEFAULT), KM_CORE_STATUS_OK);
|
||||
// print_all_action_items(this->state);
|
||||
EXPECT_TRUE(action_items(this->state, data.keydown_actions));
|
||||
}
|
||||
|
||||
TEST_P(ProcessEventTests, ReturnsExpectedActionsForKeyUp) {
|
||||
auto data = GetParam();
|
||||
Initialize(data);
|
||||
EXPECT_EQ(km_core_process_event(this->state, data.vkey, data.modifier_state, KEY_UP, KM_CORE_EVENT_FLAG_DEFAULT), KM_CORE_STATUS_OK);
|
||||
// print_all_action_items(this->state);
|
||||
EXPECT_TRUE(action_items(this->state, data.keyup_actions));
|
||||
}
|
||||
|
||||
union backspace_union {
|
||||
km_core_backspace_item backspace;
|
||||
uint32_t value;
|
||||
};
|
||||
// const backspace_union backspace_with_context = {{KM_CORE_BT_CHAR, 'x'}};
|
||||
const backspace_union backspace_no_context = {{KM_CORE_BT_CHAR, 0}};
|
||||
|
||||
const TestData values[] = {
|
||||
//--------------------------------------------------------------------
|
||||
// KMN
|
||||
// Key with rule
|
||||
{"KMN_VKey_A", "kmx/k_005___nul_with_initial_context.kmx", KM_CORE_VKEY_A, 0, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_CHAR, { 0, }, {'d'}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
{"KMN_Ctrl_VKey_A", "kmx/k_005___nul_with_initial_context.kmx", KM_CORE_VKEY_A, KM_CORE_MODIFIER_LCTRL, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Key without rule
|
||||
{"KMN_VKey_X", "kmx/k_005___nul_with_initial_context.kmx", KM_CORE_VKEY_X, 0, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_CHAR, { 0, }, {'x'}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
{"KMN_Ctrl_VKey_X", "kmx/k_005___nul_with_initial_context.kmx", KM_CORE_VKEY_X, KM_CORE_MODIFIER_LCTRL, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Frame key without rule
|
||||
{"KMN_VKey_Enter", "kmx/k_000___null_keyboard.kmx", KM_CORE_VKEY_ENTER, 0, u"x", false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// // Backspace (with available context)
|
||||
// // TODO: fix the implementation to make this test pass. Currently failing in KeyUp
|
||||
// {"KMN_VKey_Backspace_Ctxt", "kmx/k_000___null_keyboard.kmx", KM_CORE_VKEY_BKSP, 0, u"x", true,
|
||||
// { // KeyDown
|
||||
// // Once we use C++ 20 we can use:
|
||||
// //{KM_CORE_IT_BACK, { 0, }, {.backspace = {KM_CORE_BT_CHAR, 'x'}}},
|
||||
// {KM_CORE_IT_BACK, { 0, }, {backspace_with_context.value}},
|
||||
// {KM_CORE_IT_END}
|
||||
// },
|
||||
// { // KeyUp
|
||||
// {KM_CORE_IT_END}
|
||||
// }
|
||||
// },
|
||||
// Backspace (without context)
|
||||
{"KMN_VKey_Backspace_NoCtxt", "kmx/k_000___null_keyboard.kmx", KM_CORE_VKEY_BKSP, 0, NULL, false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Ctrl+Backspace (with context)
|
||||
{"KMN_Ctrl_VKey_Backspace_Ctxt", "kmx/k_000___null_keyboard.kmx", KM_CORE_VKEY_BKSP, KM_CORE_MODIFIER_LCTRL, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Ctrl+Backspace (no context)
|
||||
{"KMN_Ctrl_VKey_Backspace_NoCtxt", "kmx/k_000___null_keyboard.kmx", KM_CORE_VKEY_BKSP, KM_CORE_MODIFIER_LCTRL, NULL, false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Modifier frame key
|
||||
{"KMN_VKey_Shift", "kmx/k_000___null_keyboard.kmx", KM_CORE_VKEY_SHIFT, 0, u"x", false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// LDML
|
||||
// Key with rule
|
||||
{"LDML_VKey_A", "ldml/keyboards/k_020_fr.kmx", KM_CORE_VKEY_A, 0, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_CHAR, { 0, }, {'q'}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
{"LDML_Ctrl_VKey_A", "ldml/keyboards/k_020_fr.kmx", KM_CORE_VKEY_A, KM_CORE_MODIFIER_LCTRL, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Key without rule
|
||||
{"LDML_VKey_X", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_X, 0, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_END} // LDML: no output without rule
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
{"LDML_Ctrl_VKey_X", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_X, KM_CORE_MODIFIER_LCTRL, u"x", true,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END} // LDML: no output without rule
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Frame key without rule
|
||||
{"LDML_VKey_Enter", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_ENTER, 0, u"x", false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Backspace (with available context)
|
||||
{"LDML_VKey_Backspace_Ctxt", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_BKSP, 0, u"x", true,
|
||||
{ // KeyDown
|
||||
// Once we use C++ 20 we can use:
|
||||
//{KM_CORE_IT_BACK, { 0, }, {.backspace = {KM_CORE_BT_CHAR, 0}}},
|
||||
{KM_CORE_IT_BACK, { 0, }, {backspace_no_context.value}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Backspace (without context)
|
||||
{"LDML_VKey_Backspace_NoCtxt", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_BKSP, 0, NULL, false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Ctrl+Backspace (with context)
|
||||
{"LDML_Ctrl_VKey_Backspace_Ctxt", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_BKSP, KM_CORE_MODIFIER_LCTRL, u"x", false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Ctrl+Backspace (no context)
|
||||
{"LDML_Ctrl_VKey_Backspace_NoCtxt", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_BKSP, KM_CORE_MODIFIER_LCTRL, NULL, false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_INVALIDATE_CONTEXT, { 0, }, {0}},
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
// Modifier frame key
|
||||
{"LDML_VKey_Shift", "ldml/keyboards/k_000_minimal_keyboard.kmx", KM_CORE_VKEY_SHIFT, 0, u"x", false,
|
||||
{ // KeyDown
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
},
|
||||
{ // KeyUp
|
||||
{KM_CORE_IT_EMIT_KEYSTROKE, { 0, }, {0}},
|
||||
{KM_CORE_IT_END}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(KMXProcessEvent, ProcessEventTests, testing::ValuesIn(values), GenerateTestName);
|
||||
|
||||
// provide our own `main` so that we can get the path of the exe so that
|
||||
// we have a well-defined location to find our test keyboards
|
||||
int main(int argc, char** argv) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
test_dir = get_wasm_file_path(km::core::path(argv[0]).parent());
|
||||
#else
|
||||
test_dir = km::core::path(argv[0]).parent();
|
||||
#endif
|
||||
std::cout << "test_dir=" << test_dir.c_str() << std::endl;
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -48,6 +48,18 @@ kmcorekeyboardapitests = executable('km_core_keyboard_api.tests',
|
|||
|
||||
test('km-core-keyboard-api-tests', kmcorekeyboardapitests)
|
||||
|
||||
# tests for km_core_process_event
|
||||
km_core_process_event_tests_exe = executable(
|
||||
'km_core_process_event_tests',
|
||||
['km_core_process_event.tests.cpp', common_test_files],
|
||||
include_directories: [inc, libsrc],
|
||||
cpp_args: defns + warns,
|
||||
link_args: [ links, extra_link_args ],
|
||||
dependencies: [icu_uc, icu_i18n, gtest_dep, gmock_dep],
|
||||
objects: lib.extract_all_objects(recursive: false),
|
||||
)
|
||||
test('km_core_process_event', km_core_process_event_tests_exe)
|
||||
|
||||
subdir('json')
|
||||
subdir('utftest')
|
||||
subdir('kmnkbd')
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ export class LexicalModelCompiler implements KeymanCompiler {
|
|||
const sources: string[] = modelSource.sources.map(function(source) {
|
||||
return new TextDecoder().decode(callbacks.loadFile(callbacks.path.join(sourcePath, source)));
|
||||
});
|
||||
func += `globalThis.exports = globalThis.exports ?? {};\n`;
|
||||
func += this.transpileSources(sources).join('\n');
|
||||
func += `LMLayerWorker.loadModel(new ${modelSource.rootClass}());\n`;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ describe('LexicalModelCompiler', function () {
|
|||
'example.qaa.wordbreaker',
|
||||
'example.qaa.joinwordbreaker',
|
||||
'example.qaa.scriptusesspaces',
|
||||
'example.qaa.custom',
|
||||
];
|
||||
|
||||
for (const modelID of MODELS) {
|
||||
|
|
@ -37,7 +38,11 @@ describe('LexicalModelCompiler', function () {
|
|||
|
||||
assert.isFalse(compilation.hasSyntaxError, 'model code had syntax error');
|
||||
assert.isNull(compilation.error, `compilation error: ${compilation.error}`);
|
||||
assert.equal(compilation.modelConstructorName, 'TrieModel');
|
||||
if(modelID == 'example.qaa.custom') {
|
||||
assert.isNull(compilation.modelConstructorName);
|
||||
} else {
|
||||
assert.equal(compilation.modelConstructorName, 'TrieModel');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
25
developer/src/kmc-model/test/fixtures/example.qaa.custom/ExampleCustomModel.ts
vendored
Normal file
25
developer/src/kmc-model/test/fixtures/example.qaa.custom/ExampleCustomModel.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
|
||||
export class ExampleCustomModel implements LexicalModelTypes.LexicalModel {
|
||||
configure(capabilities: LexicalModelTypes.Capabilities): LexicalModelTypes.Configuration {
|
||||
return {
|
||||
leftContextCodePoints: 16,
|
||||
rightContextCodePoints: 0,
|
||||
wordbreaksAfterSuggestions: false,
|
||||
}
|
||||
}
|
||||
|
||||
languageUsesCasing: boolean = true;
|
||||
|
||||
predict(transform: LexicalModelTypes.Transform, context: LexicalModelTypes.Context): LexicalModelTypes.Distribution<LexicalModelTypes.Suggestion> {
|
||||
if(transform.deleteLeft == 0 && context.left.endsWith('te') && transform.insert == 'h') {
|
||||
return [
|
||||
{ p: 0.3, sample: { displayAs: 'the', transform: { deleteLeft: 2, insert: 'the' }, tag: 'correction' } },
|
||||
{ p: 0.2, sample: { displayAs: 'them', transform: { deleteLeft: 2, insert: 'them' }, tag: 'correction' } },
|
||||
{ p: 0.1, sample: { displayAs: 'tee hee', transform: { deleteLeft: 2, insert: 'tee hee' }, tag: 'correction' } },
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
6
developer/src/kmc-model/test/fixtures/example.qaa.custom/example.qaa.custom.model.ts
vendored
Normal file
6
developer/src/kmc-model/test/fixtures/example.qaa.custom/example.qaa.custom.model.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const source: LexicalModelSource = {
|
||||
format: 'custom-1.0',
|
||||
rootClass: 'ExampleCustomModel',
|
||||
sources: ['ExampleCustomModel.ts'],
|
||||
};
|
||||
export default source;
|
||||
|
|
@ -88,9 +88,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
|
|
@ -540,9 +540,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -769,9 +769,9 @@
|
|||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"requires": {
|
||||
"balanced-match": "^4.0.2"
|
||||
}
|
||||
|
|
@ -1099,9 +1099,9 @@
|
|||
}
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
|
||||
},
|
||||
"proc-log": {
|
||||
"version": "6.1.0",
|
||||
|
|
|
|||
|
|
@ -91,12 +91,17 @@ var
|
|||
begin
|
||||
lm := TLexicalModelParser.Create(m.Text);
|
||||
try
|
||||
// TODO: LexicalModelParser does not support `rootClass` which is needed for
|
||||
// custom lexical models, and the property `Wordlists` is inappropriate for
|
||||
// the list of sources files. This is part of a bigger project for better
|
||||
// custom model support in TIKE
|
||||
//
|
||||
// For now, this test excludes the `rootClass` property in order to pass
|
||||
lm.Comment := 'Testing';
|
||||
lm.Format := lmfCustom10;
|
||||
lm.WordBreaker := lmwbAscii;
|
||||
lm.Wordlists.Clear;
|
||||
lm.Wordlists.Add('foo.tsv');
|
||||
lm.Wordlists.Add('bar.tsv');
|
||||
lm.Wordlists.Add('CustomModel.ts');
|
||||
Assert.AreEqual(mcustom.Text.Trim, lm.Text.Trim); // Ignoring whitespace before/after
|
||||
finally
|
||||
lm.Free;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
const source: LexicalModelSource = {
|
||||
format: 'custom-1.0',
|
||||
wordBreaker: 'ascii',
|
||||
sources: ['foo.tsv', 'bar.tsv']
|
||||
sources: ['CustomModel.ts']
|
||||
};
|
||||
|
||||
export default source;
|
||||
|
|
|
|||
|
|
@ -782,7 +782,7 @@ commit_current_queue_item(IBusKeymanEngine *keyman) {
|
|||
ibus_engine_forward_key_event(engine, current_item->keyval, current_item->keycode, current_item->state);
|
||||
}
|
||||
keyman->commit_item--;
|
||||
memmove(keyman->commit_queue, &keyman->commit_queue[1], sizeof(commit_queue_item) * MAX_QUEUE_SIZE - 1);
|
||||
memmove(keyman->commit_queue, &keyman->commit_queue[1], sizeof(commit_queue_item) * (MAX_QUEUE_SIZE - 1));
|
||||
initialize_queue_items(keyman, MAX_QUEUE_SIZE - 1, 1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#import "KMInputMethodEventHandler.h"
|
||||
#import <KeymanEngine4Mac/KeymanEngine4Mac.h>
|
||||
#import <Carbon/Carbon.h> /* For kVK_ constants. */
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
#import "KeySender.h"
|
||||
#import "TextApiCompliance.h"
|
||||
#import "KMSettingsRepository.h"
|
||||
|
|
@ -370,7 +371,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil;
|
|||
contextString = attributedString.string;
|
||||
|
||||
//only uncomment for testing as we do not want to write context in logs
|
||||
//os_log_debug([KMLogs testLog], " length: %lu result: %{public}@", contextString.length, contextString);
|
||||
//os_log_debug([KMLogs keyTraceLog], " length: %lu result: %{public}@", contextString.length, contextString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -405,18 +406,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil;
|
|||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
[self insertAndReplaceTextForOutput:output client:client];
|
||||
} else if (output.isDeleteOnlyScenario) {
|
||||
if ((event.keyCode == kVK_Delete) && output.codePointsToDeleteBeforeInsert == 1) {
|
||||
// let the delete pass through in the original event rather than sending a new delete
|
||||
NSString *message = @"applyOutputToTextInputClient, delete only scenario with passthrough";
|
||||
os_log_debug([KMLogs keyTraceLog], "%@", message);
|
||||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
handledEvent = NO;
|
||||
} else {
|
||||
NSString *message = @"applyOutputToTextInputClient, delete only scenario";
|
||||
os_log_debug([KMLogs keyTraceLog], "%@", message);
|
||||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
[self sendEvents:event forOutput:output];
|
||||
}
|
||||
handledEvent = [self handleDeleteOnlyScenario:output keyDownEvent:event client:client];
|
||||
} else if (output.isDeleteAndInsertScenario) {
|
||||
// TODO: fix issue #10246
|
||||
/*
|
||||
|
|
@ -511,6 +501,220 @@ CGEventSourceRef _sourceForGeneratedEvent = nil;
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles deleting without an associated insert in one of three methods:
|
||||
* 1. delete via replace: do the delete by replacing two (or more) characters with one.
|
||||
* 2. backspace passthrough : if the original keydown event was a backspace, pass it through unhandled
|
||||
* 3. generate event: generate keydown backspace events as necessary
|
||||
*/
|
||||
-(BOOL)handleDeleteOnlyScenario:(CoreKeyOutput*)output keyDownEvent:(nonnull NSEvent *)event client:(id) client {
|
||||
|
||||
// attempt to delete by replacing -- for compliant apps only
|
||||
if ([self handleDeleteWithReplacement:output keyDownEvent:event client:client]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// pass through if this was a backspace keydown event
|
||||
if (event.keyCode == kVK_Delete && output.codePointsToDeleteBeforeInsert == 1) {
|
||||
// let the delete pass through in the original event rather than sending a new delete
|
||||
NSString *message = @"handleDeleteOnlyForOutput, delete only scenario with passthrough";
|
||||
os_log_debug([KMLogs keyTraceLog], "%@", message);
|
||||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
|
||||
// instruct system to handle the event
|
||||
return NO;
|
||||
}
|
||||
else {
|
||||
// otherwise generate a backspace
|
||||
NSString *message = @"handleDeleteOnlyForOutput, send backspace event";
|
||||
os_log_debug([KMLogs keyTraceLog], "%@", message);
|
||||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
[self sendEvents:event forOutput:output];
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For compliant apps only.
|
||||
*
|
||||
* This an attempt to make sure that deletion removes only the expected codepoints.
|
||||
* When handling a transform which only deletes a character, or when allowing a
|
||||
* backspace to pass through, the OS or application may not use the same rules
|
||||
* around deletion as Keyman -- especially when deleting clusters such as letter +
|
||||
* combining diacritic (e.g. `U+0062 U+0301`), where some applications may delete
|
||||
* both together as they represent a single 'grapheme cluster'.
|
||||
*
|
||||
* (Note, the question of whether it is appropriate for backspace to delete a
|
||||
* cluster rather than a codepoint from an end-user perspective is not relevant
|
||||
* here, because what is important is that we match the rules that the keyboard has
|
||||
* provided, which means we need a method of deleting a precise number of
|
||||
* codepoints. The keyboard author can and should include rules for cluster
|
||||
* deletion that meet end-user expectations.)
|
||||
*
|
||||
* The `insertText` API takes two parameters: a string to insert, and a range to
|
||||
* replace with that string. However, we cannot simply pass through a zero-length
|
||||
* insertion string along with the range to delete, because the `insertText` API
|
||||
* treats this as an invalid call and ignores it.
|
||||
*
|
||||
* Instead, we can delete the desired number of codepoints only by using the
|
||||
* `insertText` API to replace e.g. two codepoints with one.
|
||||
*
|
||||
* This method only works for compliant apps because non-compliant apps do not
|
||||
* support the `insertText` API.
|
||||
*
|
||||
* Ref: https://developer.apple.com/documentation/appkit/nstextinputclient/inserttext(_:replacementrange:)
|
||||
*/
|
||||
-(BOOL)handleDeleteWithReplacement:(CoreKeyOutput*)output keyDownEvent:(nonnull NSEvent *)event client:(id) client {
|
||||
BOOL handledEvent = NO;
|
||||
NSString *context = [self readContext:event forClient:client];
|
||||
|
||||
// guard: only for compliant apps with sufficient context
|
||||
if (!(self.apiCompliance.canReplaceText) || ([context length] <= output.textToDelete.length)) {
|
||||
os_log_debug([KMLogs keyTraceLog], "cannot replace text, non-compliant or insufficient context");
|
||||
return NO; // return without deleting/replacing
|
||||
}
|
||||
|
||||
// guard: the logic of this method depends on locating textToDelete in the context
|
||||
if (![self stringToDeleteMatchesContextSuffix:output.textToDelete context:context]) {
|
||||
os_log_debug([KMLogs keyTraceLog], "cannot replace text, textToDelete not found at end of context");
|
||||
return NO; // return without deleting/replacing
|
||||
}
|
||||
|
||||
NSUInteger deletionTargetLength = output.textToDelete.length;
|
||||
NSUInteger deletionTargetLocation = context.length-deletionTargetLength;
|
||||
NSUInteger precedingCharacterLocation = deletionTargetLocation - 1;
|
||||
|
||||
// if the preceding character is the trailing half of a surrogate pair
|
||||
// then delete by replacing with the entire surrogate pair
|
||||
if ([self precededBySurrogatePair:precedingCharacterLocation context:context]) {
|
||||
handledEvent = [self deleteByReplacingWithPrecedingSurrogate:precedingCharacterLocation deleteLength:deletionTargetLength context:context client:client];
|
||||
} else {
|
||||
// otherwise replace with only the preceding character
|
||||
handledEvent = [self deleteByReplacingWithPrecedingCharacter:precedingCharacterLocation deleteLength:deletionTargetLength context:context client:client];
|
||||
}
|
||||
|
||||
return handledEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the string to be deleted is found at the tail end of the current context.
|
||||
*/
|
||||
-(BOOL) stringToDeleteMatchesContextSuffix:(NSString*)textToDelete context:(NSString*) context {
|
||||
BOOL doesMatch = NO;
|
||||
|
||||
// get length of string to delete and compare to end of context
|
||||
NSUInteger deleteLength = textToDelete.length;
|
||||
NSUInteger locationOfDeletionTarget = context.length-deleteLength;
|
||||
NSUInteger locationOfPrecedingCharacter = locationOfDeletionTarget - 1;
|
||||
NSString *contextSuffix = [context substringFromIndex:context.length-deleteLength];
|
||||
|
||||
os_log_debug([KMLogs keyTraceLog], "stringToDeleteMatchesSuffix, textToDelete: '%{public}@', contextSuffix: '%{public}@', locationOfDeletionTarget: %u, locationOfprecedingCharacter: %u", textToDelete, contextSuffix, (int)locationOfDeletionTarget, (int)locationOfPrecedingCharacter);
|
||||
|
||||
doesMatch = [textToDelete isEqualToString:contextSuffix];
|
||||
os_log_debug([KMLogs keyTraceLog], "stringToDeleteMatchesSuffix: %{public}@", doesMatch?@"YES":@"NO");
|
||||
return doesMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the preceding character, which is to be used for the replacement,
|
||||
* is part of a surrogate pair that is distinct from the character being deleted.
|
||||
*/
|
||||
-(BOOL) precededBySurrogatePair: (NSUInteger)precedingLocation context:(NSString*) context {
|
||||
BOOL precedingCharacterIsLowSurrogate = false;
|
||||
unichar precedingCharacter = [context characterAtIndex:precedingLocation];
|
||||
|
||||
if (CFStringIsSurrogateHighCharacter(precedingCharacter)) {
|
||||
// preceding character is high character -- unexpected from Keyman Core
|
||||
// write to log and return false
|
||||
precedingCharacterIsLowSurrogate = false;
|
||||
NSString *message = [NSString stringWithFormat:@"Unexpected high surrogate found for preceding character at %ld", (long)precedingCharacter];
|
||||
os_log_debug([KMLogs keyTraceLog], "%{public}@", message);
|
||||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
} else if (CFStringIsSurrogateLowCharacter(precedingCharacter)) {
|
||||
// preceding character is low surrogate
|
||||
precedingCharacterIsLowSurrogate = true;
|
||||
}
|
||||
|
||||
return precedingCharacterIsLowSurrogate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace both the text to delete and the character preceding it solely with the character that precedes it.
|
||||
* Returns YES if executing the replace/delete and NO otherwise.
|
||||
*/
|
||||
-(BOOL) deleteByReplacingWithPrecedingCharacter:(NSUInteger)precedingCharacterLocation deleteLength:(NSUInteger)deleteLength context:(NSString*) context client:(id) client {
|
||||
|
||||
os_log_debug([KMLogs keyTraceLog], "deleteByReplacingWithPrecedingCharacter");
|
||||
// get the preceding character
|
||||
NSRange precedingCharacterRange = NSMakeRange(precedingCharacterLocation, 1);
|
||||
NSString *replacementString = [context substringWithRange:precedingCharacterRange];
|
||||
|
||||
// guard: if preceding character is a control character, return NO
|
||||
if ([self containsControlCharacter:replacementString]) {
|
||||
NSString *message = @"replacementString contains control characters, cannot delete with replace";
|
||||
os_log_debug([KMLogs keyTraceLog], "%@", message);
|
||||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
return NO;
|
||||
}
|
||||
|
||||
// perform the replacement
|
||||
NSUInteger replacementLength = [replacementString length] + deleteLength;
|
||||
NSRange replacementRange = NSMakeRange(precedingCharacterLocation, replacementLength);
|
||||
os_log_debug([KMLogs keyTraceLog], "replacementRange: %{public}@", NSStringFromRange(replacementRange));
|
||||
[client insertText:replacementString replacementRange:replacementRange];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace both the text to delete and the surrogate pair preceding it with the surrogate pair preceding it.
|
||||
* Returns YES if executing the replace/delete and NO otherwise.
|
||||
*/
|
||||
-(BOOL) deleteByReplacingWithPrecedingSurrogate:(NSUInteger)precedingCharacterLocation deleteLength:(NSUInteger)deleteLength context:(NSString*) context client:(id) client {
|
||||
|
||||
os_log_debug([KMLogs keyTraceLog], "deleteByReplacingWithPrecedingSurrogate");
|
||||
|
||||
// guard: return NO if there is no character before the precedingCharacterLocation
|
||||
if (precedingCharacterLocation <= 0) {
|
||||
NSString *message = @"no characters exist before precedingCharacterLocation, so it cannot be a surrogate pair";
|
||||
os_log_debug([KMLogs keyTraceLog], "%@", message);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// get the preceding character
|
||||
NSRange precedingCharacterRange = NSMakeRange(precedingCharacterLocation - 1, 2);
|
||||
NSString *replacementString = [context substringWithRange:precedingCharacterRange];
|
||||
|
||||
unichar highCharacter = [replacementString characterAtIndex:0];
|
||||
unichar lowCharacter = [replacementString characterAtIndex:1];
|
||||
|
||||
// verify that preceding characters comprise a surrogate pair
|
||||
if ((CFStringIsSurrogateHighCharacter(highCharacter)) && (CFStringIsSurrogateLowCharacter(lowCharacter))) {
|
||||
// found preceding surrogate as expected
|
||||
} else {
|
||||
NSString *message = [NSString stringWithFormat:@"Preceding characters of string do not comprise a surrogate pair: 0x%02x, 0x%02x", (unsigned int)highCharacter, (unsigned int)lowCharacter];
|
||||
os_log_debug([KMLogs keyTraceLog], "%@", message);
|
||||
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
|
||||
return NO;
|
||||
}
|
||||
|
||||
// perform the replacement
|
||||
NSUInteger replacementLength = [replacementString length] + deleteLength;
|
||||
NSRange replacementRange = NSMakeRange(precedingCharacterRange.location, replacementLength);
|
||||
os_log_debug([KMLogs keyTraceLog], "replacementRange: %{public}@", NSStringFromRange(replacementRange));
|
||||
[client insertText:replacementString replacementRange:replacementRange];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(BOOL) containsControlCharacter:(NSString*)text {
|
||||
NSCharacterSet *controlSet = [NSCharacterSet controlCharacterSet];
|
||||
NSRange range = [text rangeOfCharacterFromSet:controlSet];
|
||||
|
||||
return (range.location != NSNotFound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the range where text will be inserted and replace existing text.
|
||||
* Returning {NSNotFound, NSNotFound} for range signifies to insert at current location without replacement.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
-(void) checkComplianceAfterInsert:(NSString *)insertedText deleted:(NSString *)deletedText;
|
||||
-(BOOL)isComplianceUncertain;
|
||||
-(BOOL)canReadText;
|
||||
-(BOOL)canReplaceText;
|
||||
-(BOOL)mustBackspaceUsingEvents;
|
||||
@end
|
||||
|
||||
|
|
|
|||
91
package-lock.json
generated
91
package-lock.json
generated
|
|
@ -4823,15 +4823,6 @@
|
|||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
|
|
@ -5955,15 +5946,6 @@
|
|||
"version": "1.0.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
|
@ -7963,7 +7945,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.2.5",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
|
@ -9882,15 +9866,6 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/make-fetch-happen": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz",
|
||||
|
|
@ -11288,10 +11263,11 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
|
|
@ -12895,47 +12871,6 @@
|
|||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz",
|
||||
"integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"source-map-support": "^0.5.17",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node/node_modules/diff": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsc-watch": {
|
||||
"version": "4.6.2",
|
||||
"dev": true,
|
||||
|
|
@ -13772,18 +13707,6 @@
|
|||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"dev": true,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,6 @@ jobs:
|
|||
steps:
|
||||
- name: Check PR build status
|
||||
id: run_pr_build_status_script
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
|
|
|
|||
|
|
@ -65,8 +65,26 @@ _tc_rsync() {
|
|||
else
|
||||
local RSYNC=rsync
|
||||
if builder_is_macos; then
|
||||
# We need to look for a newer version of rsync because the version bundled
|
||||
# with macos does not work correctly; we assume that this is installed
|
||||
# with Homebrew. On Intel, this is found in /usr/local/bin, and on M1, it
|
||||
# is found in /opt/homebrew/bin.
|
||||
|
||||
RSYNC=/usr/local/bin/rsync
|
||||
[[ -f /opt/homebrew/bin/rsync ]] && RSYNC=/opt/homebrew/bin/rsync
|
||||
[[ -x /opt/homebrew/bin/rsync ]] && RSYNC=/opt/homebrew/bin/rsync
|
||||
|
||||
# On build agents, we unlink rsync from homebrew, because otherwise xcode
|
||||
# builds which use rsync internally fail, so we need to specify the full
|
||||
# path to rsync. Generally, rsync is not needed on developer machines as
|
||||
# this is only used for release builds from the build agents. #15764
|
||||
if [[ ! -x "${RSYNC}" ]]; then
|
||||
# Try and find the rsync binary with `brew list`. It should be something
|
||||
# like `/opt/homebrew/Cellar/rsync/3.4.1/bin/rsync`
|
||||
RSYNC="$(brew list rsync | grep /bin/rsync\$)"
|
||||
if [[ ! -x "${RSYNC}" ]]; then
|
||||
builder_die "Could not find Homebrew-installed rsync anywhere"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
${RSYNC} "${rsync_args[@]}"
|
||||
|
|
|
|||
|
|
@ -12,9 +12,13 @@ ba_mac_unlock_keychain() {
|
|||
}
|
||||
|
||||
ba_mac_clean_xcode_derived_data() {
|
||||
builder_echo start "clean" "Cleaning XCode DerivedData mess"
|
||||
builder_echo start "clean" "Cleaning XCode DerivedData and Carthage cache mess"
|
||||
rm -rf "${HOME}/Library/Developer/Xcode/DerivedData"
|
||||
builder_echo end "clean" success "Finished cleaning XCode DerivedData mess"
|
||||
|
||||
# https://stackoverflow.com/a/45504898/1836776
|
||||
rm -rf "${HOME}/Library/Caches/org.carthage.CarthageKit"
|
||||
|
||||
builder_echo end "clean" success "Finished cleaning XCode DerivedData and Carthage cache mess"
|
||||
}
|
||||
|
||||
ba_mac_unmount_volumes_keyman() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue