Merge pull request #10472 from keymanapp/feat/developer/10416-10458-ldml-debugger-markers

feat(developer): LDML keyboard test window marker support 🌱
This commit is contained in:
Marc Durdin 2024-01-26 15:31:01 +11:00 committed by GitHub
commit fa91bdcd85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 347 additions and 437 deletions

View file

@ -92,7 +92,7 @@ km_core_state_get_intermediate_context(km_core_state *state, km_core_context_ite
##### Description:
Free the allocated memory belonging to a `km_core_context_item` array previously
returned by `km_core_state_get_intermediate_context` (internally, also
`context_items_from_utf16` and `context_get`)
`context_items_from_utf16` and `km_core_context_get`)
##### Parameters:
- __context_items__: A pointer to the start of the `km_core_context_item` array
to be disposed of.
@ -182,6 +182,41 @@ KMN_API
size_t
km_core_context_item_list_size(km_core_context_item const *context_items);
/**
*
* Copies all items in the context into a new array and returns the new array.
* This must be disposed of by caller using `km_core_context_items_dispose`.
*
* @return km_core_status
* * `KM_CORE_STATUS_OK`: On success.
* * `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are
* null.
* * `KM_CORE_STATUS_NO_MEM`: In the event not enough memory can be
* allocated for the output buffer.
*
* @param context A pointer to an opaque context object
* @param out A pointer to the result variable: A pointer to the start
* of the `km_core_context_item` array containing a copy of
* the context. Terminated with a type of `KM_CORE_CT_END`.
* Must be disposed of with
* `km_core_context_items_dispose`.
*/
KMN_API
km_core_status
km_core_context_get(km_core_context const *context,
km_core_context_item **out);
/**
* Return the number of items in the context.
*
* @return size_t The number of items in the context, and will return 0 if
* passed a null `context` pointer.
* @param context A pointer to an opaque context object
*/
KMN_API
size_t
km_core_context_length(km_core_context *);
#if defined(__cplusplus)
} // extern "C"
#endif

View file

