chore(web): Merge branch 'beta' into fix/web/sticky-preview

This commit is contained in:
Joshua A. Horton 2024-03-08 07:58:16 +07:00
commit bc338231ff
421 changed files with 14417 additions and 2882 deletions

View file

@ -2,7 +2,11 @@
⭐ Thank you for your contribution! ⭐
The following is a set of guidelines for contributing to Keyman, Keyman keyboards, Keyman lexical models and Keyman websites, which are hosted in the [Keymanapp Organization](https://github.com/keymanapp) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
The following is a set of guidelines for contributing to Keyman, Keyman
keyboards, Keyman lexical models and Keyman websites, which are hosted
in the [Keymanapp Organization](https://github.com/keymanapp) on GitHub.
These are mostly guidelines, not rules. Use your best judgment, and feel
free to propose changes to this document in a pull request.
(These guidelines have been adapted from [Atom Guidelines](https://github.com/atom/atom/blob/master/CONTRIBUTING.md).)
@ -35,11 +39,17 @@ The following is a set of guidelines for contributing to Keyman, Keyman keyboard
## Code of Conduct
This project and everyone participating in it is governed by the [Keyman Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [community@keyman.com](mailto:community@keyman.com).
This project and everyone participating in it is governed by the [Keyman
Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected
to uphold this code. Please report unacceptable behavior to
[community@keyman.com](mailto:community@keyman.com).
## I don't want to read this whole thing I just have a question!!!
**Note:** If you have a question about using Keyman, you'll get faster results by asking in the Keyman Community, where many other users may also be able to assist you. If you have a question about the Keyman source code, feel free to open an issue!
**Note:** If you have a question about using Keyman, you'll get faster
results by asking in the Keyman Community, where many other users may
also be able to assist you. If you have a question about the Keyman
source code, feel free to open an issue!
* [Keyman Community](https://community.software.sil.org/c/keyman)
@ -47,12 +57,23 @@ This project and everyone participating in it is governed by the [Keyman Code of
### Keyman, Keyboard Layouts and Lexical Models
Keyman is a complex project that spans six different platforms, with many a variety of components on each platform. We have chosen to host the Keyman project in the [keymanapp/keyman](https://github.com/keymanapp/keyman) monorepo, but [keyboard layouts](https://github.com/keymanapp/keyboards) and [lexical models](https://github.com/keymanapp/lexical-models) are stored in their own repositories.
Keyman is a complex project that spans six different platforms, with
many a variety of components on each platform. We have chosen to host
the Keyman project in the
[keymanapp/keyman](https://github.com/keymanapp/keyman) monorepo, but
[keyboard layouts](https://github.com/keymanapp/keyboards) and [lexical
models](https://github.com/keymanapp/lexical-models) are stored in their
own repositories.
* Issues relating to the Keyman apps and their interactions with operating systems or other apps should be opened in the Keyman repo
* For keyboard-specific or model-specific issues, open issues in the corresponding repository.
* The Keyman websites are also hosted in their own repositories. Contributions, including issues and pull requests can be opened against these repositories also.
* Product documentation for the Keyman app is hosted in the Keyman app repository, and replicated to the help.keyman.com repository.
* Issues relating to the Keyman apps and their interactions with
operating systems or other apps should be opened in the Keyman repo
* For keyboard-specific or model-specific issues, open issues in the
corresponding repository.
* The Keyman websites are also hosted in their own repositories.
Contributions, including issues and pull requests can be opened
against these repositories also.
* Product documentation for the Keyman app is hosted in the Keyman app
repository, and replicated to the help.keyman.com repository.
The primary repositories you are most likely to interact with are:
@ -68,147 +89,291 @@ The primary repositories you are most likely to interact with are:
### Reporting Bugs
This section guides you through submitting a bug report for Keyman. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
This section guides you through submitting a bug report for Keyman.
Following these guidelines helps maintainers and the community
understand your report :pencil:, reproduce the behavior :computer:
:computer:, and find related reports :mag_right:.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/keymanapp/keyman/issues/new?assignees=&labels=bug&template=bug_report.md&title=bug%3A+); the information it asks for helps us resolve issues faster.
Before creating bug reports, please check [this
list](#before-submitting-a-bug-report) as you might find out that you
don't need to create one. When you are creating a bug report, please
[include as many details as
possible](#how-do-i-submit-a-good-bug-report). Fill out [the required
template](https://github.com/keymanapp/keyman/issues/new?assignees=&labels=bug&template=bug_report.md&title=bug%3A+);
the information it asks for helps us resolve issues faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
> **Note:** If you find a **Closed** issue that seems like it is the
> same thing that you're experiencing, open a new issue and include a
> link to the original issue in the body of your new one.
#### Before Submitting A Bug Report
* **Check that you are running the [latest version](https://keyman.com/downloads).**
* **Check the [community](https://community.software.sil.org/c/keyman)** for common questions and problems; search there to see if other people have reported the issue and possible solutions.
* **Determine [which repository the problem should be reported in](#keyman-keyboard-layouts-and-lexical-models)**.
* **Perform a [cursory search](https://github.com/search?q=+is%3Aissue+user%3Akeymanapp+label%3Abug)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
* **Check that you are running the [latest
version](https://keyman.com/downloads).**
* **Check the [community](https://community.software.sil.org/c/keyman)**
for common questions and problems; search there to see if other people
have reported the issue and possible solutions.
* **Determine [which repository the problem should be reported
in](#keyman-keyboard-layouts-and-lexical-models)**.
* **Perform a [cursory
search](https://github.com/search?q=+is%3Aissue+user%3Akeymanapp+label%3Abug)**
to see if the problem has already been reported. If it has **and the
issue is still open**, add a comment to the existing issue instead of
opening a new one.
#### How Do I Submit A (Good) Bug Report?
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#keyman-keyboard-layouts-and-lexical-models) your bug is related to, create an issue on that repository and provide the following information by filling in the template (not all repositories currently have a bug reporting template; in those cases, you can start with the [Keyman bug report template](https://github.com/keymanapp/keyman/blob/master/.github/ISSUE_TEMPLATE/bug_report.md?plain=1) if you wish).
Bugs are tracked as [GitHub
issues](https://guides.github.com/features/issues/). After you've
determined [which
repository](#keyman-keyboard-layouts-and-lexical-models) your bug is
related to, create an issue on that repository and provide the following
information by filling in the template (not all repositories currently
have a bug reporting template; in those cases, you can start with the
[Keyman bug report
template](https://github.com/keymanapp/keyman/blob/master/.github/ISSUE_TEMPLATE/bug_report.md?plain=1)
if you wish).
Explain the problem and include additional details to help maintainers reproduce the problem:
Explain the problem and include additional details to help maintainers
reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**. For example, if you typed on Android using a bluetooth keyboard, instead of with the touch keyboard, note that.
* **Provide specific examples to demonstrate the steps** If you are documenting a problem with unexpected output from a keyboard, make sure you include the exact keystroke sequence you typed, the output you received, and the expected output.
* **Use a clear and descriptive title** for the issue to identify the
problem.
* **Describe the exact steps which reproduce the problem** in as many
details as possible. When listing steps, **don't just say what you
did, but explain how you did it**. For example, if you typed on
Android using a bluetooth keyboard, instead of with the touch
keyboard, note that.
* **Provide specific examples to demonstrate the steps** If you are
documenting a problem with unexpected output from a keyboard, make
sure you include the exact keystroke sequence you typed, the output
you received, and the expected output.
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **Include screenshots and animated GIFs** which show you following the
described steps and clearly demonstrate the problem. You can use [this
tool](https://www.cockos.com/licecap/) to record GIFs on macOS and
Windows, and [this tool](https://github.com/colinkeenan/silentcast) or
[this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you're reporting that Keyman crashed**, include the reference number from the crash report, if available.
* **If you're reporting that Keyman crashed**, include the reference
number from the crash report, if available.
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g. after updating to a new version of Keyman, keyboard, app or operating system) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of Keyman?** What's the most recent version in which the problem doesn't happen? You can download older versions of Keyman from [the Downloads page](https://keyman.com/downloads).
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
* **Did the problem start happening recently** (e.g. after updating to a
new version of Keyman, keyboard, app or operating system) or was this
always a problem?
* If the problem started happening recently, **can you reproduce the
problem in an older version of Keyman?** What's the most recent
version in which the problem doesn't happen? You can download older
versions of Keyman from [the Downloads
page](https://keyman.com/downloads).
* **Can you reliably reproduce the issue?** If not, provide details
about how often the problem happens and under which conditions it
normally happens.
Include details about your configuration and environment:
* **Which version of Keyman are you using?** The version information is typically available in the About or Info screen, depending on your device type.
* **What's the name and version of the OS you're using**? Note exactly which type of device you are using, the version of the operating system.
* **Are you running Keyman in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest?
* **Which keyboards and models do you have installed?** You can get that list by running `apm list --installed`.
* **What app are you interacting with?** If the problem is in interaction with a specific app, note the app you are trying to work with, and its version.
* **Which keyboard layout experiences the problem?** Capture the keyboard ID and the version of the keyboard layout. On Windows, tell us also the base keyboard layout, found in Keyman Configuration, Options tab.
* **Diagnostic reports** On Windows, you can capture a diagnostic report by clicking Diagnostics in the Support tab of Keyman Configuration, save it and attach it to the issue.
* **Which version of Keyman are you using?** The version information is
typically available in the About or Info screen, depending on your
device type.
* **What's the name and version of the OS you're using**? Note exactly
which type of device you are using, the version of the operating
system.
* **Are you running Keyman in a virtual machine?** If so, which VM
software are you using and which operating systems and versions are
used for the host and the guest?
* **Which keyboards and models do you have installed?** You can get that
list by running `apm list --installed`.
* **What app are you interacting with?** If the problem is in
interaction with a specific app, note the app you are trying to work
with, and its version.
* **Which keyboard layout experiences the problem?** Capture the
keyboard ID and the version of the keyboard layout. On Windows, tell
us also the base keyboard layout, found in Keyman Configuration,
Options tab.
* **Diagnostic reports** On Windows, you can capture a diagnostic report
by clicking Diagnostics in the Support tab of Keyman Configuration,
save it and attach it to the issue.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Keyman, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
This section guides you through submitting an enhancement suggestion for
Keyman, including completely new features and minor improvements to
existing functionality. Following these guidelines helps maintainers and
the community understand your suggestion :pencil: and find related
suggestions :mag_right:.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion).
Fill in [the template](https://github.com/keymanapp/keyman/issues/new?assignees=&labels=feat&template=feature_request.md&title=feat%3A+), including the steps that you imagine you would take if the feature you're requesting existed.
Before creating enhancement suggestions, please check [this
list](#before-submitting-an-enhancement-suggestion) as you might find
out that you don't need to create one. When you are creating an
enhancement suggestion, please [include as many details as
possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the
template](https://github.com/keymanapp/keyman/issues/new?assignees=&labels=feat&template=feature_request.md&title=feat%3A+),
including the steps that you imagine you would take if the feature
you're requesting existed.
#### Before Submitting An Enhancement Suggestion
* **Determine [which repository the enhancement should be suggested in](#keyman-keyboard-layouts-and-lexical-models).**
* **Perform a [cursory search](https://github.com/search?q=+is%3Aissue+user%3Akeymanapp+label%3Afeat)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
* **Determine [which repository the enhancement should be suggested
in](#keyman-keyboard-layouts-and-lexical-models).**
* **Perform a [cursory
search](https://github.com/search?q=+is%3Aissue+user%3Akeymanapp+label%3Afeat)**
to see if the enhancement has already been suggested. If it has, add a
comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#keyman-keyboard-layouts-and-lexical-models) your enhancement suggestion is related to, create an issue on that repository and provide the following information:
Enhancement suggestions are tracked as [GitHub
issues](https://guides.github.com/features/issues/). After you've
determined [which
repository](#keyman-keyboard-layouts-and-lexical-models) your
enhancement suggestion is related to, create an issue on that repository
and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Keyman which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **Use a clear and descriptive title** for the issue to identify the
suggestion.
* **Provide a step-by-step description of the suggested enhancement** in
as many details as possible.
* **Provide specific examples to demonstrate the steps**. Include
copy/pasteable snippets which you use in those examples, as [Markdown
code
blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the current behavior** and **explain which behavior you
expected to see instead** and why.
* **Include screenshots and animated GIFs** which help you demonstrate
the steps or point out the part of Keyman which the suggestion is
related to. You can use [this tool](https://www.cockos.com/licecap/)
to record GIFs on macOS and Windows, and [this
tool](https://github.com/colinkeenan/silentcast) or [this
tool](https://github.com/GNOME/byzanz) on Linux.
* **Explain why this enhancement would be useful** to most Keyman users.
* **List some other keyboard tools or operating systems where this enhancement exists.**
* **Specify which version of Keyman you're using.** The version information is typically available in the About or Info screen, depending on your device type.
* **List some other keyboard tools or operating systems where this
enhancement exists.**
* **Specify which version of Keyman you're using.** The version
information is typically available in the About or Info screen,
depending on your device type.
* **Specify the name and version of the OS you're using.**
### Your First Code Contribution
Unsure where to begin contributing to Keyman? You can start by looking through these `good-first-issue` and `help-wanted` issues:
Unsure where to begin contributing to Keyman? You can start by looking
through these `good-first-issue` and `help-wanted` issues:
* [Good first issues](https://github.com/keymanapp/keyman/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) - issues which should only require a few lines of code, and a test or two.
* [Help wanted issues](https://github.com/keymanapp/keyman/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) - issues which should be a bit more involved than `Good First Issues` issues.
* [Good first
issues](https://github.com/keymanapp/keyman/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
- issues which should only require a few lines of code, and a test or
two.
* [Help wanted
issues](https://github.com/keymanapp/keyman/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
- issues which should be a bit more involved than `Good First Issues`
issues.
### Building Keyman
Keyman can be built locally. For instructions on how to do this, see the [build documentation](./docs/build/index.md).
Keyman can be built locally. For instructions on how to do this, see the
[build documentation](./docs/build/index.md).
### Localize Keyman
We use CrowdIn to develop translations for the Keyman user interface.
* [Localize Keyman](https://translate.keyman.com/project/keyman) [![Crowdin](https://badges.crowdin.net/keyman/localized.svg)](https://translate.keyman.com/project/keyman)
* [Localize Keyman](https://translate.keyman.com/project/keyman)
[![Crowdin](https://badges.crowdin.net/keyman/localized.svg)](https://translate.keyman.com/project/keyman)
### Other Ways of Getting Involved
Even if you are not a coder, there are many other ways you can help with the Keyman project:
Even if you are not a coder, there are many other ways you can help with
the Keyman project:
* [Other Ways of Getting Involved](https://keyman.com/about/get-involved)
* [Other Ways of Getting
Involved](https://keyman.com/about/get-involved)
## Style Guides
### Principles of Keyman Code Changes
These are some general principles that we need to be following when we create features or bug fixes in Keyman. These principles help to uphold a good user experience for Keyman end users and keyboard/model developers.
These are some general principles that we need to be following when we
create features or bug fixes in Keyman. These principles help to uphold
a good user experience for Keyman end users and keyboard/model
developers.
* [Principles of Keyman Code Changes Wiki](https://github.com/keymanapp/keyman/wiki/Principles-of-Keyman-Code-Changes)
* [Principles of Keyman Code Changes
Wiki](https://github.com/keymanapp/keyman/wiki/Principles-of-Keyman-Code-Changes)
### Pull Request and Commit Workflow
We have a number of git commit and pull request formatting preferences. We can review your pull request faster if you follow these guidelines:
We have a number of git commit and pull request formatting preferences.
We can review your pull request faster if you follow these guidelines:
* [Pull Request and Commit workflow wiki](https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes)
* [Pull Request and Commit workflow
wiki](https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes)
### Code Style Guide
The Keyman style guide will help you to prepare code in a way that matches the Keyman source. Note that older source may not yet meet this guide, so if you are fixing up an old piece of code, you may need to use your judgment to determine the code styling to use.
The Keyman style guide will help you to prepare code in a way that
matches the Keyman source. Note that older source may not yet meet this
guide, so if you are fixing up an old piece of code, you may need to use
your judgment to determine the code styling to use.
* [Code Style Guide wiki](https://github.com/keymanapp/keyman/wiki/Keyman-Code-Style-Guide)
* [Code Style Guide
wiki](https://github.com/keymanapp/keyman/wiki/Keyman-Code-Style-Guide)
### User Testing
Any pull request that impacts end user experience should include a user test. This is a set of instructions that an experienced user of Keyman could follow to validate that your pull request does what it should do. A good tester may depart from the test steps you provide to check the things you forgot -- but you should try and provide comprehensive test steps.
Any pull request that impacts end user experience should include a user
test. This is a set of instructions that an experienced user of Keyman
could follow to validate that your pull request does what it should do.
A good tester may depart from the test steps you provide to check the
things you forgot -- but you should try and provide comprehensive test
steps.
The Keyman app repository has a bot which coordinates user testing for each pull request:
The Keyman app repository has a bot which coordinates user testing for
each pull request:
* [User Testing wiki](https://github.com/keymanapp/keyman/wiki/User-Testing-Workflows)
* [User Testing
wiki](https://github.com/keymanapp/keyman/wiki/User-Testing-Workflows)
### Documentation Style Guide
This applies to user documentation found both on [help.keyman.com](https://github.com/keymanapp/help.keyman.com) and the application user documentation found in the [Keyman repository](https://github.com/keymanapp/keyman). Remember that application documentation is replicated to help.keyman.com but should be edited in the Keyman repository.
This applies to user documentation found both on
[help.keyman.com](https://github.com/keymanapp/help.keyman.com) and the
application user documentation found in the [Keyman
repository](https://github.com/keymanapp/keyman). Remember that
application documentation is replicated to help.keyman.com but should be
edited in the Keyman repository.
* All new documentation pages should be written in [Markdown](https://daringfireball.net/projects/markdown).
* Older documentation may be written in HTML or a strange hybrid of PHP scripts and HTML. If you make more than a minor change to an older doc, rewrite it to Markdown:
* All new documentation pages should be written in
[Markdown](https://daringfireball.net/projects/markdown).
* Older documentation may be written in HTML or a strange hybrid of PHP
scripts and HTML. If you make more than a minor change to an older
doc, rewrite it to Markdown:
1. View Source on the page from your browser
2. Find the documentation content (often within the `<article>` tag)
3. Use a tool such as [Browserling HTML to Markdown](https://www.browserling.com/tools/html-to-markdown) to convert the document to Markdown
4. Paste this into a new file with the same name but the .md extension, and remove the old .html or .php file.
3. Use a tool such as [Browserling HTML to
Markdown](https://www.browserling.com/tools/html-to-markdown) to
convert the document to Markdown
4. Paste this into a new file with the same name but the .md
extension, and remove the old .html or .php file.
The style guide for code documentation and comments can be found in the [Code Style Guide wiki](https://github.com/keymanapp/keyman/wiki/Keyman-Code-Style-Guide).
The style guide for code documentation and comments can be found in the
[Code Style Guide
wiki](https://github.com/keymanapp/keyman/wiki/Keyman-Code-Style-Guide).
## Additional Notes
### Issue and Pull Request Labels
This section lists the labels we use to help us track and manage issues and pull requests. These labels are used on the [Keyman app repo and the website repos](#keyman-keyboard-layouts-and-lexical-models).
This section lists the labels we use to help us track and manage issues
and pull requests. These labels are used on the [Keyman app repo and the
website repos](#keyman-keyboard-layouts-and-lexical-models).
[GitHub search](https://help.github.com/articles/searching-issues/) makes it easy to use labels for finding groups of issues or pull requests you're interested in.
The labels are loosely grouped by their purpose, but it's not required that every issue has a label from every group or that an issue can't have more than one label from the same group.
[GitHub search](https://help.github.com/articles/searching-issues/)
makes it easy to use labels for finding groups of issues or pull
requests you're interested in. The labels are loosely grouped by their
purpose, but it's not required that every issue has a label from every
group or that an issue can't have more than one label from the same
group.
#### Type of Issue

View file

@ -1,5 +1,143 @@
# Keyman Version History
## 17.0.284 beta 2024-03-07
* fix(web): globe key highlighting (#10932)
* chore(web): updates mtnt model used by test page (#10935)
* fix(developer): kmc keyboard info compiler no kmp error (#10893)
* fix(developer): Treat Right Shift as Shift in debugger (#10919)
* docs(developer): kmc-model api documentation (#10921)
* docs(developer): kmc-model-info api documentation (#10922)
* docs(developer): kmc-keyboard-info api documentation (#10923)
* docs(developer): kmc-package api documentation (#10924)
* fix(developer): normalize all input paths in .kps compiler (#10950)
## 17.0.283 beta 2024-03-06
* fix(android): fixes context-change detection for repeated-char cases (#10873)
* fix(developer): search-term quote replacement was not global (#10934)
* (#10913)
* fix(developer): fix for errant \uXXXX error (#10946)
* (#10948)
* refactor(oem/fv/android): Install fallback keyboard (#10907)
* refactor(android/app): Move storage permission checks (#10904)
* fix(common): missing script exec bit (#10951)
## 17.0.282 beta 2024-03-05
* fix(developer): move osk.ts from developer-utils to kmc-kmn (#10903)
* fix(developer): hint text special characters (#10927)
* docs(developer): kmc-ldml api documentation (#10917)
* chore(developer): consolidate external links in Developer messages (#10918)
* fix(developer): message export filename was title case (#10941)
## 17.0.281 beta 2024-03-04
* chore(linux): Update debian changelog (#10897)
* fix(ios): cross-paragraph keyboard rules (#10905)
* chore(developer): deploy compiler messages to help site (#10906)
## 17.0.280 beta 2024-03-01
* fix(developer): make sure scroll bar appears when needed in global welcome (#10818)
* chore(common): loosen .keyman-touch-layout schema layer id requirements (#10819)
* docs(developer): kmc-kmn api documentation and some message documentation (#10856)
* refactor(developer): cleanup kmn compiler message namespaces (#10867)
* refactor(developer): reorganize messages for adding details (#10878)
* fix(ios): context-initial diacritic handling (#10589)
* feat(developer): adds kmc message command (#10888)
* test(developer): kmc unit test infrastructure messages (#10825)
* fix(android): handle surrogate pairs in selection range indexing (#10885)
* feat(windows): move the code kmshell.dpr to unit so that only the includes are in dpr (#10871)
## 17.0.279 beta 2024-02-29
* fix(linux): Fix libkeymancore-dev dependencies (#10880)
* chore(resources): update to latest ldml: xmlns, uset (#10865)
## 17.0.278 beta 2024-02-28
* docs(linux): Updated whatsnew for Keyman for Linux 17 (#10858)
* (#10869)
* chore(android/engine): Reduce toast notifications after installations (#10868)
* fix(android/engine): Check selection indexes (#10857)
## 17.0.277 beta 2024-02-27
* fix(developer): exit kmc test data if any failure messages occurred (#10805)
* docs(windows): add whats new Keyman for Windows 17.0 (#10783)
* feat(developer): add api-extractor and api-documenter, config, and build integration (#10839)
* feat(developer): publish api documentation to help.keyman.com (#10841)
* docs(developer): npm package readme files (#10842)
* refactor(developer): move osk module from common-types to developer-utils (#10845)
* chore(linux): Update debian changelog (#10828)
* fix(developer): repeated options in kmc must now be fully specified (#10821)
* docs(developer): add documentation for kmc-analyze (#10854)
## 17.0.276 beta 2024-02-26
* chore(linux): Fix autopkg tests (#10824)
## 17.0.275 beta 2024-02-23
* (#10791)
* fix(developer): publish keymancore-1.dll symbols (#10797)
* fix(developer): improve uploading of sourcemaps to sentry (#10798)
* fix(android/engine): Fix how keyboard picker menu exits (#10806)
* chore(linux): Fix `upload-to-debian.sh` script for beta releases (#10784)
* chore(linux): Fix API verification after adding SONAME (#10813)
* chore(linux): Address comments from Debian mailing list (#10800)
* docs(linux): Update how to build binary package with docker (#10814)
* chore(linux): Update debian changelog (#10785)
## 17.0.274 beta 2024-02-22
* fix(android/engine): Remove logs for uninitialized default keyboard (#10782)
* refactor(android/app): Allow SystemKeyboard to install default keyboard and dictionary (#10794)
* chore(android/app): Remove logging for install referrer details (#10795)
* docs(android/app): Update tablet screenshots (again) (#10796)
* docs(android): Update screenshots for help (#10808)
* chore(web): proper reporting of errors from es5 KMW (#10807)
* fix(android): first keystroke when context is empty (#10809)
* chore(linux): Update Debian `control` file (#10787)
## 17.0.273 beta 2024-02-21
* fix(developer): match kmc console formatting for messages in IDE (#10775)
* fix(developer): ensure fatal errors report message or made non-fatal (#10777)
* chore(developer): add LDML specific text to Project view (#10779)
* fix(developer): reset debugger after ldml keyboard compile (#10780)
* fix(developer): crash opening invalid project (#10781)
* docs(ios): document gestures (#10763)
## 17.0.272 beta 2024-02-20
* fix(core): fix output append logic in any_group::apply_transform (#10758)
* fix(android): Match phone suggestion banner styling on tablet (#10760)
* docs(mac): Document What's New for version 17.0 (#10764)
* (#10773)
* chore(developer): reduce WARN_TouchLayoutUsesUnsupportedGesturesDownlevel to HINT (#10766)
* fix(developer): prevent renaming of new project types in New Project dialog (#10767)
* fix(developer): return an error code if build-test-data fails (#10765)
* fix(developer): allow more parameters in kmc.cmd (#10759)
* chore(linux): Ignore missing .symbols file in stable branch (#10674)
* chore(linux): Update symbols file with current version (#10575)
* docs(linux): Update screenshots and FAQ (#10768)
* chore(linux): Fix `debian.sh` by removing quotes (#10772)
## 17.0.271 beta 2024-02-19
* chore(developer): add infrastructure messages tests (#10756)
* chore(windows): Update more PT strings (#10718)
* docs(android/app): Update tablet screenshots (#10726)
* fix(web): sticky special-key highlighting (#10729)
* fix(web): disables modipress for layer-switch keys with subkeys (#10745)
* fix(web): missing null-guard for hardware keystrokes without active Keyman keyboard (#10740)
* docs(ios): what's new in 17.0 (#10748)
* fix(web): handling of page-elements focused before engine initialization (#10744)
* fix(ios): multitap consistency after new-lines (#10728)
* docs(common): Update website README (#10738)
## 17.0.270 beta 2024-02-17
* (#10735)

View file

@ -1 +1 @@
17.0.271
17.0.285

View file

@ -5,9 +5,13 @@
<!-- Keyman Engine removes INTERNET permission, so add it back -->
<uses-permission android:name="android.permission.INTERNET" tools:node="replace" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Devices running up to Android 12L
Starting in API level 33, this permission has no effect:
https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Ideally we would declare this permission so we can get other installed keyboard names.
But would involve a long review process with the Play Store:
https://support.google.com/googleplay/android-developer/answer/9214102#

View file

@ -91,7 +91,7 @@ public class CheckInstallReferrer {
}
if (!installerInfo.equalsIgnoreCase(GOOGLE_PLAY)) {
KMLog.LogInfo(TAG, "Skipping install referrer. Installed from " + installerInfo);
// Don't log install referrer #10762
return;
}

View file

@ -5,8 +5,8 @@
package com.keyman.android;
import com.tavultesoft.kmapro.BuildConfig;
import com.tavultesoft.kmapro.DefaultLanguageResource;
import com.tavultesoft.kmapro.KeymanSettingsActivity;
import com.keyman.android.BannerController;
import com.keyman.engine.KMManager;
import com.keyman.engine.KMManager.KeyboardType;
import com.keyman.engine.KMHardwareKeyboardInterpreter;
@ -64,11 +64,13 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
KMManager.setDebugMode(true);
}
KMManager.addKeyboardEventListener(this);
KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_SYSTEM);
interpreter = new KMHardwareKeyboardInterpreter(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_SYSTEM);
Context context = getApplicationContext();
KMManager.initialize(context, KeyboardType.KEYBOARD_TYPE_SYSTEM);
DefaultLanguageResource.install(context);
interpreter = new KMHardwareKeyboardInterpreter(context, KeyboardType.KEYBOARD_TYPE_SYSTEM);
KMManager.setInputMethodService(this); // for HW interface
SharedPreferences prefs = getApplicationContext().getSharedPreferences(getApplicationContext().getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString()));
KMManager.setSpacebarText(spacebarText);

View file

@ -0,0 +1,94 @@
/**
* Copyright 2024 SIL International
*/
package com.tavultesoft.kmapro;
import static com.tavultesoft.kmapro.MainActivity.PERMISSION_REQUEST_STORAGE;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class CheckPermissions {
public static boolean isPermissionOK(Activity activity) {
boolean permissionsOK = true;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return permissionsOK;
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
permissionsOK = Environment.isExternalStorageManager();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {
// TODO: Workout scoped storage permission #10659
}
} else {
permissionsOK = checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
return permissionsOK;
}
public static boolean checkPermission(Activity activity, String permission) {
return ContextCompat.checkSelfPermission(activity.getApplicationContext(), permission) == PackageManager.PERMISSION_GRANTED;
}
public static void requestPermission (Activity activity, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Need to request multiple permissions at once
// TODO: request multiple permissions at once #10659
showPermissionDenied(context);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// TODO: find alternative to below. #10659
// It works, but would need Play Store approval
// For now, show permission denied
showPermissionDenied(context);
/*
try {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivity(intent);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
activity.startActivity(intent);
}
*/
} else if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.READ_EXTERNAL_STORAGE)) {
// Provide additional rationale to the user if the permission was not granted
String message = activity.getApplicationContext().getString(R.string.request_storage_permission);
Toast.makeText(activity.getApplicationContext(), message ,
Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(activity,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
} else {
// Request the permission. The result will be received in onRequestPermissionsResult().
ActivityCompat.requestPermissions(activity,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
}
}
public static void showPermissionDenied(Context context) {
// Permission request denied
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
dialogBuilder.setTitle(String.format(context.getString(R.string.title_add_keyboard)));
dialogBuilder.setMessage(String.format(context.getString(R.string.storage_permission_denied2)));
dialogBuilder.setPositiveButton(context.getString(R.string.label_ok), null);
AlertDialog dialog = dialogBuilder.create();
dialog.show();
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2024 SIL International. All rights reserved.
*/
package com.tavultesoft.kmapro;
import android.content.Context;
import android.content.SharedPreferences;
import com.keyman.engine.KMManager;
import com.keyman.engine.data.LexicalModel;
import java.util.HashMap;
public class DefaultLanguageResource {
private static final String defaultKeyboardInstalled = "DefaultKeyboardInstalled";
private static final String defaultDictionaryInstalled = "DefaultDictionaryInstalled";
/**
* Check if app has installed default keyboard and dictionary. If not, install them
* @param context
*/
public static void install(Context context) {
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
// Add default keyboard
boolean installDefaultKeyboard = prefs.getBoolean(defaultKeyboardInstalled, false);
if (!installDefaultKeyboard) {
if (!KMManager.keyboardExists(context, KMManager.KMDefault_PackageID, KMManager.KMDefault_KeyboardID,
KMManager.KMDefault_LanguageID)) {
KMManager.addKeyboard(context, KMManager.getDefaultKeyboard(context.getApplicationContext()));
}
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(defaultKeyboardInstalled, true);
editor.commit();
}
// Add default dictionary
boolean installDefaultDictionary = prefs.getBoolean(defaultDictionaryInstalled, false);
if (!installDefaultDictionary) {
LexicalModel defaultLexicalModel = LexicalModel.getDefaultLexicalModel(context);
HashMap<String, String> lexicalModelInfo = new HashMap<String, String>();
lexicalModelInfo.put(KMManager.KMKey_PackageID, defaultLexicalModel.getPackageID());
lexicalModelInfo.put(KMManager.KMKey_LanguageID, defaultLexicalModel.getLanguageID());
lexicalModelInfo.put(KMManager.KMKey_LanguageName, defaultLexicalModel.getLanguageName());
lexicalModelInfo.put(KMManager.KMKey_LexicalModelID, defaultLexicalModel.getLexicalModelID());
lexicalModelInfo.put(KMManager.KMKey_LexicalModelName, defaultLexicalModel.getLexicalModelName());
lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, defaultLexicalModel.getVersion());
/*
// If welcome.htm exists, add custom help link
welcomeFile = new File(KMManager.getLexicalModelsDir(), KMManager.KMDefault_DictionaryPackageID + File.separator + FileUtils.WELCOME_HTM);
lexicalModelInfo.put(KMManager.KMKey_CustomHelpLink, welcomeFile.getPath());
*/
KMManager.addLexicalModel(context, lexicalModelInfo);
KMManager.registerAssociatedLexicalModel(KMManager.KMDefault_LanguageID);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(defaultDictionaryInstalled, true);
editor.commit();
}
}
}

View file

@ -4,6 +4,8 @@
package com.tavultesoft.kmapro;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
@ -50,11 +52,17 @@ import android.net.Uri;
import android.net.Uri.Builder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.app.AlertDialog;
import android.content.ClipData;
@ -75,7 +83,9 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.provider.Settings;
import android.text.Html;
import android.util.Log;
import android.util.TypedValue;
@ -101,7 +111,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
public static Context context;
// Fields used for installing kmp packages
private static final int PERMISSION_REQUEST_STORAGE = 0;
public static final int PERMISSION_REQUEST_STORAGE = 0;
public static final int READ_REQUEST_CODE = 42;
private static final String TAG = "MainActivity";
@ -111,8 +121,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
private final int maxTextSize = 72;
private int textSize = minTextSize;
private int lastOrientation = Configuration.ORIENTATION_UNDEFINED;
private static final String defaultKeyboardInstalled = "DefaultKeyboardInstalled";
private static final String defaultDictionaryInstalled = "DefaultDictionaryInstalled";
private static final String userTextKey = "UserText";
private static final String userTextSizeKey = "UserTextSize";
private Toolbar toolbar;
@ -147,43 +155,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP);
KMManager.executeResourceUpdate(this);
DefaultLanguageResource.install(context);
SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
// Add default keyboard
boolean installDefaultKeyboard = prefs.getBoolean(defaultKeyboardInstalled, false);
if (!installDefaultKeyboard) {
if (!KMManager.keyboardExists(context, KMManager.KMDefault_PackageID, KMManager.KMDefault_KeyboardID,
KMManager.KMDefault_LanguageID)) {
KMManager.addKeyboard(this, KMManager.getDefaultKeyboard(getApplicationContext()));
}
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(defaultKeyboardInstalled, true);
editor.commit();
}
// Add default dictionary
boolean installDefaultDictionary = prefs.getBoolean(defaultDictionaryInstalled, false);
if (!installDefaultDictionary) {
LexicalModel defaultLexicalModel = LexicalModel.getDefaultLexicalModel(context);
HashMap<String, String> lexicalModelInfo = new HashMap<String, String>();
lexicalModelInfo.put(KMManager.KMKey_PackageID, defaultLexicalModel.getPackageID());
lexicalModelInfo.put(KMManager.KMKey_LanguageID, defaultLexicalModel.getLanguageID());
lexicalModelInfo.put(KMManager.KMKey_LanguageName, defaultLexicalModel.getLanguageName());
lexicalModelInfo.put(KMManager.KMKey_LexicalModelID, defaultLexicalModel.getLexicalModelID());
lexicalModelInfo.put(KMManager.KMKey_LexicalModelName, defaultLexicalModel.getLexicalModelName());
lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, defaultLexicalModel.getVersion());
/*
// If welcome.htm exists, add custom help link
welcomeFile = new File(KMManager.getLexicalModelsDir(), KMManager.KMDefault_DictionaryPackageID + File.separator + FileUtils.WELCOME_HTM);
lexicalModelInfo.put(KMManager.KMKey_CustomHelpLink, welcomeFile.getPath());
*/
KMManager.addLexicalModel(context, lexicalModelInfo);
KMManager.registerAssociatedLexicalModel(KMManager.KMDefault_LanguageID);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(defaultDictionaryInstalled, true);
editor.commit();
}
KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString()));
KMManager.setSpacebarText(spacebarText);
@ -292,7 +266,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
// file:// Chrome downloads and Filebrowsers
case "content":
case "file":
checkStoragePermission(loadingIntentUri);
requestPermissionIntentUri = loadingIntentUri;
if (CheckPermissions.isPermissionOK(this)) {
useLocalKMP(context, loadingIntentUri);
} else {
CheckPermissions.requestPermission(this, context);
}
break;
case "http" :
case "https" :
@ -820,54 +799,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
if (requestCode == PERMISSION_REQUEST_STORAGE) {
// Request for storage permission
if (grantResults.length == 1 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
grantResults[0] == PERMISSION_GRANTED) {
// Permission has been granted. Resume task needing this permission
useLocalKMP(context, requestPermissionIntentUri);
} else {
// Permission request denied
String message = getString(R.string.storage_permission_denied);
Toast.makeText(getApplicationContext(), message,
Toast.LENGTH_SHORT).show();
CheckPermissions.showPermissionDenied(context);
}
}
}
private void checkStoragePermission(Uri data) {
// Check if the Storage permission has been granted
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
useLocalKMP(context, data);
} else {
// Permission is missing and must be requested
requestPermissionIntentUri = data;
requestStoragePermission();
}
} else {
// Permission automatically granted on older Android versions
useLocalKMP(context, data);
}
}
/**
* Requests the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permissions
*/
private void requestStoragePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
// Provide additional rationale to the user if the permission was not granted
String message = getString(R.string.request_storage_permission);
Toast.makeText(getApplicationContext(), message ,
Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(this,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
} else {
// Request the permission. The result will be received in onRequestPermissionsResult().
ActivityCompat.requestPermissions(this,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
}
}
// TODO: Move this to KMEA during Keyman 13.0 refactoring
public static void useLocalKMP(Context context, Uri data) {
if (data != null) {

View file

@ -92,6 +92,9 @@
<string name="storage_permission_denied" comment="Keyboard package installation may fail since Android storage permission not granted">
Storage permission request was denied. May fail to install keyboard package</string>
<string name="storage_permission_denied2" comment="Keyboard package installation failed. Recommend install from local file">
Storage permission request failed. Try Keyman Settings - Install from local file</string>
<!-- Context: Keyman Settings menu -->
<string name="keyman_settings" comment="Settings menu title">Settings</string>

View file

@ -1,4 +1,4 @@
var _debug = 0;
var _debug = false;
// Android harness attachment
if(window.parent && window.parent.jsInterface && !window.jsInterface) {
@ -235,11 +235,16 @@ function updateKMText(text) {
text = '';
}
console_debug('updateKMText(text='+text+') context.value='+keyman.context.getText());
console_debug('updateKMText(text=' + text + ') with: \n' + build_context_string(keyman.context));
if(text != keyman.context.getText()) {
if(!text || text != keyman.context.getText()) {
keyman.context.setText(text);
keyman.resetContext();
console_debug('result: \n' + build_context_string(keyman.context));
} else {
console_debug('context unchanged');
}
}
@ -249,11 +254,17 @@ function console_debug(s) {
}
}
function build_context_string(context) {
// Sadly, ES6-style "template strings" - strings with backticks - require Chrome 41+.
return 'preCaret: `' + context.getTextBeforeCaret() + '`\n' +
'selected: `' + context.getSelectedText() + '`\n' +
'postCaret: `' + context.getTextAfterCaret() + '`';
}
function updateKMSelectionRange(start, end) {
var context = keyman.context;
// console_debug('updateKMSelectionRange('+start+','+end+'): context.selStart='+ta.selectionStart+' '+
// '['+ta._KeymanWebSelectionStart+'] context.selEnd='+ta.selectionEnd+' '+ta._KeymanWebSelectionEnd);
console_debug('updateKMSelectionRange(' + start + ', ' + end + ') with: \n' + build_context_string(context));
if(start > end) {
var e0 = end;
@ -264,6 +275,10 @@ function updateKMSelectionRange(start, end) {
if(context.selStart != start || context.selEnd != end) {
keyman.context.setSelection(start, end);
keyman.resetContext();
console_debug('result:\n' + build_context_string(context));
} else {
console.debug('range unchanged');
}
}

View file

@ -17,6 +17,7 @@ import com.keyman.engine.data.KeyboardController;
import com.keyman.engine.KMManager.KeyboardType;
import com.keyman.engine.KeyboardEventHandler.EventType;
import com.keyman.engine.KeyboardEventHandler.OnKeyboardEventListener;
import com.keyman.engine.util.CharSequenceUtil;
import com.keyman.engine.util.DependencyUtil;
import com.keyman.engine.util.DependencyUtil.LibraryType;
import com.keyman.engine.util.FileUtils;
@ -167,9 +168,31 @@ final class KMKeyboard extends WebView {
InputConnection ic = KMManager.getInputConnection(this.keyboardType);
if (ic != null) {
ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0);
if (icText != null) {
updateText(icText.text.toString());
if (icText == null) {
return false;
}
String rawText = icText.text.toString();
updateText(rawText.toString());
/*
The values of selStart & selEnd provided by the system are in code units,
not code-points. We need to account for surrogate pairs here.
Fortunately, it uses UCS-2 encoding... just like JS.
References:
- https://stackoverflow.com/a/23980211
- https://android.googlesource.com/platform/frameworks/base/+/152944f/core/java/android/view/inputmethod/InputConnection.java#326
*/
// Count the number of characters which are surrogate pairs.
int pairsAtStart = CharSequenceUtil.countSurrogatePairs(rawText.substring(0, selStart), rawText.length());
String selectedText = rawText.substring(selStart, selEnd);
int pairsSelected = CharSequenceUtil.countSurrogatePairs(selectedText, selectedText.length());
selStart -= pairsAtStart;
selEnd -= (pairsAtStart + pairsSelected);
}
this.loadJavascript(KMString.format("updateKMSelectionRange(%d,%d)", selStart, selEnd));
result = true;

View file

@ -24,6 +24,7 @@ import com.keyman.engine.KMManager.KeyboardType;
import com.keyman.engine.data.Keyboard;
import com.keyman.engine.util.CharSequenceUtil;
import com.keyman.engine.util.KMLog;
import com.keyman.engine.util.KMString;
public class KMKeyboardJSHandler {
private Context context;
@ -284,14 +285,25 @@ public class KMKeyboardJSHandler {
// Chop dn+numPairs code points from the end of charsBackup
// subSequence indices are start(inclusive) to end(exclusive)
CharSequence expectedChars = charsBackup.subSequence(0, charsBackup.length() - (dn + numPairs));
int start = 0;
int end = charsBackup.length() - (dn + numPairs);
CharSequence expectedChars;
try {
expectedChars = charsBackup.subSequence(start, end);
} catch (IndexOutOfBoundsException e) {
KMLog.LogException(TAG,
KMString.format("Bad subSequence of start %d, end is %d, length %d, dn %d, numPairs %d",
start, end, charsBackup.length(), dn, numPairs), e);
expectedChars = "";
}
ic.deleteSurroundingText(dn + numPairs, 0);
CharSequence newContext = getCharacterSequence(ic, originalBufferLength - 2*dn);
// Shorten the retrieved context by exactly as many characters as were just deleted.
CharSequence newContext = getCharacterSequence(ic, originalBufferLength - dn - numPairs);
CharSequence charsToRestore = CharSequenceUtil.restoreChars(expectedChars, newContext);
if (charsToRestore.length() > 0) {
// Restore expectedChars that Chromium deleted.
// Use newCusorPosition 1 so cursor will be after the inserted string
// Use newCursorPosition 1 so cursor will be after the inserted string
ic.commitText(charsToRestore, 1);
}
}
@ -327,7 +339,15 @@ public class KMKeyboardJSHandler {
if (Character.isLowSurrogate(sequence.charAt(0))) {
// Adjust if the first char is also a split surrogate pair
// subSequence indices are start(inclusive) to end(exclusive)
sequence = sequence.subSequence(1, sequence.length());
int start = 1;
int end = sequence.length();
try {
sequence = sequence.subSequence(start, end);
} catch (IndexOutOfBoundsException e) {
KMLog.LogException(TAG,
KMString.format("Bad subSequence of start %d, end is %d",
start, end), e);
}
}
return sequence;

View file

@ -90,10 +90,7 @@ public final class KMKeyboardWebViewClient extends WebViewClient {
// Revert to default (index 0) or fallback keyboard
keyboardInfo = KMManager.getKeyboardInfo(context, 0);
if (keyboardInfo == null) {
// Only log SystemKeyboard to Sentry because some keyboard apps like FV don't install keyboards until the user chooses
if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
KMLog.LogError(TAG, "No keyboards installed. Reverting to fallback");
}
// Don't log to Sentry because some keyboard apps like FV don't install keyboards until the user chooses
keyboardInfo = KMManager.getDefaultKeyboard(context);
}
if (keyboardInfo != null) {

View file

@ -1632,6 +1632,12 @@ public final class KMManager {
return KeyboardPickerActivity.removeKeyboard(context, position);
}
public static boolean isDefaultKey(String key) {
return (
key != null &&
key.equals(KMString.format("%s_%s", KMDefault_LanguageID, KMDefault_KeyboardID)));
}
/**
* Some apps have the user select which keyboard to add. To ensure there's always a fallback
* system keyboard when the keyboard list is empty, use this method.
@ -2092,7 +2098,6 @@ public final class KMManager {
if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
i.putExtra(KMKey_DisplayKeyboardSwitcher, kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM);
@ -2171,13 +2176,13 @@ public final class KMManager {
public static Keyboard getCurrentKeyboardInfo(Context context) {
int index = getCurrentKeyboardIndex(context);
if(index < 0) {
// As of 15.0-beta and 15.0-stable, this only appears to occur when
// #6703 would trigger. This logging may help us better identify the
// root cause.
// index can be undefined if user installs Keyman (without launching it)
// and then enables Keyaman as a system keyboard from the Android settings menus.
// We'll only log if key isn't for fallback keyboard
String key = KMKeyboard.currentKeyboard();
// Even if it's null, it's still a notable error. This function shouldn't
// be reachable in execution (15.0) at a time this variable would be set to `null`.
KMLog.LogError(TAG, "Failed getCurrentKeyboardIndex check for keyboard: " + key);
if (!isDefaultKey(key)) {
KMLog.LogError(TAG, "Failed getCurrentKeyboardIndex check for keyboard: " + key);
}
return null;
}
return KeyboardController.getInstance().getKeyboardInfo(index);

View file

@ -2,7 +2,6 @@ package com.keyman.engine.cloud.impl;
import android.content.Context;
import android.net.Uri;
import android.widget.Toast;
import com.keyman.engine.BaseActivity;
import com.keyman.engine.KMKeyboardDownloaderActivity;
@ -99,8 +98,6 @@ public class CloudLexicalPackageDownloadCallback implements ICloudDownloadCallba
@Override
public void applyCloudDownloadToModel(Context aContext, Void aModel, CloudKeyboardDownloadReturns aCloudResult)
{
BaseActivity.makeToast(aContext, R.string.dictionary_download_finished, Toast.LENGTH_SHORT);
if(aCloudResult.installedResource != null)
{
KeyboardEventHandler.notifyListeners(KMKeyboardDownloaderActivity.getKbDownloadEventListeners(),

View file

@ -12,6 +12,7 @@ import com.keyman.engine.data.Keyboard;
import com.keyman.engine.KeyboardPickerActivity;
import com.keyman.engine.util.BCP47;
import com.keyman.engine.util.FileUtils;
import com.keyman.engine.util.KMString;
import com.keyman.engine.util.MapCompat;
import com.keyman.engine.KMManager;
import com.keyman.engine.util.KMLog;
@ -167,7 +168,7 @@ public class KeyboardController {
KMLog.LogError(TAG, "getKeyboardInfo while KeyboardController() not initialized");
return null;
} else if (index < 0) {
KMLog.LogError(TAG, "getKeyboardInfo with invalid index: " + index);
// Don't need to report to Sentry
return null;
}
@ -208,7 +209,10 @@ public class KeyboardController {
}
}
KMLog.LogError(TAG, "getKeyboardIndex failed for key " + key);
// We'll only log if key isn't for fallback keyboard
if (!KMManager.isDefaultKey(key)) {
KMLog.LogError(TAG, "getKeyboardIndex failed for key " + key);
}
return index;
}

View file

@ -572,7 +572,6 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
updateFailed = true;
checkingUpdates = false;
} else {
BaseActivity.makeToast(currentContext, R.string.update_success, Toast.LENGTH_SHORT);
lastUpdateCheck = Calendar.getInstance();
SharedPreferences prefs = currentContext.getSharedPreferences(currentContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();

View file

@ -48,7 +48,10 @@ public final class CharSequenceUtil {
if (expectedChars.length() != currentContext.length()) {
String expectedCharsString = expectedChars.toString();
String currentContextString = currentContext.toString();
int index = expectedCharsString.indexOf(currentContextString);
int index = expectedCharsString.lastIndexOf(currentContextString);
if (currentContextString.length() == 0) {
index = 0;
}
if (index > -1) {
// subSequence indices are start(inclusive) to end(exclusive)
charsToRestore = expectedChars.subSequence(index + currentContextString.length(), expectedChars.length());

View file

@ -172,7 +172,7 @@
<!-- Context: Background download messages-->
<string name="dictionary_download_is_running_in_background" comment="Notification that a dictionary download is still running">The selected dictionary is already downloading; please try again in a moment!</string>
<!-- Context: Background download messages-->
<!-- Context: Background download messages. Removed in Keyman 17-->
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">Dictionary download is finished.</string>
<!-- Context: Background download messages -->
@ -190,7 +190,7 @@
<!-- Context: General Updates -->
<string name="update_failed" comment="Notification that a resource update failed">One or more resources failed to update!</string>
<!-- Context: General Updates -->
<!-- Context: General Updates. Removed in Keyman 17 -->
<string name="update_success" comment="Notification that a resource update successfully updated">Resources successfully updated!</string>
<!-- Context: Model Info -->

View file

@ -151,5 +151,12 @@ public class CharSequenceUtilTest {
Assert.assertEquals("p" + COMPOSING_CIRCUMFLEX_ACCENT + "p" + COMPOSING_CIRCUMFLEX_ACCENT, charsToRestore);
}
@Test
public void test_repeated_char_backspace() {
CharSequence currentContext = "----------------";
CharSequence expectedChars = "-----------------";
CharSequence charsToRestore = CharSequenceUtil.restoreChars(expectedChars, currentContext);
Assert.assertEquals("", charsToRestore);
}
//endregion
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,11 +1,11 @@
# keyman-touch-layout
Documentation at https://help.keyman.com/developer/...TODO...
Documentation at https://github.com/keymanapp/keyman/tree/master/common/schemas/keyman-touch-layout/README.md
* Primary version:
* https://github.com/keymanapp/keyman/tree/master/common/schemas/keyman-touch-layout
* Deployed to:
* N/A
* https://github.com/keymanapp/api.keyman.com/tree/master/schemas/keyman-touch-layout
# .keyman-touch-layout File Format
@ -117,7 +117,8 @@ affect the modifiers in any key events generated by keys on the layer.
* `id`
: the string identifier of the layer. Required. There are a number of reserved
: the string identifier of the layer. Required. This should use alphanumeric,
hyphen, and underscore characters only. There are a number of reserved
identifiers. The following identifiers are recommended for use:
id | Modifier | Notes
@ -405,9 +406,9 @@ and `width`.
user longpresses the base key without moving their finger. Only one subkey
should be default; if multiple subkeys have the default field set, then
behaviour is undefined. Given that cancel is not the preferred default
action for a longpress, the user can cancel the longpress default by
sliding outside the 'acceptance' area -- e.g. south of the key cap,
which would then unhighlight the default and indicate visually that
action for a longpress, the user can cancel the longpress default by
sliding outside the 'acceptance' area -- e.g. south of the key cap,
which would then unhighlight the default and indicate visually that
no output would occur when they release their finger.
## `flick` object
@ -456,6 +457,10 @@ string") into the appropriate spec format.
# .keyman-touch-layout version history
## 2024-02-23 2.1.1 stable
* Loosen `layer.id` requirements to any non-whitespace characters, recommend
only alphanumeric, -, _. clean spec enforces this recommendation.
## 2023-08-08 2.1 stable
* Add support for default on longpress (sk) keys. Aligns with Keyman 17.0.

View file

@ -44,7 +44,7 @@
"layer-id": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$"
"pattern": "^[\\S]+$"
},
"rows": {

View file

@ -43,20 +43,20 @@ While running, kmcompxtest extracts those 4 characters from the Filename (e.g. 4
### Tests are available for the following CERR_:
<font size="1">
| | Error Number | CERR_
| | Error Number | CERR_
| :-: | :-----------:| :-------------------------------------
| | 00000800 | CERR_LEXICAL_MODEL_MIN
| | 00000800 | CERR_LEXICAL_MODEL_MIN
| | 000008FF | CERR_LEXICAL_MODEL_MAX
| | 00000800 | CERR_LEXICAL_MODEL_MIN
| | 000008FF | CERR_LEXICAL_MODEL_MAX
| | 00000000 | CERR_None
| | 00000001 | CERR_EndOfFile
| | 00000001 | CERR_EndOfFile
| | 00008002 | CERR_BadCallParams
| | 00008004 | CERR_CannotAllocateMemory
| | 00008005 | CERR_InfileNotExist
| | 00008006 | CERR_CannotCreateOutfile
| | 00004005 | CERR_InfileNotExist
| | 00004006 | // CERR_CannotCreateOutfile
| | 00008007 | CERR_UnableToWriteFully
| | 00008008 | CERR_CannotReadInfile
| | 00004008 | CERR_CannotReadInfile
| | 00008009 | CERR_SomewhereIGotItWrong
|**X**| 0000400A | CERR_InvalidToken
| | 0000400B | CERR_InvalidBegin

View file

@ -25,12 +25,14 @@ export class KeymanSentryManager {
_enabled: boolean = true;
static STANDARD_ALIASABLE_FILES = {
'keymanweb.js': 'keymanweb.js',
'keymanweb-webview.js': 'keymanweb-webview.js',
'kmwuibutton.js': 'kmwuibutton.js',
'kmwuifloat.js': 'kmwuifloat.js',
'kmwuitoggle.js': 'kmwuitoggle.js',
'kmwuitoolbar.js': 'kmwuitoolbar.js'
'keymanweb.js': 'keymanweb.js',
'keymanweb-webview.js': 'keymanweb-webview.js',
'keymanweb.es5.js': 'keymanweb.es5.js',
'keymanweb-webview.es5.js': 'keymanweb-webview.es5.js',
'kmwuibutton.js': 'kmwuibutton.js',
'kmwuifloat.js': 'kmwuifloat.js',
'kmwuitoggle.js': 'kmwuitoggle.js',
'kmwuitoolbar.js': 'kmwuitoolbar.js'
// Also add entries for the naming system used by Android and iOS - and map them to the EMBEDDED upload, not the std 'native' one.
}

View file

@ -29,8 +29,8 @@ builder_parse "$@"
function compile_schemas() {
# We need the schema files at runtime and bundled, so always copy it for all actions except `clean`
local schemas=(
"$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard3.schema.json"
"$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest3.schema.json"
"$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/45/ldml-keyboard3.schema.json"
"$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/45/ldml-keyboardtest3.schema.json"
"$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json"
"$KEYMAN_ROOT/common/schemas/kpj/kpj.schema.json"
"$KEYMAN_ROOT/common/schemas/kpj-9.0/kpj-9.0.schema.json"

View file

@ -38,7 +38,7 @@ export function build_vars(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_e
value: build_strs_index(sect_strs, v.value),
elem: build_elem_index(sect_elem, v.items),
});
const uniSetVars = kmxplus.vars.unicodeSets.map(v => <BUILDER_VARS_ITEM>{
const uniSetVars = kmxplus.vars.usets.map(v => <BUILDER_VARS_ITEM>{
type: constants.vars_entry_type_unicodeSet,
id: build_strs_index(sect_strs, v.id),
value: build_strs_index(sect_strs, v.value),

View file

@ -228,19 +228,19 @@ export class Strs extends Section {
*/
export class Vars extends Section {
totalCount() : number {
return this.strings.length + this.sets.length + this.unicodeSets.length;
return this.strings.length + this.sets.length + this.usets.length;
}
markers: ListItem;
strings: StringVarItem[] = []; // ≠ StrsItem
sets: SetVarItem[] = [];
unicodeSets: UnicodeSetItem[] = [];
usets: UnicodeSetItem[] = [];
/**
*
* @returns false if any invalid variables
*/
valid() : boolean {
for (const t of [this.sets, this.strings, this.unicodeSets]) {
for (const t of [this.sets, this.strings, this.usets]) {
for (const i of t) {
if (!i.valid()) {
return false;
@ -264,7 +264,7 @@ export class Vars extends Section {
}
substituteUnicodeSets(value: string, sections: DependencySections): string {
return value.replaceAll(VariableParser.SET_REFERENCE, (_entire, id) => {
const v = Vars.findVariable(this.unicodeSets, id);
const v = Vars.findVariable(this.usets, id);
if (v === null) {
// Should have been caught during validation.
throw Error(`Internal Error: reference to missing UnicodeSet variable ${id}`);
@ -297,7 +297,7 @@ export class Vars extends Section {
}
// try as unicodeset
const uset = Vars.findVariable(this.unicodeSets, id);
const uset = Vars.findVariable(this.usets, id);
if (uset !== null) {
const { unicodeSet } = uset;
const inner = unicodeSet.ranges.map(([start, end]) => {

View file

@ -87,7 +87,7 @@ export class LDMLKeyboardXMLSourceFileReader {
if(source?.keyboard3?.variables) {
boxXmlArray(source?.keyboard3?.variables, 'set');
boxXmlArray(source?.keyboard3?.variables, 'string');
boxXmlArray(source?.keyboard3?.variables, 'unicodeSet');
boxXmlArray(source?.keyboard3?.variables, 'uset');
}
if(source?.keyboard3?.transforms) {
for(let transforms of source.keyboard3.transforms) {
@ -156,7 +156,7 @@ export class LDMLKeyboardXMLSourceFileReader {
}
// then, the implied imports
if (subtag === 'keys') {
// <import base="cldr" path="techpreview/keys-Latn-implied.xml"/>
// <import base="cldr" path="45/keys-Latn-implied.xml"/>
if (!this.resolveOneImport(obj, subtag, {
base: constants.cldr_import_base,
path: constants.cldr_implied_keys_import
@ -164,7 +164,7 @@ export class LDMLKeyboardXMLSourceFileReader {
return false;
}
} else if (subtag === 'forms') {
// <import base="cldr" path="techpreview/scanCodes-implied.xml"/>
// <import base="cldr" path="45/scanCodes-implied.xml"/>
if (!this.resolveOneImport(obj, subtag, {
base: constants.cldr_import_base,
path: constants.cldr_implied_forms_import

View file

@ -1,5 +1,5 @@
//
// Conforms to techpreview
// Conforms to 45
//
// The interfaces in this file are designed with reference to the mapped
// structures produced by xml2js when passed a LDML keyboard .xml file.
@ -40,7 +40,7 @@ export interface LKImport {
*/
base: string;
/**
* path to imported resource, of the form `techpreview/*.xml`
* path to imported resource, of the form `45/*.xml`
*/
path: string;
};
@ -124,7 +124,7 @@ export interface LKRow {
export interface LKVariables {
string?: LKString[];
set?: LKSet[];
unicodeSet?: LKUnicodeSet[];
uset?: LKUSet[];
};
/**
@ -137,7 +137,7 @@ export interface Variable {
export interface LKString extends Variable {};
export interface LKSet extends Variable {};
export interface LKUnicodeSet extends Variable {};
export interface LKUSet extends Variable {};
export interface LKTransforms {
type?: "simple" | "backspace";

View file

@ -23,8 +23,8 @@ export * as Constants from './consts/virtual-key-constants.js';
export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, CompilerOptions, CompilerEvent, CompilerErrorNamespace,
CompilerErrorSeverity, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerCallbackOptions,
CompilerError, CompilerMessageSpec, compilerErrorSeverity, CompilerErrorMask, CompilerFileCallbacks, compilerErrorSeverityName,
compilerExceptionToString, compilerErrorFormatCode,
CompilerError, CompilerMessageSpec, CompilerMessageSpecWithException, compilerErrorSeverity, CompilerErrorMask, CompilerFileCallbacks, compilerErrorSeverityName,
compilerErrorFormatCode, CompilerMessageDef,
compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS,
ALL_COMPILER_LOG_FORMATS, CompilerLogFormat,
CompilerMessageOverride,
@ -54,7 +54,5 @@ export * as util from './util/util.js';
export * as KeymanFileTypes from './util/file-types.js';
export * as Osk from './osk/osk.js';
export * as Schemas from './schemas.js';
export * as SchemaValidators from './schema-validators.js';

View file

@ -1,109 +0,0 @@
import { TouchLayoutFile, TouchLayoutFlick, TouchLayoutKey, TouchLayoutPlatform, TouchLayoutSubKey } from "../keyman-touch-layout/keyman-touch-layout-file.js";
import { VisualKeyboard } from "../kvk/visual-keyboard.js";
import SchemaValidators from "../schema-validators.js";
export interface StringRefUsage {
filename: string;
count: number;
};
export interface StringRef {
str: string;
usages: StringRefUsage[];
};
export interface StringResult {
str: string; // the key cap string
unicode: string; // unicode code points in <str> for reference
pua: string; // hexadecimal single character in PUA range
usages: StringRefUsage[] | string[]; // files in which the string is referenced
};
export type PuaMap = {[index:string]: string};
export function parseMapping(mapping: any) {
if(!SchemaValidators.displayMap(<any>mapping))
/* c8 ignore next 3 */
{
throw new Error(JSON.stringify((<any>SchemaValidators.displayMap).errors));
}
let map: PuaMap = {};
for (let item of mapping.map) {
map[item.str] = String.fromCharCode(parseInt(item.pua, 16));
}
return map;
}
function cleanString(s: string): string {
s = s.replace(/\u25cc/g, '');
return s.trim();
}
function remap(text: string, map: PuaMap) {
if(map[text.trim()]) {
return map[text.trim()];
}
text = cleanString(text);
if(map[text]) {
return map[text];
}
return text;
}
export function remapVisualKeyboard(vk: VisualKeyboard, map: PuaMap): boolean {
let dirty = false;
for(let key of vk.keys) {
if(!key.text) {
continue;
}
const text = remap(key.text, map);
dirty = dirty || text != key.text;
key.text = text;
}
return dirty;
}
export function remapTouchLayout(source: TouchLayoutFile, map: PuaMap) {
let dirty = false;
const scanKey = (key: TouchLayoutKey | TouchLayoutSubKey) => {
if(!key.text) {
return;
}
if(key.text.length > 2 && key.text[0] == '*' && key.text[key.text.length-1] == '*') {
// Don't touch '*special*' key captions
return;
}
const text = remap(key.text, map);
dirty = dirty || text != key.text;
key.text = text;
}
const scanPlatform = (platform: TouchLayoutPlatform) => {
if(!platform) {
return;
}
for(let layer of platform.layer) {
for(let row of layer.row) {
for(let key of row.key) {
scanKey(key);
let f: keyof TouchLayoutFlick;
for(f in key.flick ?? {}) {
scanKey(key.flick[f]);
}
for(let sk of key.sk ?? []) {
scanKey(sk);
}
for(let mt of key.multitap ?? []) {
scanKey(mt);
}
}
}
}
}
scanPlatform(source.desktop);
scanPlatform(source.phone);
scanPlatform(source.tablet);
return dirty;
}

View file

@ -44,8 +44,8 @@ export interface KpsFileOptions {
LicenseFile?: string;
WelcomeFile?: string;
ExecuteProgram?: string;
MsiFileName?: string;
MsiOptions?: string;
MSIFileName?: string;
MSIOptions?: string;
}
export interface KpsFileInfo {

View file

@ -1,4 +1,4 @@
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from './compiler-interfaces.js';
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m } from './compiler-interfaces.js';
import { constants } from '@keymanapp/ldml-keyboard-constants';
const CommonTypesErrMask = CompilerErrorNamespace.CommonTypes;
@ -10,37 +10,37 @@ const SevError = CompilerErrorSeverity.Error | CommonTypesErrMask;
export class CommonTypesMessages {
// structured Ajv validation error
static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}) => m(this.ERROR_SchemaValidationError,
`Error validating LDML XML file: ${o.instancePath}: ${o.keyword}: ${o.message} ${o.params}`);
static ERROR_SchemaValidationError = SevError | 0x0001;
static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}) => m(this.ERROR_SchemaValidationError,
`Error validating LDML XML file: ${def(o.instancePath)}: ${def(o.keyword)}: ${def(o.message)} ${def(o.params)}`);
static ERROR_ImportInvalidBase = SevError | 0x0002;
static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) =>
m(this.ERROR_ImportInvalidBase,
`Import element with base ${o.base} is unsupported. Only ${constants.cldr_import_base} is supported.`);
static ERROR_ImportInvalidBase = SevError | 0x0002;
`Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} is supported.`);
static ERROR_ImportInvalidPath = SevError | 0x0003;
static Error_ImportInvalidPath = (o: { base: string, path: string, subtag: string }) =>
m(this.ERROR_ImportInvalidPath,
`Import element with invalid path ${o.path}: expected the form '${constants.cldr_version_latest}/*.xml`);
static ERROR_ImportInvalidPath = SevError | 0x0003;
`Import element with invalid path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml`);
static ERROR_ImportReadFail = SevError | 0x0004;
static Error_ImportReadFail = (o: { base: string, path: string, subtag: string }) =>
m(this.ERROR_ImportReadFail,
`Import could not read data with path ${o.path}: expected the form '${constants.cldr_version_latest}/*.xml'`);
static ERROR_ImportReadFail = SevError | 0x0004;
`Import could not read data with path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml'`);
static ERROR_ImportWrongRoot = SevError | 0x0005;
static Error_ImportWrongRoot = (o: { base: string, path: string, subtag: string }) =>
m(this.ERROR_ImportWrongRoot,
`Invalid import file ${o.path}: expected ${o.subtag} as root element.`);
static ERROR_ImportWrongRoot = SevError | 0x0005;
`Invalid import file ${def(o.path)}: expected ${def(o.subtag)} as root element.`);
static ERROR_ImportMergeFail = SevError | 0x0006;
static Error_ImportMergeFail = (o: { base: string, path: string, subtag: string, subsubtag: string }) =>
m(this.ERROR_ImportMergeFail,
`Problem importing ${o.path}: not sure how to handle non-array ${o.subtag}.${o.subsubtag}`);
static ERROR_ImportMergeFail = SevError | 0x0006;
`Problem importing ${def(o.path)}: not sure how to handle non-array ${def(o.subtag)}.${def(o.subsubtag)}`);
static ERROR_TestDataUnexpectedArray = SevError | 0x0007;
static Error_TestDataUnexpectedArray = (o: {subtag: string}) =>
m(this.ERROR_TestDataUnexpectedArray,
`Problem reading test data: expected single ${o.subtag} element, found multiple`);
static ERROR_TestDataUnexpectedArray = SevError | 0x0007;
`Problem reading test data: expected single ${def(o.subtag)} element, found multiple`);
};

View file

@ -6,6 +6,11 @@ export interface CompilerEvent {
line?: number;
code: number;
message: string;
/**
* detailed Markdown-formatted description of the error including
* references to documentation, remediation options.
*/
detail?: string;
/**
* an internal error occurred that should be captured with a stack trace
* e.g. to the Keyman sentry instance by kmc
@ -89,7 +94,7 @@ export class CompilerError {
* ```
*/
static formatCode(code: number): string {
return 'KM' + CompilerError.error(code).toString(16).toUpperCase().padStart(5, '0');
return Number.isInteger(code) ? 'KM' + CompilerError.error(code).toString(16).toUpperCase().padStart(5, '0') : 'KM?????';
}
/**
@ -524,25 +529,29 @@ export const defaultCompilerOptions: CompilerOptions = {
/**
* Convenience function for constructing CompilerEvents
* @param code
* @param message
* @param code Unique numeric value of the event
* @param message A short description of the error presented to the user
* @param detail Detailed Markdown-formatted description of the error
* including references to documentation, remediation options.
* @returns
*/
export const CompilerMessageSpec = (code: number, message: string, exceptionVar?: any) : CompilerEvent => ({
export const CompilerMessageSpec = (code: number, message: string, detail?: string) : CompilerEvent => ({
code,
message,
detail,
});
export const CompilerMessageDef = (param: any) => String(param ?? `<param>`);
export const CompilerMessageSpecWithException = (code: number, message: string, exceptionVar: any, detail?: string) : CompilerEvent => ({
code,
message: exceptionVar
? (message ?? `Unexpected exception`) + `: ${exceptionVar.toString()}\n\nCall stack:\n${(exceptionVar instanceof Error ? exceptionVar.stack : (new Error()).stack)}` :
message,
exceptionVar
detail,
exceptionVar,
});
/**
* @deprecated use `CompilerError.exceptionToString` instead
*/
export function compilerExceptionToString(e?: any) : string {
return CompilerError.exceptionToString(e);
}
/**
* Compiler logging level and correspondence to severity
*/

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="und" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="Test Minimal Keyboard"/>
</keyboard3>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="und" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="Test Minimal Keyboard"/>
<keys>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="und" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="Test Minimal Keyboard"/>
<keys>
<key id="a" output="å"/> <!-- override -->

View file

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="und" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="Test Minimal Keyboard"/>
<keys>
<import base="cldr" path="techpreview/keys-Zyyy-punctuation.xml"/>
<import base="cldr" path="45/keys-Zyyy-punctuation.xml"/>
<key id="zz" output="zz"/>
<key id="hash" output="##"/> <!-- override -->
</keys>

View file

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DOCTYPE keyboard SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd"
Disabling doctype for this invalid file, not used by compiler, and avoids complaints in IDEs etc.
-->
<keyboard3 locale="mt" conformsTo="nothing-anyone-ever-heard-of"> <!-- invalid -->
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="nothing-anyone-ever-heard-of"> <!-- invalid -->
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>

View file

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>
<import base="cldr" path="techpreview/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml"/> <!-- invalid -->
<import base="cldr" path="45/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml"/> <!-- invalid -->
<key id="hmaqtugha" output="ħ" />
<key id="that" output="ថា" />
</keys>

View file

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>
<import base="cldr" path="techpreview/none-Zxxx-does-not-exist.xml"/> <!-- invalid -->
<import base="cldr" path="45/none-Zxxx-does-not-exist.xml"/> <!-- invalid -->
<key id="hmaqtugha" output="ħ" />
<key id="that" output="ថា" />
</keys>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info name="TestKbd" author="srl295" indicator="🙀" layout="qwerty" />
@ -10,7 +9,7 @@
</keys>
<flicks>
<import base="cldr" path="techpreview/keys-Zyyy-punctuation.xml"/> <!-- wrong root! -->
<import base="cldr" path="45/keys-Zyyy-punctuation.xml"/> <!-- wrong root! -->
<flick id="test">
<flickSegment directions="n nw" keyId="hmaqtugha" />
</flick>

View file

@ -1,8 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DOCTYPE keyboard SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd"
Disabling doctype for this invalid file, not used by compiler, and avoids complaints in IDEs etc.
-->
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
</keyboard3>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<!--
Read by:

View file

@ -14,7 +14,7 @@ describe('ldml keyboard xml reader tests', function () {
callback: (data, source) => {
assert.ok(source);
assert.ok(source.keyboardTest3);
assert.equal(source.keyboardTest3.conformsTo, constants.cldr_version_latest);
assert.equal(source.keyboardTest3.conformsTo, constants.cldr_test_version_latest);
assert.deepEqual(source.keyboardTest3.info, {
keyboard: 'fr-t-k0-azerty.xml',

View file

@ -28,7 +28,7 @@ describe('ldml keyboard xml reader tests', function () {
instancePath: '/keyboard3/conformsTo',
keyword: 'enum',
message: `must be equal to one of the allowed values`,
params: 'allowedValues="techpreview"',
params: 'allowedValues="45"',
})],
},
{
@ -113,7 +113,7 @@ describe('ldml keyboard xml reader tests', function () {
errors: [
CommonTypesMessages.Error_ImportInvalidPath({
base: null,
path: 'techpreview/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml',
path: '45/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml',
subtag: null,
}),
],
@ -124,7 +124,7 @@ describe('ldml keyboard xml reader tests', function () {
errors: [
CommonTypesMessages.Error_ImportReadFail({
base: null,
path: 'techpreview/none-Zxxx-does-not-exist.xml',
path: '45/none-Zxxx-does-not-exist.xml',
subtag: null,
}),
],
@ -135,7 +135,7 @@ describe('ldml keyboard xml reader tests', function () {
errors: [
CommonTypesMessages.Error_ImportWrongRoot({
base: null,
path: 'techpreview/keys-Zyyy-punctuation.xml',
path: '45/keys-Zyyy-punctuation.xml',
subtag: 'flicks',
}),
],

View file

@ -16,11 +16,11 @@
#pragma once
#define LDML_BKSP_FLAGS_ERROR 0x1
#define LDML_CLDR_IMPLIED_FORMS_IMPORT "techpreview/scanCodes-implied.xml"
#define LDML_CLDR_IMPLIED_KEYS_IMPORT "techpreview/keys-Latn-implied.xml"
#define LDML_CLDR_IMPLIED_FORMS_IMPORT "45/scanCodes-implied.xml"
#define LDML_CLDR_IMPLIED_KEYS_IMPORT "45/keys-Latn-implied.xml"
#define LDML_CLDR_IMPORT_BASE "cldr"
#define LDML_CLDR_VERSION_LATEST "techpreview"
#define LDML_CLDR_VERSION_TECHPREVIEW "techpreview"
#define LDML_CLDR_TEST_VERSION_LATEST "techpreview"
#define LDML_CLDR_VERSION_LATEST "45"
#define LDML_ELEM_FLAGS_ORDER_BITSHIFT 0x10
#define LDML_ELEM_FLAGS_ORDER_MASK 0xFF0000
#define LDML_ELEM_FLAGS_PREBASE 0x8

View file

@ -57,13 +57,13 @@ class Constants {
*/
readonly version = '1.0';
/**
* The techpreview CLDR version
* The current CLDR version
*/
readonly cldr_version_techpreview = 'techpreview';
readonly cldr_version_latest = '45';
/**
* The latest CLDR version
* The version for testdata files
*/
readonly cldr_version_latest = this.cldr_version_techpreview;
readonly cldr_test_version_latest = 'techpreview';
/**
* import base
*/
@ -71,11 +71,11 @@ class Constants {
/**
* implied keys file
*/
readonly cldr_implied_keys_import = `${this.cldr_version_techpreview}/keys-Latn-implied.xml`;
readonly cldr_implied_keys_import = `${this.cldr_version_latest}/keys-Latn-implied.xml`;
/**
* implied scancodes file
*/
readonly cldr_implied_forms_import = `${this.cldr_version_techpreview}/scanCodes-implied.xml`;
readonly cldr_implied_forms_import = `${this.cldr_version_latest}/scanCodes-implied.xml`;
/**
* Length of a raw section header, in bytes
*/

View file

@ -11,7 +11,8 @@ project('keyman_core', 'cpp', 'c',
default_options : ['buildtype=release',
'cpp_std=c++14',
'b_vscrt=static_from_buildtype',
'warning_level=2'],
'warning_level=2',
'debug=true'],
meson_version: '>=0.57.0')
# Import our standard compiler defines; this is copied from

View file

@ -22,7 +22,7 @@ working on ‘layr’, using ‘disp’ as a model from https://github.com/keyma
## XML changes
- `resources/standards-data/ldml-keyboards/techpreview/` : update / reimport / fix fixup script if needed
- `resources/standards-data/ldml-keyboards/45/` : update / reimport / fix fixup script if needed
- E/C: `common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts`
- add to `LKKeyboard` and subproperties as needed to support the structure on the XML side
- Now would be a good time to stop and make sure everything compiles. It didn’t, there was an unrelated issue with snprintf!

View file

@ -155,7 +155,7 @@ reorder_sort_key::dump() const {
size_t
element_list::match_end(const std::u32string &str) const {
if (str.size() < size()) {
if (str.length() < size()) {
// input string too short, can't possibly match.
// This assumes each element is a single char, no string elements.
return 0;
@ -292,7 +292,7 @@ reorder_entry::match_end(std::u32string &str, size_t offset, size_t len) const {
// is also a precondition.
if (!before.empty()) {
// does not match before offset
std::u32string prefix = substr.substr(0, substr.size() - match_len);
std::u32string prefix = substr.substr(0, substr.length() - match_len);
// make sure the 'before' is present
if (before.match_end(prefix) == 0) {
return 0; // break out.
@ -342,7 +342,7 @@ reorder_group::apply(std::u32string &str) const {
for (const auto &r : list) {
// work backward from end of string forward
// That is, see if "abc" matches "abc" or "ab" or "a"
for (size_t s = out.size(); s > 0; s--) {
for (size_t s = out.length(); s > 0; s--) {
size_t submatch = r.match_end(out, 0, s);
if (submatch != 0) {
#if KMXPLUS_DEBUG_TRANSFORM
@ -669,6 +669,60 @@ any_group::any_group(const transform_group &g) : type(any_group_type::transform)
any_group::any_group(const reorder_group &g) : type(any_group_type::reorder), transform(), reorder(g) {
}
size_t
any_group::apply(std::u32string &input, std::u32string &output, size_t matched) const {
if (type == any_group_type::transform) {
return apply_transform(input, output, matched);
} else if(type == any_group_type::reorder) {
return apply_reorder(input, output, matched);
} else {
assert(type != any_group_type::transform && type != any_group_type::reorder);
return matched;
}
}
size_t
any_group::apply_transform(std::u32string &input, std::u32string &output, size_t matched) const {
std::u32string subOutput;
size_t subMatched = transform.apply(input, subOutput);
if (subMatched == 0) {
return matched; // no match, break out
}
// remove the matched part of the input
assert(subMatched <= input.length());
input.resize(input.length() - subMatched); // chop off the subMatched part at end
input.append(subOutput); // subOutput could be empty such as in backspace transform
if (subMatched <= output.length()) {
// remove matched part of output
output.resize(output.length() - subMatched);
} else {
// matched past beginning of 'output', expand match
matched += (subMatched - output.length());
output.resize(0);
}
output.append(subOutput);
return matched;
}
size_t
any_group::apply_reorder(std::u32string &input, std::u32string &output, size_t matched) const {
std::u32string str2 = input;
if (reorder.apply(str2)) {
output.resize(0);
output.append(str2);
input.resize(0);
input.append(str2);
// Consider the entire string as 'matched'.
// The calling chain will determine which characters actually changed.
matched = output.length();
}
return matched;
}
transforms::transforms(bool norm_disabled) : transform_groups(), normalization_disabled(norm_disabled) {
}
@ -706,7 +760,7 @@ transform_group::apply(const std::u32string &input, std::u32string &output) cons
* Apply this entire transform set to the input.
* Example: input "abc" -> output="xyz", return=2: replace last two chars "bc" with "xyz", so final output = "abxyz";
* @param input input string, will match at end: unmodified
* @param output on output: if return>0, contains text to replace
* @param output cleared. on output: if return>0, contains text to replace
* @return match length: number of chars at end of input string to modify. 0 if no match.
*/
size_t
@ -744,55 +798,17 @@ transforms::apply(const std::u32string &input, std::u32string &output) {
* Matched can increment.
*/
size_t matched = 0;
/** modified copy of input */
output.clear();
/** modified copy of input, to pass to each next step */
std::u32string updatedInput = input;
// loop over each group of transforms
for (auto group = transform_groups.begin(); group < transform_groups.end(); group++) {
// for each transform group
// break out once there's a match
/** Length of match within this group*/
// find the first match in this group (if present)
if (group->type == any_group_type::transform) {
std::u32string subOutput;
size_t subMatched = group->transform.apply(updatedInput, subOutput);
if (subMatched != 0) {
// remove the matched part of the updatedInput
updatedInput.resize(updatedInput.length() - subMatched); // chop of the subMatched part at end
updatedInput.append(subOutput); // subOutput could be empty such as in backspace transform
if (subMatched > output.size()) {
// including first time through
// expand match by amount subMatched prior to output
matched += (subMatched - output.size());
} // else: didn't match prior to the existing output, so don't expand 'match'
// now update 'output'
if (subOutput.length() >= output.length() || subMatched > output.length()) {
output = subOutput; // replace all output
} else {
// replace output with new output
output.resize(output.length() - subMatched);
output.append(subOutput);
}
}
} else if (group->type == any_group_type::reorder) {
// TODO-LDML: cheesy solution. We should be finding a smaller
// common match here.
std::u32string str2 = updatedInput;
if (group->reorder.apply(str2)) {
// pretend the whole thing matched
output.resize(0);
output.append(str2);
updatedInput.resize(0);
updatedInput.append(str2);
matched = output.length();
}
}
// else: continue to next group
matched = group->apply(updatedInput, output, matched);
}
/**
* TODO-LDML: optimization to contract 'matched' if possible.
* Could optimize to contract 'matched' if possible.
* We could decrement 'matched' for every char of output
* which is already in input. Example (regex example):
* - str = "xxyyzz";
@ -803,6 +819,9 @@ transforms::apply(const std::u32string &input, std::u32string &output) {
* (but could contract to match=1, output='w')
*
* could also handle from="x" to="x" as match=0
*
* However, the calling code already checks for common prefixes,
* so this does not need to be optimized.
*/
return matched;
}

View file

@ -232,6 +232,11 @@ public:
any_group_type type; // transform or reorder
transform_group transform;
reorder_group reorder;
size_t apply(std::u32string &input, std::u32string &output, size_t matched) const;
private:
size_t apply_transform(std::u32string &input, std::u32string &output, size_t matched) const;
size_t apply_reorder(std::u32string &input, std::u32string &output, size_t matched) const;
};
/**

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_001_tiny.xml" author="srl295" name="k_001_tiny" />
<!-- <repertoire chars="[ħ ថា]" type="simple" name="our-little-language" /> TODO-LDML: not working yet -->

View file

@ -8,8 +8,7 @@
@@expected: \u0127\u1790\u17B6\u0127
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>

View file

@ -6,8 +6,7 @@
@@expected: \u0127\u1790\u17B6\u0127\uD83D\uDE40\u0127
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd-UTF32"/>
<keys>

View file

@ -7,8 +7,7 @@ from https://github.com/unicode-org/cldr/blob/keyboard-preview/docs/ldml/tr35-ke
@@expected: qu\u00ea
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TransformKbd"/>
<keys>

View file

@ -6,8 +6,7 @@
@@expected: \u0037\u1790\u17B6\u0127
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>

View file

@ -6,8 +6,7 @@
@@expected: \u0061\u0041\u0062\u0063\u0064\u0064\u0065\u0066\u0067\u0067
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys/> <!-- implied only -->

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_006_backspace.xml" author="Team Keyboard" name="backspace" />
<tests name="backspace">

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TransformKbd"/>
<keys>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_007_transform_rgx.xml" author="Team Keyboard" name="marker" />
<tests name="regex-tests">

View file

@ -4,8 +4,7 @@
from https://github.com/unicode-org/cldr/blob/keyboard-preview/docs/ldml/tr35-keyboards.md#element-transform
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="k_007_transform_rgx" />
<keys>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_008_transform_norm.xml" author="Team Keyboard" name="transform-norm" />
<tests name="regex-test-dot0">

View file

@ -5,8 +5,7 @@ from
https://github.com/unicode-org/cldr/blob/keyboard-preview/docs/ldml/tr35-keyboards.md#element-transform
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="k_008_transform_norm" />
<keys>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_009_transform_nfc.xml" author="Team Keyboard" name="transform-norm" />
<tests name="regex-test-dot0">

View file

@ -3,8 +3,7 @@
<!--
like k_008 but in NFC (and other normalizations)
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="k_009_transform_nfc" />
<settings normalization="disabled" />

View file

@ -10,9 +10,7 @@ Gets part of the way,
Based on mt.xml from CLDR. 'TODO-LDML' denotes modifications.
Note this is the 47-key version.
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<!-- remainder entirely from resources/standards-data/ldml-keyboards/techpreview/3.0/mt-t-k0-47key.xml -->
<keyboard3 locale="mt-t-k0-47key" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt-t-k0-47key" conformsTo="45">
<locales>
<!-- English is also an official language in Malta.-->
<locale id="en" />
@ -21,8 +19,8 @@ Gets part of the way,
<keys>
<!-- imports -->
<import base="cldr" path="techpreview/keys-Zyyy-punctuation.xml" />
<import base="cldr" path="techpreview/keys-Zyyy-currency.xml" />
<import base="cldr" path="45/keys-Zyyy-punctuation.xml" />
<import base="cldr" path="45/keys-Zyyy-currency.xml" />
<!-- accent grave -->
<key id="a-grave" output="à" />

View file

@ -11,8 +11,7 @@ Exact copy of mt.xml from CLDR, but with:
- an updated DTD path
- test case
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
<locales>
<!-- English is also an official language in Malta.-->
<locale id="en" />
@ -21,8 +20,8 @@ Exact copy of mt.xml from CLDR, but with:
<keys>
<!-- imports -->
<import base="cldr" path="techpreview/keys-Zyyy-punctuation.xml"/>
<import base="cldr" path="techpreview/keys-Zyyy-currency.xml"/>
<import base="cldr" path="45/keys-Zyyy-punctuation.xml"/>
<import base="cldr" path="45/keys-Zyyy-currency.xml"/>
<!-- accent grave -->
<key id="a-grave" output="à" />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Note: similar to, but slightly ahead of, fr-t-k0-azerty -->
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_020_fr.xml" author="Team Keyboard" name="fr-test-updated" />
<repertoire name="simple-repertoire" chars="[a b c d e \u0022]" type="simple" /> <!-- verify that these outputs are all available from simple keys on any layer, for all form factors -->

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Note: similar to, but slightly ahead of, fr-t-k0-azerty -->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="fr-t-k0-azerty" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="fr-t-k0-azerty" conformsTo="45">
<locales>
<locale id="br" /> <!-- example of including Breton -->
</locales>
@ -28,8 +27,8 @@
</displays>
<keys>
<import base="cldr" path="techpreview/keys-Zyyy-punctuation.xml" />
<import base="cldr" path="techpreview/keys-Zyyy-currency.xml" />
<import base="cldr" path="45/keys-Zyyy-punctuation.xml" />
<import base="cldr" path="45/keys-Zyyy-currency.xml" />
<!-- switch keys -->
<key id="shift" layerId="shift" />

View file

@ -5,8 +5,7 @@
@@expected: \u0061
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>

View file

@ -4,8 +4,7 @@
@@keys: [K_BKQUOTE]
@@expected: \u0061
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<displays>

View file

@ -16,8 +16,7 @@
q<enter>az Az enter=not mappable, should cause context reset of qaz. TODO-LDML no invalidate here?
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<!-- TODO-LDML: split to separate test with only implied keys - the <keys/> element is omitted. -->
<keys>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_200_reorder_nod_Lana.xml" author="Team Keyboard" name="nod-Lana" />
<tests name="reorder-tests">

View file

@ -5,8 +5,7 @@
see https://keyman.com/keyboards/sil_boonkit
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="nod" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="nod" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="nod-Lana Test"/>
<keys>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_201_reorder_esk.xml" author="Team Keyboard" name="esk-reorder-test" />
<tests name="basic">

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en-t-k0-esk" conformsTo="techpreview">
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en-t-k0-esk" conformsTo="45">
<info author="srl295" indicator="🙀" layout="qwerty" name="esk reorder test"/>
<displays>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/45/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_210_marker.xml" author="Team Keyboard" name="marker" />
<tests name="marker-tests">

Some files were not shown because too many files have changed in this diff Show more