Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): new actions APIs #9828

Merged
merged 12 commits into from
Oct 25, 2023
199 changes: 193 additions & 6 deletions core/include/keyman/keyman_core_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,8 @@ km_core_context_items_from_utf8(char const *text,
Convert a context item array into a UTF-16 encoded string placing it into
the supplied buffer of specified size, and return the number of code units
actually used in the conversion. If null is passed as the buffer the
number codeunits required is returned. This will strip markers from the
context during the conversion.
number of codeunits required is returned. Any markers in the context will
not be included in the output buffer.
##### Return status:
- `KM_CORE_STATUS_OK`: On success.
- `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null.
Expand Down Expand Up @@ -285,8 +285,8 @@ km_core_context_items_to_utf16(km_core_context_item const *item,
Convert a context item array into a UTF-8 encoded string placing it into
the supplied buffer of specified size, and return the number of code units
actually used in the conversion. If null is passed as the buffer the
number codeunits required is returned. This will strip markers from the
context during the conversion.
number of codeunits required is returned. Any markers in the context will
not be included in the output buffer.
##### Return status:
- `KM_CORE_STATUS_OK`: On success.
- `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null.
Expand All @@ -310,6 +310,38 @@ km_core_context_items_to_utf8(km_core_context_item const *item,
char *buf,
size_t *buf_size);

/*
```
### `km_core_context_items_to_utf32`
##### Description:
Convert a context item array into a UTF-32 encoded string placing it into
the supplied buffer of specified size, and return the number of codepoints
actually used in the conversion. If null is passed as the buffer the
number of codepoints required is returned. Any markers in the context will
not be included in the output buffer.
##### Return status:
- `KM_CORE_STATUS_OK`: On success.
- `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null.
- `KM_CORE_STATUS_INSUFFICENT_BUFFER`: If the buffer is not large enough.
`buf_size` will contain the space required. The contents of the buffer are
undefined.
##### Parameters:
- __context_items__: A pointer to the start of an array `km_core_context_item`.
Must be terminated with a type of `KM_CORE_CT_END`.
- __buf__: A pointer to the buffer to place the UTF-32 string into.
May be null to request size calculation.
- __buf_size__: a pointer to the result variable:
The size of the supplied buffer in codepoints if `buf` is given.
On return will be the size required if `buf` is null.
mcdurdin marked this conversation as resolved.
Show resolved Hide resolved

```c
*/
KMN_API
km_core_status
km_core_context_items_to_utf32(km_core_context_item const *item,
km_core_usv *buf,
size_t *buf_size);

/*
```
### `km_core_context_items_dispose`
Expand Down Expand Up @@ -501,10 +533,10 @@ typedef struct {
uint8_t type;
uint8_t _reserved[sizeof(void*)-sizeof(uint8_t)];
union {
uintptr_t marker; // MARKER type
uint32_t marker; // MARKER type
km_core_option_item const * option; // OPT types
km_core_usv character; // CHAR type
uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off
uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off; re name see #9833
km_core_backspace_item backspace; // BACKSPACE type
};
} km_core_action_item;
Expand All @@ -526,6 +558,161 @@ enum km_core_action_type {
KM_CORE_IT_MAX_TYPE_ID
};

/*
```
### Actions
This structure provides the results of processing a key event to the Platform layer and
should be processed by the Platform layer to issue commands to the os text
services framework to transform the text store in the Client Application, among
other actions.

This API replaces the Action items APIs, which is now deprecated and will be
removed in the future.
```c
*/

typedef enum { KM_CORE_FALSE = 0, KM_CORE_TRUE = 1 } km_core_bool;
typedef enum { KM_CORE_CAPS_UNCHANGED = -1, KM_CORE_CAPS_OFF = 0, KM_CORE_CAPS_ON = 1 } km_core_caps_state;

typedef struct {
// number of codepoints (not codeunits!) to delete from app context.
unsigned int code_points_to_delete;

// null-term string of characters to insert into document
km_core_usv* output;

// list of options to persist, terminated with KM_CORE_OPTIONS_END
mcdurdin marked this conversation as resolved.
Show resolved Hide resolved
km_core_option_item* persist_options;

// issue a beep, 0 = no, 1 = yes
km_core_bool do_alert;

// emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes
km_core_bool emit_keystroke;

// -1=unchanged, 0=off, 1=on
km_core_caps_state new_caps_lock_state;
} km_core_actions;

/*
```
### `km_core_state_get_actions`
##### Description:
Returns a pointer to an actions object which details all the actions
that the Platform layer must take after a keystroke. The `code_points_to_delete`
action must be performed before the `output` action, but the other
actions may be performed in any order.
##### Return:
mcdurdin marked this conversation as resolved.
Show resolved Hide resolved
A pointer to a `km_core_actions` object, which must be freed with
`km_core_actions_dispose`.
##### Parameters:
- __state__: An opaque pointer to a state object.

```c
*/
KMN_API
km_core_actions*
km_core_state_get_actions(
km_core_state const *state
);

/*
```
### `km_core_actions_dispose`
mcdurdin marked this conversation as resolved.
Show resolved Hide resolved
##### Description:
Free the allocated memory belonging to an actions object previously
returned by `km_core_state_get_actions`.
##### Parameters:
- __actions__: A pointer to the actions object to be disposed of.

```c
*/
KMN_API
km_core_status
km_core_actions_dispose(
km_core_actions* actions
);

/*
```
### `km_core_context_status`
##### Description:
Return values for `km_core_state_context_set_if_needed`.

```c
*/

mcdurdin marked this conversation as resolved.
Show resolved Hide resolved
typedef enum {
KM_CORE_CONTEXT_STATUS_UNCHANGED = 0, // Cached context change was not needed
KM_CORE_CONTEXT_STATUS_UPDATED = 1, // Cached context was set to application context
KM_CORE_CONTEXT_STATUS_CLEARED = 2, // Application context was invalid, context was cleared
KM_CORE_CONTEXT_STATUS_ERROR = 3, // Internal error
KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT = 4, // Invalid arguments
} km_core_context_status;

/*
```
### `km_core_state_context_set_if_needed`
##### Description:
Sets the internal cached context for the state object, to the passed-in
application context string, if it differs from the codepoints in the
cached context. For the purposes of comparison, (1) cached markers are
ignored, (2) if the cached context is shorter than the application
context, it is considered identical, but (3) if the cached context is
longer, then it is considered different.

If a difference is found, then the cached context will be set to the
application context, and thus any cached markers will be cleared.

`km_core_state_context_set_if_needed` and `km_core_state_context_clear`
will replace most uses of the existing Core context APIs.

##### Parameters:
- __state__: An opaque pointer to a state object.
- __application_context__: A pointer to an null-terminated `km_core_cp`
string representing the current context from the application.
##### Return status:
- `KM_CORE_CONTEXT_STATUS_UNCHANGED`: Cached context change was not needed
- `KM_CORE_CONTEXT_STATUS_UPDATED`: Cached context was set to application
context
- `KM_CORE_CONTEXT_STATUS_CLEARED`: Application context was invalid, perhaps
had unpaired surrogates, and so cached context was cleared instead
- `KM_CORE_CONTEXT_STATUS_ERROR`: Internal error
- `KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT`: One or more parameters was null

```c
*/

KMN_API
km_core_context_status
km_core_state_context_set_if_needed(
km_core_state *state,
km_core_cp const *application_context
);

/*
```
### `km_core_state_context_clear`
##### Description:
Clears the internal cached context for the state. This is the same as
`km_core_context_clear(km_core_state_context(&state))`.

`km_core_state_context_set_if_needed` and `km_core_state_context_clear`
will replace most uses of the existing Core context APIs.

##### Parameters:
- __state__: An opaque pointer to a state object.
##### Return status:
- `KM_CORE_STATUS_OK`: On success.
- `KM_CORE_STATUS_INVALID_ARGUMENT`: If any parameters are null.

```c
*/
KMN_API
km_core_status
km_core_state_context_clear(
km_core_state *state
);

/*
```
Expand Down
138 changes: 138 additions & 0 deletions core/src/action.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
Copyright: © 2023 SIL International.
Description: Implementation of the action API functions using internal
data structures and functions.
Create Date: 23 Oct 2023
Authors: Marc Durdin (MCD)
History: 23 Oct 2023 - MCD - Initial implementation from #9720
*/
#include <cassert>
#include <algorithm>
#include <sstream>
#include <memory>

#include <keyman/keyman_core_api.h>

#include "action.hpp"
#include "state.hpp"
#include "option.hpp"

km_core_actions * km::core::action_item_list_to_actions_object(
km_core_action_item const *action_items
) {
assert(action_items != nullptr);
if(action_items == nullptr) {
return nullptr;
}

km_core_status status = KM_CORE_STATUS_OK;

std::unique_ptr<km_core_actions> actions(new km_core_actions);

// Set actions default values
std::vector<km_core_context_item> output;
std::vector<km_core_option_item> options;
actions->code_points_to_delete = 0;
actions->do_alert = KM_CORE_FALSE;
actions->emit_keystroke = KM_CORE_FALSE;
actions->new_caps_lock_state = KM_CORE_CAPS_UNCHANGED;

// Clear output pointers, will be set later once we have sizes
actions->output = nullptr;
actions->persist_options = nullptr;

for (; action_items->type != KM_CORE_IT_END; ++action_items) {
assert(action_items->type < KM_CORE_IT_MAX_TYPE_ID);

switch(action_items->type) {
case KM_CORE_IT_ALERT:
actions->do_alert = KM_CORE_TRUE;
break;
case KM_CORE_IT_BACK:
switch(action_items->backspace.expected_type) {
case KM_CORE_BT_UNKNOWN:
// this is equivalent to emit_keystroke, because the only time we
// are allowed to do an unknown bksp is when a bksp is passed in
actions->emit_keystroke = KM_CORE_TRUE;
break;
case KM_CORE_BT_CHAR:
if(output.empty()) {
actions->code_points_to_delete++;
} else {
auto last_context_item = output.back();
output.pop_back();
assert(last_context_item.type == KM_CORE_CT_CHAR);
assert(last_context_item.character == action_items->backspace.expected_value);
}
break;
case KM_CORE_BT_MARKER:
if(output.empty()) {
// deleting a marker has no effect on the application
} else {
auto last_context_item = output.back();
output.pop_back();
assert(last_context_item.type == KM_CORE_CT_MARKER);
assert(last_context_item.marker == action_items->backspace.expected_value);
}
break;
default:
assert(false);
}
break;
case KM_CORE_IT_CAPSLOCK:
actions->new_caps_lock_state = action_items->capsLock ? KM_CORE_CAPS_ON : KM_CORE_CAPS_OFF;
break;
case KM_CORE_IT_CHAR:
output.push_back({KM_CORE_CT_CHAR,{0},{action_items->character}});
break;
case KM_CORE_IT_EMIT_KEYSTROKE:
actions->emit_keystroke = KM_CORE_TRUE;
break;
case KM_CORE_IT_INVALIDATE_CONTEXT:
// no-op
break;
case KM_CORE_IT_MARKER:
output.push_back({KM_CORE_CT_MARKER,{0},{action_items->marker}});
break;
case KM_CORE_IT_PERSIST_OPT:
// TODO: lowpri: replace existing item if already present in options vector?
options.push_back(km::core::option(
static_cast<km_core_option_scope>(action_items->option->scope),
action_items->option->key,
action_items->option->value
));
break;
default:
assert(false);
}
}


// Strip the markers from the output, and convert to an string of UTF-32

output.push_back(KM_CORE_CONTEXT_ITEM_END);

size_t buf_size;

mcdurdin marked this conversation as resolved.
Show resolved Hide resolved
if((status = km_core_context_items_to_utf32(output.data(), nullptr, &buf_size)) != KM_CORE_STATUS_OK) {
return nullptr;
}

std::unique_ptr<km_core_usv[]> output_usv(new km_core_usv[buf_size]);

if((status = km_core_context_items_to_utf32(output.data(), output_usv.get(), &buf_size)) != KM_CORE_STATUS_OK) {
return nullptr;
}

actions->output = output_usv.release();

// Create an array of the persisted options

options.push_back(KM_CORE_OPTIONS_END);
actions->persist_options = new km_core_option_item[options.size()];
std::copy(options.begin(), options.end(), actions->persist_options);

// We now have a complete set of actions

return actions.release();
}
20 changes: 20 additions & 0 deletions core/src/action.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Copyright: © 2023 SIL International.
Description: Internal actions methods for Keyman Core
Create Date: 23 Oct 2023
Authors: Marc Durdin (MCD)
History: 23 Oct 2023 - MCD - Initial implementation
*/

#pragma once

#include <keyman/keyman_core_api.h>

namespace km {
namespace core
{
km_core_actions* action_item_list_to_actions_object(
km_core_action_item const *action_items
);
} // namespace core
} // namespace km
Loading
Loading