-
-
Notifications
You must be signed in to change notification settings - Fork 103
/
km_core_state_context_set_if_needed.cpp
282 lines (250 loc) · 9 KB
/
km_core_state_context_set_if_needed.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
Copyright: © 2018-2024 SIL International.
Description: Implementation of the state API functions using internal
data structures and functions.
Create Date: 15 Jan 2024
Authors: Marc Durdin
History: 15 Jan 2024 - MCD - Refactor our km_core_state_context_set_if_needed
and implement normalization
*/
#include <cassert>
#include "keyman_core.h"
#include "processor.hpp"
#include "state.hpp"
#include "debuglog.h"
#include "core_icu.h"
using namespace km::core;
enum context_changed_type {
// new and internal contexts are identical
CONTEXT_UNCHANGED = 0,
// new and internal contexts differ
CONTEXT_DIFFERENT = 1,
// internal context is shorter, but content is same as far as it goes
CONTEXT_SHORTER = 2,
// internal context is longer, but content is same as far as it goes
CONTEXT_LONGER = 3,
};
typedef struct {
enum context_changed_type type;
// the number of characters/context items (from beginning of context)
// that are missing from the new/internal context.
uint32_t end_difference;
} context_change_type;
// Forward declarations
bool replace_context(context_change_type context_changed, km_core_context *context, km_core_cp const *new_context);
bool should_normalize(km_core_state *state);
context_change_type is_context_unchanged(km_core_cp const *new_context, km_core_context *context);
bool do_normalize_nfd(km_core_cp const * src, std::u16string &dst);
km_core_context_status do_fail(km_core_context *app_context, km_core_context *cached_context, const char* error);
// ---------------------------------------------------------------------------
km_core_context_status
km_core_state_context_set_if_needed(
km_core_state *state,
km_core_cp const *new_app_context
) {
assert(state != nullptr);
assert(new_app_context != nullptr);
if (state == nullptr || new_app_context == nullptr) {
return KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT;
}
auto app_context = km_core_state_app_context(state);
auto cached_context = km_core_state_context(state);
// Compare the internal app context with the passed-in application context
auto context_changed = is_context_unchanged(new_app_context, app_context);
if (context_changed.type != CONTEXT_UNCHANGED) {
// We replace the internal app context with the passed-in application context
if (!replace_context(context_changed, app_context, new_app_context)) {
switch (context_changed.type) {
case CONTEXT_DIFFERENT:
return do_fail(app_context, cached_context, "could not set new app context");
case CONTEXT_SHORTER:
return do_fail(app_context, cached_context, "could not prepend new app context");
case CONTEXT_LONGER:
return do_fail(app_context, cached_context, "could not shrink app context");
case CONTEXT_UNCHANGED:
break; // can't happen, but makes compiler happy
}
}
}
// Finally, we normalize and replace the cached context
std::u16string normalized_buffer;
km_core_cp const *new_cached_context = nullptr;
if (should_normalize(state)) {
if (!do_normalize_nfd(new_app_context, normalized_buffer)) {
return do_fail(app_context, cached_context, "could not normalize string");
}
new_cached_context = normalized_buffer.c_str();
} else {
new_cached_context = new_app_context;
}
context_changed = is_context_unchanged(new_cached_context, cached_context);
if (context_changed.type == CONTEXT_UNCHANGED) {
// We keep the context as is
return KM_CORE_CONTEXT_STATUS_UNCHANGED;
}
if (!replace_context(context_changed, cached_context, new_cached_context)) {
switch (context_changed.type) {
case CONTEXT_DIFFERENT:
return do_fail(app_context, cached_context, "could not set new cached context");
case CONTEXT_SHORTER:
return do_fail(app_context, cached_context, "could not prepend new cached context");
case CONTEXT_LONGER:
return do_fail(app_context, cached_context, "could not shrink cached context");
case CONTEXT_UNCHANGED:
break; // can't happen, but makes compiler happy
}
}
return KM_CORE_CONTEXT_STATUS_UPDATED;
}
bool
replace_context(
context_change_type context_changed,
km_core_context *context,
km_core_cp const *new_context
) {
if (context_changed.type == CONTEXT_DIFFERENT) {
if (set_context_from_string(context, new_context) != KM_CORE_STATUS_OK) {
return false;
}
} else if (context_changed.type == CONTEXT_SHORTER) {
km_core_context_item *new_context_items = nullptr;
if (context_items_from_utf16(new_context, &new_context_items) != KM_CORE_STATUS_OK ||
context_prepend(context, new_context_items, context_changed.end_difference) != KM_CORE_STATUS_OK) {
km_core_context_items_dispose(new_context_items);
return false;
}
} else {
assert(context_changed.type == CONTEXT_LONGER);
if (context_shrink(context, context_changed.end_difference, false) != KM_CORE_STATUS_OK) {
return false;
}
}
return true;
}
/**
* Returns true if the current keyboard processor wants a normalized cached context
*/
bool should_normalize(km_core_state *state) {
return state->processor().supports_normalization();
}
/**
* Returns the previous context item that is a character skipping any
* markers, or NULL if there are no more characters before to
* `context_items`.
*/
km_core_context_item const *
context_previous_char(
km_core_context_item const *context_items,
km_core_context_item const *context_start
) {
if (context_items == context_start) {
return NULL;
}
while (context_items > context_start) {
context_items--;
if (context_items->type == KM_CORE_CT_CHAR) {
return context_items;
}
// skip over marker
}
return NULL;
}
/**
* Returns true if the internal app context does not need to be updated to the new
* app context
*/
context_change_type
is_context_unchanged(
km_core_cp const *new_context_string,
km_core_context *context
) {
context_change_type change_type({CONTEXT_DIFFERENT, 0});
assert(new_context_string != nullptr);
assert(context != nullptr);
if (new_context_string == nullptr || context == nullptr) {
return change_type;
}
std::unique_ptr<km_core_cp[]> context_string(get_context_as_string(context));
if (context_string[0] == '\0') {
// If the app_context is "empty" then it needs updating
return change_type;
}
km_core_context_item *context_items;
if (km_core_context_get(context, &context_items) != KM_CORE_STATUS_OK) {
return change_type;
}
// move to the end of the new and internal app context
km_core_cp const *new_context_p = new_context_string;
while (*new_context_p) {
new_context_p++;
}
km_core_context_item const *context_p = context_items;
while (context_p->type != KM_CORE_CT_END) {
context_p++;
}
// we need to compare from the end of the cached context
for (; new_context_p >= new_context_string && context_p != NULL;
new_context_p--, context_p = context_previous_char(context_p, context_items)) {
if (*new_context_p != context_p->character && context_p->type != KM_CORE_CT_END) {
// The cached context doesn't match the application context
km_core_context_items_dispose(context_items);
return change_type;
}
}
if (context_p == NULL && new_context_p < new_context_string) {
// new and internal app contexts are identical
change_type.type = CONTEXT_UNCHANGED;
km_core_context_items_dispose(context_items);
return change_type;
}
if (context_p >= context_items) {
uint8_t n = 1;
for (; context_p > context_items; context_p--) {
n++;
}
change_type.type = CONTEXT_LONGER;
change_type.end_difference = n;
km_core_context_items_dispose(context_items);
return change_type;
}
uint8_t n = 1;
for (; new_context_p > new_context_string; new_context_p--) {
n++;
}
change_type.type = CONTEXT_SHORTER;
change_type.end_difference = n;
km_core_context_items_dispose(context_items);
return change_type;
}
/**
* Normalize the input string using ICU
*/
bool do_normalize_nfd(km_core_cp const * src, std::u16string &dst) {
UErrorCode icu_status = U_ZERO_ERROR;
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(icu_status);
assert(U_SUCCESS(icu_status));
if(!U_SUCCESS(icu_status)) {
// TODO: log the failure code
return false;
}
icu::UnicodeString udst;
icu::UnicodeString usrc = icu::UnicodeString(src);
nfd->normalize(usrc, udst, icu_status);
assert(U_SUCCESS(icu_status));
if(!U_SUCCESS(icu_status)) {
// TODO: log the failure code
return false;
}
dst.assign(udst.getBuffer(), udst.length());
return true;
}
/**
* Clear the context when we have a failure so we don't end up with inconsistent
* context buffers, and log the error to our diagnostic log.
*/
km_core_context_status do_fail(km_core_context *app_context, km_core_context *cached_context, const char* error) {
DebugLog("%s", error);
km_core_context_clear(app_context);
km_core_context_clear(cached_context);
return KM_CORE_CONTEXT_STATUS_CLEARED;
}