@ -228,7 +228,7 @@ icu::UnicodeString context_items_to_unicode_string(km_core_context const *contex
km_core_context_item *items = nullptr;
km_core_status status;
if((status = context_get(context, &items)) != KM_CORE_STATUS_OK) {
if((status = km_core_context_get(context, &items)) != KM_CORE_STATUS_OK) {
DebugLog("Failed to retrieve context with %s", status);
return nullString;
}
@ -297,8 +297,8 @@ bool km::core::actions_update_app_context_nfu(
km_core_status status = KM_CORE_STATUS_OK;
km_core_context_item *items = nullptr;
if((status = context_get(cached_context, &items)) != KM_CORE_STATUS_OK) {
DebugLog("context_get failed with %d", status);
if((status = km_core_context_get(cached_context, &items)) != KM_CORE_STATUS_OK) {
DebugLog("km_core_context_get failed with %d", status);
return false;
}

View file

@ -169,38 +169,6 @@ km_core_status
context_items_to_utf32(km_core_context_item const *item,
km_core_usv *buf,
size_t *buf_size);
/**
*
* Copies all items in the context into a new array and returns the new array.
* This must be disposed of by caller using `km_core_context_items_dispose`.
*
* @return km_core_status
* * `KM_CORE_STATUS_OK`: On success.
* * `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are
* null.
* * `KM_CORE_STATUS_NO_MEM`: In the event not enough memory can be
* allocated for the output buffer.
*
* @param context A pointer to an opaque context object
* @param out A pointer to the result variable: A pointer to the start
* of the `km_core_context_item` array containing a copy of
* the context. Terminated with a type of `KM_CORE_CT_END`.
* Must be disposed of with
* `km_core_context_items_dispose`.
*/
km_core_status
context_get(km_core_context const *context,
km_core_context_item **out);
/**
* Return the number of items in the context.
*
* @return size_t The number of items in the context, and will return 0 if
* passed a null `context` pointer.
* @param context A pointer to an opaque context object
*/
size_t
context_length(km_core_context *);
/**
* Add more items to the end (insertion point) of the context. If these exceed

View file

@ -24,7 +24,7 @@ km_core_cp* km::core::get_context_as_string(km_core_context *context) {
size_t buf_size = 0;
km_core_context_item* context_items = nullptr;
if(context_get(context, &context_items) != KM_CORE_STATUS_OK) {
if(km_core_context_get(context, &context_items) != KM_CORE_STATUS_OK) {
return nullptr;
}

View file

@ -153,7 +153,7 @@ km_core_status km_core_context_set(km_core_context *ctxt, km_core_context_item c
}
km_core_status context_get(km_core_context const *ctxt,
km_core_status km_core_context_get(km_core_context const *ctxt,
km_core_context_item **out_ptr)
{
assert(ctxt); assert(out_ptr);
@ -184,7 +184,7 @@ void km_core_context_clear(km_core_context *ctxt)
}
size_t context_length(km_core_context *ctxt)
size_t km_core_context_length(km_core_context *ctxt)
{
assert(ctxt);
return ctxt ? ctxt->size() : 0;

View file

@ -310,11 +310,11 @@ km_core_cp * km_core_state_context_debug(
return _new_error_string(u"<error retrieving intermediate context>");
}
} else if(context_type == KM_CORE_DEBUG_CONTEXT_CACHED) {
if(context_get(km_core_state_context(state), &context_items) != KM_CORE_STATUS_OK) {
if(km_core_context_get(km_core_state_context(state), &context_items) != KM_CORE_STATUS_OK) {
return _new_error_string(u"<error retrieving cached context>");
}
} else if(context_type == KM_CORE_DEBUG_CONTEXT_APP) {
if(context_get(km_core_state_app_context(state), &context_items) != KM_CORE_STATUS_OK) {
if(km_core_context_get(km_core_state_app_context(state), &context_items) != KM_CORE_STATUS_OK) {
return _new_error_string(u"<error retrieving app context>");
}
} else {

View file

@ -218,6 +218,16 @@ int main(int argc, char *argv []) {
}
console_color::enabled = console_color::isaterminal() || arg_color;
km_core_actions act = {0};
std::cout << "sizeof(km_core_actions): " << sizeof(km_core_actions) << std::endl;
std::cout << "&km_core_actions.code_points_to_delete: " << ((intptr_t)(&act.code_points_to_delete)-(intptr_t)(&act)) << std::endl;
std::cout << "&km_core_actions.output: " << ((intptr_t)(&act.output)-(intptr_t)(&act)) << std::endl;
std::cout << "&km_core_actions.persist_options: " << ((intptr_t)(&act.persist_options)-(intptr_t)(&act)) << std::endl;
std::cout << "&km_core_actions.do_alert: " << ((intptr_t)(&act.do_alert)-(intptr_t)(&act)) << std::endl;
std::cout << "&km_core_actions.emit_keystroke: " << ((intptr_t)(&act.emit_keystroke)-(intptr_t)(&act)) << std::endl;
std::cout << "&km_core_actions.new_caps_lock_state: " << ((intptr_t)(&act.new_caps_lock_state)-(intptr_t)(&act)) << std::endl;
// actions
test_two_backspaces();
test_marker_text_interleaved();

View file

@ -79,19 +79,19 @@ int main(int, char * [])
km_core_context_items_dispose(ctxt2);
// Test lengths, these are Unicode Scalar Values, not utf16 codeunits.
if(context_length(&mock_ctxt1) != bmp_ctxt_size) return __LINE__;
if(context_length(&mock_ctxt2) != smp_ctxt_size) return __LINE__;
if(km_core_context_length(&mock_ctxt1) != bmp_ctxt_size) return __LINE__;
if(km_core_context_length(&mock_ctxt2) != smp_ctxt_size) return __LINE__;
// retrieve bmp context and check it's okay.
km_core_context_item *tmp_ctxt;
try_status(context_get(&mock_ctxt1, &tmp_ctxt));
try_status(km_core_context_get(&mock_ctxt1, &tmp_ctxt));
ctxt_size=sizeof ctxt_buffer/sizeof(km_core_cp);
try_status(context_items_to_utf16(tmp_ctxt, ctxt_buffer, &ctxt_size));
km_core_context_items_dispose(tmp_ctxt);
if (initial_bmp_context != ctxt_buffer) return __LINE__;
// retrieve smp context and check it's okay.
try_status(context_get(&mock_ctxt2, &tmp_ctxt));
try_status(km_core_context_get(&mock_ctxt2, &tmp_ctxt));
ctxt_size=sizeof ctxt_buffer/sizeof(km_core_cp);
try_status(context_items_to_utf16(tmp_ctxt, ctxt_buffer, &ctxt_size));
km_core_context_items_dispose(tmp_ctxt);
@ -99,7 +99,7 @@ int main(int, char * [])
// Call km_core_context_clear
km_core_context_clear(&mock_ctxt2);
if(context_length(&mock_ctxt2) != 0) return __LINE__;
if(km_core_context_length(&mock_ctxt2) != 0) return __LINE__;
// Mutation tests
try_status(context_shrink(&mock_ctxt1, 42, nullptr));
@ -117,7 +117,7 @@ int main(int, char * [])
// Check it matches. The marker will be elided during the conversion.
ctxt_size=sizeof ctxt_buffer/sizeof(km_core_cp);
try_status(context_get(&mock_ctxt1, &tmp_ctxt));
try_status(km_core_context_get(&mock_ctxt1, &tmp_ctxt));
try_status(context_items_to_utf16(tmp_ctxt, ctxt_buffer, &ctxt_size));
if (std::u16string(u"Hello World!") != ctxt_buffer) return __LINE__;
km_core_context_items_dispose(tmp_ctxt);
@ -128,7 +128,7 @@ int main(int, char * [])
// expected if you go by the test string above.
try_status(context_shrink(&mock_ctxt1, 8, ctxt1));
ctxt_size=sizeof ctxt_buffer/sizeof(km_core_cp);
try_status(context_get(&mock_ctxt1, &tmp_ctxt));
try_status(km_core_context_get(&mock_ctxt1, &tmp_ctxt));
try_status(context_items_to_utf16(tmp_ctxt, ctxt_buffer, &ctxt_size));
if (std::u16string(u"Bye, Hello") != ctxt_buffer) return __LINE__;

View file

@ -133,9 +133,9 @@ int main(int argc, char * argv[])
try_status(context_items_from_utf16(u"Hello 😁", &citems));
try_status(km_core_context_set(km_core_state_context(test_state), citems));
km_core_context_items_dispose(citems);
if(context_length(km_core_state_context(test_state)) != 7)
if(km_core_context_length(km_core_state_context(test_state)) != 7)
return __LINE__;
if(context_length(km_core_state_context(test_clone)) != 0)
if(km_core_context_length(km_core_state_context(test_clone)) != 0)
return __LINE__;
// Overwrite some data.

View file

@ -51,7 +51,7 @@ setup(const char *keyboard, const km_core_cp *context) {
bool
is_identical_context(km_core_cp const *cached_context) {
size_t buf_size;
try_status(context_get(km_core_state_context(test_state), &citems));
try_status(km_core_context_get(km_core_state_context(test_state), &citems));
try_status(context_items_to_utf16(citems, nullptr, &buf_size));
km_core_cp *new_cached_context = new km_core_cp[buf_size];
try_status(context_items_to_utf16(citems, new_cached_context, &buf_size));
@ -145,7 +145,7 @@ test_context_set_if_needed_cached_context_has_markers() {
km_core_context_item *citems_new;
try_status(context_get(km_core_state_context(test_state), &citems_new));
try_status(km_core_context_get(km_core_state_context(test_state), &citems_new));
for (int i = 0; citems[i].type || citems_new[i].type; i++) {
assert(citems_new[i].type == citems[i].type);

View file

@ -244,7 +244,7 @@ run_test(const km::core::path &source, const km::core::path &compiled) {
// Compare context and text store at each step
// should be identical unless an action has caused the context to be invalidated
size_t n = 0;
try_status(context_get(km_core_state_context(test_state), &citems));
try_status(km_core_context_get(km_core_state_context(test_state), &citems));
try_status(context_items_to_utf16(citems, nullptr, &n));
km_core_cp *core_context_str = new km_core_cp[n];
try_status(context_items_to_utf16(citems, core_context_str, &n));
@ -271,7 +271,7 @@ run_test(const km::core::path &source, const km::core::path &compiled) {
// Compare final output - retrieve internal context
size_t n = 0;
try_status(context_get(km_core_state_context(test_state), &citems));
try_status(km_core_context_get(km_core_state_context(test_state), &citems));
try_status(context_items_to_utf16(citems, nullptr, &n));
km_core_cp *core_context_str = new km_core_cp[n];
try_status(context_items_to_utf16(citems, core_context_str, &n));

View file

@ -199,7 +199,7 @@ verify_context(std::u16string& text_store, km_core_state* &test_state, std::vect
// Compare context and text store at each step - should be identical
size_t n = 0;
km_core_context_item* citems = nullptr;
try_status(context_get(km_core_state_context(test_state), &citems));
try_status(km_core_context_get(km_core_state_context(test_state), &citems));
try_status(context_items_to_utf16(citems, nullptr, &n));
km_core_cp *buf = new km_core_cp[n];
try_status(context_items_to_utf16(citems, buf, &n));

View file

@ -63,9 +63,9 @@ bool is_identical_context(km_core_cp const *cached_context, km_core_debug_contex
debug_context(context_type);
if(context_type == KM_CORE_DEBUG_CONTEXT_APP) {
try_status(context_get(km_core_state_app_context(test_state), &citems));
try_status(km_core_context_get(km_core_state_app_context(test_state), &citems));
} else {
try_status(context_get(km_core_state_context(test_state), &citems));
try_status(km_core_context_get(km_core_state_context(test_state), &citems));
}
try_status(context_items_to_utf16(citems, nullptr, &buf_size));
km_core_cp* new_cached_context = new km_core_cp[buf_size];

View file

@ -37,7 +37,6 @@ inherited frmLdmlKeyboardDebug: TfrmLdmlKeyboardDebug
OnChange = memoChange
OnClick = memoClick
OnEnter = memoGotFocus
OnExit = memoLostFocus
OnKeyUp = memoKeyUp
OnMessage = memoMessage
IsDebugging = False

View file

@ -32,8 +32,6 @@ uses
PaintPanel,
KeymanDeveloperDebuggerMemo,
Keyman.System.Debug.DebugCore,
Keyman.System.Debug.DebugEvent,
Keyman.System.Debug.DebugUIStatus,
Keyman.System.Debug.DebugUtils,
Keyman.System.KeymanCore,
Keyman.System.KeymanCoreDebug,
@ -42,6 +40,13 @@ uses
UfrmTike,
UserMessages;
type
TLdmlDebugUIStatus = (
duiStarting,
duiTest,
duiClosing
);
type
TfrmLdmlKeyboardDebug = class(TTikeForm)
memo: TKeymanDeveloperDebuggerMemo;
@ -56,7 +61,6 @@ type
mnuPaste: TMenuItem; // I4808
procedure FormCreate(Sender: TObject);
procedure memoGotFocus(Sender: TObject);
procedure memoLostFocus(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure memoChange(Sender: TObject);
@ -70,27 +74,15 @@ type
procedure memoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormResize(Sender: TObject);
private
FDebugVisible: Boolean;
FRunning: Boolean;
FFileName: string;
debugkeyboard: TDebugKeyboard;
_FCurrentEvent: Integer;
FDefaultFont: Boolean;
FEvents: TDebugEventList;
FUIStatus: TDebugUIStatus;
FUIStatus: TLdmlDebugUIStatus;
FUIDisabled: Boolean;
{ Deadkey member variables }
FSelectedDeadkey: TDeadKeyInfo;
FSavedSelection: TMemoSelection;
procedure ResetEvents;
procedure ExecuteEvent(n: Integer);
procedure ExecuteEventAction(n: Integer);
procedure SetUIStatus(const Value: TDebugUIStatus);
procedure DisableUI;
procedure EnableUI;
function GetStatusText: string;
procedure SetStatusText(Value: string);
@ -102,7 +94,6 @@ type
FDebugFileName: WideString;
procedure ClearDeadkeys;
procedure ClearDeadkeyStyle;
function GetCurrentEvent: TDebugEvent;
procedure UninitDeadkeys;
procedure UpdateDeadkeyDisplay;
procedure UpdateDeadkeys;
@ -114,8 +105,7 @@ type
procedure CleanupCoreState;
function SetKeyEventContext: Boolean;
function HandleMemoKeydown(var Message: TMessage): Boolean;
procedure SetCurrentEvent(Value: Integer);
procedure Run;
procedure Run(vkey: Word);
protected
function GetHelpTopic: string; override;
@ -132,7 +122,7 @@ type
function GetDebugKeyboard: TDebugKeyboard;
property UIStatus: TDebugUIStatus read FUIStatus write SetUIStatus;
property UIStatus: TLdmlDebugUIStatus read FUIStatus write FUIStatus;
property DebugFileName: WideString read FDebugFileName write FDebugFileName;
property CompiledFileName: string read FFileName write FFileName; // I4695
@ -141,11 +131,7 @@ type
procedure ShowDebugForm;
procedure HideDebugForm;
property DebugVisible: Boolean read FDebugVisible;
function ShortcutDisabled(Key: Word): Boolean;
property CurrentEvent: TDebugEvent read GetCurrentEvent;
end;
implementation
@ -153,20 +139,18 @@ implementation
{$R *.DFM}
uses
System.Character,
System.Math,
Keyman.Developer.System.HelpTopics,
ActiveX,
dmActionsDebugger,
dmActionsMain,
Glossary,
Keyman.Developer.System.Project.ProjectLog,
Keyman.Developer.UI.Project.ProjectUI,
Keyman.Developer.UI.UfrmLdmlKeyboardEditor,
Keyman.UI.Debug.CharacterGridRenderer,
KeyNames,
kmxfile,
kmxfileconsts,
ErrorControlledRegistry,
RegistryKeys,
KeymanDeveloperOptions,
KeymanDeveloperUtils,
ScanCodeMap,
@ -174,13 +158,9 @@ uses
UfrmEditor,
UfrmMain,
UfrmMessages,
UfrmRegressionTestFailure,
UfrmSelectSystemKeyboard,
Unicode,
utilstr,
UTikeDebugMode,
VKeys,
XString;
utilstr;
const
// WM_KEYDOWN bits; KEYFLAG_KEYMAN is a reserved value
@ -197,18 +177,19 @@ begin
FDeadkeys := TDebugDeadkeyInfoList.Create;
FUIStatus := duiInvalid;
FEvents := TDebugEventList.Create;
FUIStatus := duiStarting;
FDefaultFont := True;
memo.Align := alClient;
memo.ReadOnly := False;
memo.IsDebugging := True;
StatusText := 'Simple Test';
UIStatus := duiReadyForInput;
UIStatus := duiTest;
end;
procedure TfrmLdmlKeyboardDebug.FormDestroy(Sender: TObject);
begin
ResetDebug;
FreeAndNil(FEvents);
UninitDeadkeys;
end;
@ -224,9 +205,6 @@ end;
procedure TfrmLdmlKeyboardDebug.memoGotFocus(Sender: TObject);
begin
if UIStatus = duiReadyForInput then
UIStatus := duiFocusedForInput;
memoSelMove(memo);
end;
@ -236,30 +214,15 @@ begin
memoSelMove(memo);
end;
procedure TfrmLdmlKeyboardDebug.memoLostFocus(Sender: TObject);
begin
if UIStatus = duiFocusedForInput then
UIStatus := duiReadyForInput;
end;
function TfrmLdmlKeyboardDebug.HandleMemoKeydown(var Message: TMessage): Boolean;
begin
if (Message.wParam = VK_ESCAPE) and
(GetKeyState(VK_SHIFT) < 0) and
(UIStatus <> duiPaused) then // I4033
UIStatus := duiPaused
else if (Message.wParam = VK_F6) and
if (Message.wParam = VK_F6) and
(GetKeyState(VK_CONTROL) >= 0) and
(GetKeyState(VK_MENU) >= 0) and
(UIStatus <> duiFocusedForInput) then
(GetKeyState(VK_MENU) >= 0) then
begin
EditorMemo.SetFocus;
end
else if (Message.wParam = VK_ESCAPE) and
(GetKeyState(VK_SHIFT) < 0) and
(UIStatus = duiPaused) then
UIStatus := duiFocusedForInput
else if UIStatus in [duiTest, duiFocusedForInput] then
else if UIStatus = duiTest then
begin
Exit(ProcessKeyEvent(Message));
end
@ -274,9 +237,9 @@ procedure TfrmLdmlKeyboardDebug.memoMessage(Sender: TObject; var Message: TMessa
begin
Handled := False;
if UIStatus = duiClosing then
if UIStatus <> duiTest then
begin
Exit; // Don't process while destroying...
Exit;
end;
case Message.Msg of
@ -384,63 +347,35 @@ begin
Exit(True);
end;
FEvents.Clear;
FEvents.AddLdmlStateItems(FDebugCore.State, vkey, modifier, debugkeyboard);
Run(vkey);
FRunning := True;
SetCurrentEvent(0);
Run;
UpdateDeadkeys;
end
else
begin
FEvents.Clear;
Result := False;
end;
end;
procedure TfrmLdmlKeyboardDebug.SetCurrentEvent(Value: Integer);
function UsvToString(p: pkm_core_usv): string;
begin
_FCurrentEvent := Value;
Result := '';
while p^ <> 0 do
begin
Result := Result + Char.ConvertFromUtf32(p^);
Inc(p);
end;
end;
procedure TfrmLdmlKeyboardDebug.Run;
begin
FRunning := True;
try
while (_FCurrentEvent < FEvents.Count) do
begin
ExecuteEvent(_FCurrentEvent);
SetCurrentEvent(_FCurrentEvent + 1);
if UIStatus = duiPaused then // I4033
Exit;
end;
finally
FRunning := False;
EnableUI;
UpdateCharacterGrid;
// We want to refresh the memo and character grid for rapid typing
memo.Update;
sgChars.Update;
procedure TfrmLdmlKeyboardDebug.Run(vkey: Word);
procedure DoAlert;
begin
MessageBeep(0);
end;
if UIStatus <> duiTest then
if memo.Focused
then UIStatus := duiFocusedForInput
else UIStatus := duiReadyForInput;
end;
{ Stores }
procedure TfrmLdmlKeyboardDebug.ExecuteEvent(n: Integer);
begin
memo.ReadOnly := False;
memo.Selection := FSavedSelection;
ExecuteEventAction(n);
FSavedSelection := memo.Selection;
memo.ReadOnly := True;
end;
procedure TfrmLdmlKeyboardDebug.ExecuteEventAction(n: Integer);
type
TMemoSelectionState = record
Selection: TMemoSelection;
@ -481,126 +416,6 @@ procedure TfrmLdmlKeyboardDebug.ExecuteEventAction(n: Integer);
UpdateDeadkeys;
end;
procedure DoBackspace(BackspaceType: km_core_backspace_type);
var
m, n: Integer;
dk: TDeadKeyInfo;
state: TMemoSelectionState;
begin
// Offset is zero-based, but string is 1-based. Beware!
state := SaveMemoSelectionState;
n := memo.SelStart;
m := n;
if memo.SelLength > 0 then
begin
// If the memo has a selection, we have given Core an empty context,
// which forces it to emit a KM_CORE_BT_UNKNOWN backspace, which is
// exactly what we want here. We just delete the selection
Assert(BackspaceType = KM_CORE_BT_UNKNOWN);
memo.SelText := '';
RealignMemoSelectionState(state);
Exit;
end;
case BackspaceType of
KM_CORE_BT_MARKER:
begin
Assert(m >= 1);
Assert(memo.Text[m] = #$FFFC);
dk := FDeadkeys.GetFromPosition(m-1);
Assert(Assigned(dk));
dk.Delete;
Dec(m);
end;
KM_CORE_BT_CHAR:
begin
Assert(m >= 1);
Assert(memo.Text[m] <> #$FFFC);
// Delete surrogate pairs
if (m > 1) and
Uni_IsSurrogate2(memo.Text[m]) and
Uni_IsSurrogate1(memo.Text[m-1]) then
Dec(m, 2)
// Delete \r\n line breaks
else if (m > 1) and
(memo.Text[m] = #$0A) and
(memo.Text[m-1] = #$0D) then
Dec(m, 2)
else
Dec(m);
end;
KM_CORE_BT_UNKNOWN:
begin
while (m >= 1) and (memo.Text[m] = #$FFFC) do
begin
dk := FDeadkeys.GetFromPosition(m-1);
Assert(Assigned(dk));
dk.Delete;
Dec(m);
end;
// Delete character
if (m > 1) and
Uni_IsSurrogate2(memo.Text[m]) and
Uni_IsSurrogate1(memo.Text[m-1]) then
Dec(m, 2)
else
Dec(m);
// Also delete deadkeys to left of current character
while (m >= 1) and (memo.Text[m] = #$FFFC) do
begin
dk := FDeadkeys.GetFromPosition(m-1);
Assert(Assigned(dk));
dk.Delete;
Dec(m);
end;
end;
else
Assert(False, 'Unrecognised backspace type');
end;
memo.Text := Copy(memo.Text, 1, m) + Copy(memo.Text, n+1, MaxInt);
memo.SelStart := m;
RealignMemoSelectionState(state);
end;
procedure DoDeadkey(dkCode: Integer);
var
dk: TDeadKeyInfo;
i: Integer;
state: TMemoSelectionState;
begin
dk := TDeadKeyInfo.Create;
dk.Memo := memo;
dk.Deadkey := nil;
for i := 0 to debugkeyboard.Deadkeys.Count - 1 do
if debugkeyboard.Deadkeys[i].Value = dkCode then
begin
dk.Deadkey := debugkeyboard.Deadkeys[i];
Break;
end;
if not Assigned(dk.Deadkey) then
dk.Free //silent failure
else
begin
state := SaveMemoSelectionState;
memo.SelText := WideChar($FFFC);
memo.SelStart := memo.SelStart + memo.SelLength; // I1603
memo.SelLength := 0;
dk.Position := memo.SelStart - 1;
RealignMemoSelectionState(state);
FDeadkeys.Add(dk);
UpdateDeadkeyDisplay;
end;
end;
procedure DoHandleShortcut(vk: UINT);
begin
// Because we disable shortcuts in the debug memo, there are a small set of
@ -610,7 +425,13 @@ procedure TfrmLdmlKeyboardDebug.ExecuteEventAction(n: Integer);
// apart from Ctrl+A
if GetKeyState(VK_CONTROL) >= 0 then
begin
if (vk = VK_RETURN) and (GetKeyState(VK_MENU) >= 0) then
begin
memo.SelText := #13#10;
end;
Exit;
end;
case vk of
Ord('A'): memo.SelectAll;
@ -653,85 +474,187 @@ procedure TfrmLdmlKeyboardDebug.ExecuteEventAction(n: Integer);
end;
end;
procedure DoChar(const text: string);
function ContextToDebugString(context_items: pkm_core_context_item; offset, length: Integer): string;
var
state: TMemoSelectionState;
outText: array of UInt32;
ox: Integer;
pc: pkm_core_context_item;
dk: TDeadKeyInfo;
begin
state := SaveMemoSelectionState;
// Line breaks: replace \r (0x0D) with \r\n (0x0D 0x0A) so line breaks work
memo.SelText := ReplaceStr(Text, #$0D, #$0D#$0A);
memo.SelStart := memo.SelStart + memo.SelLength; // I1603
memo.SelLength := 0;
RealignMemoSelectionState(state);
end;
SetLength(outText, length + 1);
procedure DoBell;
begin
MessageBeep(0);
end;
begin
DisableUI;
with FEvents[n].Action do
begin
case ActionType of
KM_CORE_IT_EMIT_KEYSTROKE: DoEmitKeystroke(dwData);
KM_CORE_IT_CHAR: DoChar(Text);
KM_CORE_IT_MARKER: DoDeadkey(dwData);
KM_CORE_IT_ALERT: DoBell;
KM_CORE_IT_BACK: DoBackspace(km_core_backspace_type(dwData));
KM_CORE_IT_PERSIST_OPT: ; //TODO
KM_CORE_IT_CAPSLOCK: ; //TODO
KM_CORE_IT_INVALIDATE_CONTEXT: ; // no-op
ox := 0;
pc := context_items;
while pc._type <> KM_CORE_CT_END do
begin
if pc._type = KM_CORE_CT_CHAR then
outText[ox] := pc.character
else
begin
dk := TDeadKeyInfo.Create;
dk.Deadkey := TDebugDeadkey.Create;
dk.Deadkey.Name := '\m{'+IntToStr(pc.marker)+'}';
dk.Deadkey.Value := pc.marker;
dk.Position := ox + offset;
dk.SavedPosition := ox + offset;
FDeadkeys.Add(dk);
outText[ox] := $FFFC;
end;
Inc(pc);
Inc(ox);
end;
// AddDEBUG(Format('%d: %d [%s]', [ActionType, dwData, Text]));
end;
EnableUI;
end;
procedure TfrmLdmlKeyboardDebug.DisableUI;
outText[ox] := 0;
Result := USVToString(@outText[0]);
end;
var
actions: pkm_core_actions;
context_items: pkm_core_context_item;
selection: TMemoSelection;
output: string;
lhs, rhs, context: string;
status: km_core_status;
dk: TDeadKeyInfo;
context_items_length: Integer;
state: TMemoSelectionState;
Adjustment: Integer;
begin
FUIDisabled := True;
// UpdateControlCaptions;
end;
procedure TfrmLdmlKeyboardDebug.EnableUI;
begin
if not FRunning then
actions := km_core_state_get_actions(FDebugCore.State);
if actions = nil then
begin
GetGlobalProjectUI.Log(plsError, FDebugFileName,
'Failed to get actions from Keyman Core', 0, 0);
Exit;
end;
context_items := nil;
status := km_core_context_get(km_core_state_context(FDebugCore.State), @context_items);
if status <> KM_CORE_STATUS_OK then
begin
GetGlobalProjectUI.Log(plsError, FDebugFileName,
'Failed to get context from Keyman Core with error '+IntToStr(Ord(Status)), 0, 0);
km_core_actions_dispose(actions);
Exit;
end;
selection := memo.Selection;
// TODO: #10471 deleting U+000D U+000A should delete as a unit
try
if (actions.emit_keystroke = 1) and (vkey <> VK_BACK) and
(actions.code_points_to_delete = 0) and (actions.output^ = 0) then
begin
// If the keystroke is being emitted, then it was not matched by LDML, and
// must be a frame key. We exclude Bksp as a special case which needs
// additional processing. All other frame keys should be passed to the
// text control for default processing.
end
else
begin
if selection.Finish > selection.Start then
begin
// We have a selection, so we should remove it first and then continue
// as normal. We will have given Core a zero-length context, so we
// need to save everything before the start of selection before
// continuing, and cleanup any markers that were in the selection
if (actions.emit_keystroke = 0) or (vkey = VK_BACK) then
begin
state := SaveMemoSelectionState;
memo.SelText := '';
RealignMemoSelectionState(state);
selection := memo.Selection;
end;
lhs := Copy(memo.Text, 1, selection.Start);
end
else
lhs := '';
context := Copy(memo.Text, lhs.Length + 1, selection.Start - lhs.Length);
rhs := Copy(memo.Text, lhs.Length + context.Length + 1, MaxInt);
// Reinsert the context
context_items_length := km_core_context_length(km_core_state_context(FDebugCore.State));
Adjustment := context_items_length - selection.Start + lhs.Length;
// Delete all markers that are in the context, because we will reinsert
// them shortly, and adjust marker positions to right of insertion point
for dk in FDeadkeys do
begin
if (dk.Position > lhs.Length) and (dk.Position < selection.Start) then
begin
dk.Delete;
end
else if dk.Position >= selection.Start then
begin
dk.Position := dk.Position + Adjustment;
end;
end;
// Build the new context string and create new markers
output := ContextToDebugString(context_items, lhs.Length, context_items_length);
// Merge left of context, context, and right of context and update memo
// insertion point position
memo.Text := lhs + output + rhs;
selection.Start := lhs.Length + output.Length;
selection.Finish := selection.Start;
selection.Anchor := selection.Start;
memo.Selection := selection;
end;
// actions.persist_options are not currently supported by LDML
if actions.do_alert <> 0 then
begin
DoAlert;
end;
if actions.emit_keystroke <> 0 then
begin
DoEmitKeystroke(vkey);
end;
finally
km_core_actions_dispose(actions);
km_core_context_items_dispose(context_items);
FUIDisabled := False;
UpdateCharacterGrid;
// We want to refresh the memo and character grid for rapid typing
memo.Update;
sgChars.Update;
end;
end;
{ Stores }
procedure TfrmLdmlKeyboardDebug.CleanupCoreState;
begin
FreeAndNil(FDebugCore);
end;
procedure TfrmLdmlKeyboardDebug.ResetEvents;
begin
if _FCurrentEvent > 0 then
begin
FEvents.Clear;
SetCurrentEvent(0);
end;
end;
procedure TfrmLdmlKeyboardDebug.HideDebugForm;
begin
frmKeymanDeveloper.ShowDebug(False); // I4796
FDebugVisible := False;
ResetDebug;
UIStatus := duiReadyForInput;
end;
procedure TfrmLdmlKeyboardDebug.ShowDebugForm;
begin
UIStatus := duiTest;
frmKeymanDeveloper.ShowDebug(True); // I4796
FDebugVisible := True;
//UpdateFont(nil);
if not SetupDebug then
Exit;
@ -741,8 +664,6 @@ end;
procedure TfrmLdmlKeyboardDebug.ResetDebug;
begin
FreeAndNil(debugkeyboard);
SetCurrentEvent(1);
ResetEvents;
ClearDeadkeys; // I1699
CleanupCoreState;
memo.Text := '';
@ -783,67 +704,6 @@ begin
memo.Font := FFont;
end;
procedure TfrmLdmlKeyboardDebug.SetUIStatus(const Value: TDebugUIStatus);
var
FOldUIStatus: TDebugUIStatus;
begin
if FUIStatus <> Value then
begin
FOldUIStatus := FUIStatus;
FUIStatus := Value;
case Value of
duiTest:
begin
StatusText := 'Simple Test';
memo.ReadOnly := False;
memo.IsDebugging := True;
end;
duiFocusedForInput:
begin
DisableUI;
StatusText := 'Focused for input';
memo.ReadOnly := False;
memo.IsDebugging := True;
end;
duiReadyForInput:
begin
if memo.Focused then UIStatus := duiFocusedForInput
else
begin
EnableUI;
StatusText := 'Ready for input';
memo.ReadOnly := True;
memo.IsDebugging := False;
end;
end;
duiPaused:
begin
EnableUI;
FUIDisabled := False; // I4033
StatusText := 'Paused';
memo.ReadOnly := True;
memo.IsDebugging := False;
end;
end;
if FOldUIStatus = duiTest then
begin
if memo.Focused then
begin
GetParentForm(memo).ActiveControl := nil;
memo.SetFocus;
end;
end
end;
end;
function TfrmLdmlKeyboardDebug.GetCurrentEvent: TDebugEvent;
begin
if (_FCurrentEvent >= 0) and (_FCurrentEvent < FEvents.Count)
then Result := FEvents[_FCurrentEvent]
else Result := nil;
end;
function TfrmLdmlKeyboardDebug.GetDebugKeyboard: TDebugKeyboard;
begin
Result := debugkeyboard;
@ -971,7 +831,6 @@ begin
if not memo.ReadOnly then
begin
FSavedSelection := memo.Selection;
UpdateCharacterGrid; // I4808
end;
end;
@ -981,7 +840,8 @@ begin
if csDestroying in ComponentState then
Exit;
TCharacterGridRenderer.Fill(sgChars, memo.Text, FDeadkeys, memo.SelStart, memo.SelLength, memo.Selection.Anchor);
TCharacterGridRenderer.Fill(sgChars, memo.Text, FDeadkeys, memo.SelStart,
memo.SelLength, memo.Selection.Anchor, True);
TCharacterGridRenderer.Size(sgChars, memo.Font);
end;

View file

@ -83,8 +83,6 @@ procedure TfrmLdmlKeyboardEditor.StartDebugging;
begin
if not IsDebugVisible then
begin
FDebugForm.UIStatus := duiReadyForInput;
(ProjectFileUI as TxmlLdmlProjectFileUI).Debug := True; // I4687
frmMessages.Clear; // I4686

View file

@ -93,9 +93,6 @@ type
vk: uint16_t;
modifier_state: uint16_t
): Boolean; overload;
function AddLdmlStateItems(state: pkm_core_state; vk,
modifier_state: uint16_t; debugkeyboard: TDebugKeyboard): Boolean;
end;
implementation
@ -375,36 +372,6 @@ begin
Assert(action._type = KM_CORE_IT_END);
end;
function TDebugEventList.AddLdmlStateItems(
state: pkm_core_state;
vk: uint16_t;
modifier_state: uint16_t;
debugkeyboard: TDebugKeyboard
): Boolean;
var
action: pkm_core_action_item;
begin
// TODO: use action struct
Result := True;
action := km_core_state_action_items(state, nil);
while (action._type <> KM_CORE_IT_END) do
begin
Result := Result and AddActionItem(vk, action);
Inc(action);
end;
if action._type = KM_CORE_IT_EMIT_KEYSTROKE then
begin
// The EMIT_KEYSTROKE action comes after all rules have completed processing
Result := Result and AddActionItem(vk, action);
Inc(action);
end;
// By the time we get to the end of rule processing, all actions should have
// already been undertaken
Assert(action._type = KM_CORE_IT_END);
end;
{ TDebugEventRuleData }
procedure TDebugEventRuleData.FillStoreList(event: pkm_core_state_debug_item; KeyboardMemory: PChar);

View file

@ -22,7 +22,8 @@ type
class procedure Fill(grid: TStringGrid; const text: string;
deadkeys: TDebugDeadkeyInfoList;
SelStart, SelLength, SelAnchor: Integer); static;
SelStart, SelLength, SelAnchor: Integer;
DeadkeysAreCalledMarkers: Boolean = False); static;
class procedure Render(grid: TStringGrid; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState; CharFont: TFont); static;
class procedure Size(grid: TStringGrid; CharFont: TFont); static;
@ -36,7 +37,8 @@ uses
class procedure TCharacterGridRenderer.Fill(grid: TStringGrid;
const text: string; deadkeys: TDebugDeadkeyInfoList;
SelStart, SelLength, SelAnchor: Integer);
SelStart, SelLength, SelAnchor: Integer;
DeadkeysAreCalledMarkers: Boolean);
type
TCellType = (ctChar, ctDeadkey);
TCell = record
@ -87,7 +89,9 @@ type
if not Assigned(cell.dk)
then grid.Cells[x, 0] := '???'
else grid.Cells[x, 0] := cell.dk.Deadkey.Name;
grid.Cells[x, 1] := 'Deadkey';
if DeadkeysAreCalledMarkers
then grid.Cells[x, 1] := 'Marker'
else grid.Cells[x, 1] := 'Deadkey';
end
else
begin

View file

@ -20,6 +20,7 @@ type
km_core_virtual_key = uint16_t;
km_core_usv = uint32_t; // UTF-32
pkm_core_usv = ^km_core_usv;
km_core_cp = WideChar;
pkm_core_cp = ^km_core_cp;
@ -70,6 +71,7 @@ type
end;
pkm_core_context_item = ^km_core_context_item;
ppkm_core_context_item = ^pkm_core_context_item;
const
KM_CORE_CONTEXT_ITEM_END: km_core_context_item = (
@ -82,7 +84,7 @@ const
keymancore = 'keymancore-1.dll';
procedure km_core_context_items_dispose(
const context_items: km_core_context_item
context_items: pkm_core_context_item
); cdecl; external keymancore delayed;
function km_core_context_set(
@ -90,10 +92,18 @@ function km_core_context_set(
context_items: pkm_core_context_item
): km_core_status; cdecl; external keymancore delayed;
function km_core_context_get(
context: pkm_core_context;
context_items: ppkm_core_context_item
): km_core_status; cdecl; external keymancore delayed;
procedure km_core_context_clear(
context: pkm_core_context
); cdecl; external keymancore delayed;
function km_core_context_length(
context: pkm_core_context
): uint32; cdecl; external keymancore delayed;
type
km_core_option_scope = (
@ -162,6 +172,35 @@ type
pkm_core_action_item = ^km_core_action_item;
km_core_bool = uint32;
km_core_caps_state = (
KM_CORE_CAPS_UNCHANGED = -1,
KM_CORE_CAPS_OFF = 0,
KM_CORE_CAPS_ON = 1
);
km_core_actions = record
code_points_to_delete: uint32; // number of codepoints (not codeunits!) to delete from app context.
// null-term string of characters to insert into document
output: pkm_core_usv;
// list of options to persist, terminated with KM_CORE_OPTIONS_END
persist_options: pkm_core_option_item;
// issue a beep, 0 = no, 1 = yes
do_alert: km_core_bool;
// emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes
emit_keystroke: km_core_bool;
// -1=unchanged, 0=off, 1=on
new_caps_lock_state: km_core_caps_state;
end;
pkm_core_actions = ^km_core_actions;
// These types are used only for debugging convenience
type
km_core_action_item_array = array[0..100] of km_core_action_item;
@ -241,6 +280,14 @@ function km_core_state_action_items(
num_items: pinteger
): pkm_core_action_item; cdecl; external keymancore delayed;
function km_core_state_get_actions(
state: pkm_core_state
): pkm_core_actions; cdecl; external keymancore delayed;
function km_core_actions_dispose(
actions: pkm_core_actions
): km_core_status; cdecl; external keymancore delayed;
function km_core_state_to_json(
state: pkm_core_state;
buf: PAnsiChar;
@ -613,4 +660,24 @@ begin
RaiseLastOSError;
end;
// Verify size of km_core_actions - from x86 core c++ build, test_actions.cpp
procedure VerifyKmCoreActionsSize;
var
act: km_core_actions;
begin
{$IFDEF WIN64}
{$ERROR Struct size not yet verified for 64-bit}
{$ENDIF}
assert(sizeof(km_core_actions) = 24);
// &km_core_actions.code_points_to_delete: 0
assert(Uint32(@act.output) - Uint32(@act) = 4);
assert(Uint32(@act.persist_options) - Uint32(@act) = 8);
assert(Uint32(@act.do_alert) - Uint32(@act) = 12);
assert(Uint32(@act.emit_keystroke) - Uint32(@act) = 16);
assert(Uint32(@act.new_caps_lock_state) - Uint32(@act) = 20);
end;
initialization
VerifyKmCoreActionsSize;
end.

View file

@ -4,8 +4,10 @@ libkeymancore.so.1 libkeymancore #MINVER#
(c++|optional)"typeinfo name for std::codecvt_utf8_utf16<char16_t, 1114111ul, (std::codecvt_mode)0>@Base" 17.0.244
km_core_actions_dispose@Base 17.0.197
km_core_context_clear@Base 17.0.195
km_core_context_get@Base 17.0.195
km_core_context_item_list_size@Base 17.0.195
km_core_context_items_dispose@Base 17.0.195
km_core_context_length@Base 17.0.195
km_core_context_set@Base 17.0.195
km_core_cp_dispose@Base 17.0.244
km_core_event@Base 17.0.195