Skip to content

Conversation

@sgschantz
Copy link
Contributor

@sgschantz sgschantz commented Feb 4, 2026

Introduces an alternative to approach to backspace for compliant apps by using the insertText API to
replace a character to be deleted and the preceding character from the context with only the character from the context

Fixes: #15543

Build-bot: release:mac

User Testing

Preparation: install the keyboard bksp_ldml.zip
All tests should be using compliant apps such as Apple's Pages, TextEdit or Stickies.

  • TEST_BKSP_D_ACUTE_VERIFY_CONTEXT: In a compliant app, with the BKSP keyboard selected, type:
    abc     d/
    Select the text, copy and paste it into a character viewer and verify that the last two characters are U+0064 U+0301.
  • TEST_BKSP_D_ACUTE: In a compliant app, with the BKSP keyboard selected, type:
    abc     d/
    Then press bksp. The result should be abc d.
  • TEST_EUROLATIN_E_ACUTE: In a compliant app, with the EuroLatin keyboard selected, type:
    caf'e
    The result should be café.
    Then press bksp. The result should be caf.
  • TEST_EUROLATIN_BACKSPACE: In a compliant app, with the EuroLatin keyboard selected, type:
    abcreturnab
    Then press bksp four times. The result should be ab.

introduces an alternative to approach to backspace
for compliant apps by using the insertText API to
replace a character to be deleted and the preceding
character from the context with only the character
from the context

Fixes: #15543
@sgschantz sgschantz added this to the A19S22 milestone Feb 4, 2026
@sgschantz sgschantz requested a review from mcdurdin February 4, 2026 19:59
@sgschantz sgschantz self-assigned this Feb 4, 2026
@sgschantz sgschantz added this to Keyman Feb 4, 2026
@sgschantz sgschantz added the mac/ label Feb 4, 2026
@github-project-automation github-project-automation bot moved this to Todo in Keyman Feb 4, 2026
@github-actions github-actions bot added the fix label Feb 4, 2026
@keymanapp-test-bot
Copy link

keymanapp-test-bot bot commented Feb 4, 2026

User Test Results

Test specification and instructions

Test Artifacts

@Meng-Heng
Copy link
Contributor

Test Specs

  1. macOS Sequoia
  2. TextEdit v1.20
  3. Keyman 19.0.198-alpha-test-15561

Test Results

  • TEST_BKSP_D_ACUTE_VERIFY_CONTEXT (PASSED):
  1. In TextEdit, type abc d/
  2. Select, Copy, and Paste into unicode.scarfboy.com
  3. Verify: The last two characters are U+0064 U+0301.
  • TEST_BKSP_D_ACUTE (PASSED):
  1. In TextEdit, type abc d/
  2. Press bksp
  3. Verify: the result is abc d.
  • TEST_EUROLATIN_E_ACUTE (PASSED):
  1. In TextEdit with EuroLatin (SIL) keyboard
  2. Type caf'e, result is café
  3. Press bksp
  4. Verify: The result is caf.
  • TEST_EUROLATIN_BACKSPACE (PASSED):
  1. In TextEdit with EuroLatin (SIL) keyboard
  2. Type abcreturnab
  3. Press bksp 4 times
  4. Verify: The result is ab.

@keymanapp-test-bot keymanapp-test-bot bot removed the user-test-required User tests have not been completed label Feb 6, 2026
Copy link
Member

@mcdurdin mcdurdin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good -- it's a clear and easy to follow function. I think we need to cover off the surrogate pair scenario for completeness.

I have tried to write a background description in the function header comment because the 'why' for this I think will be unclear to future maintainers. Feel free to adapt especially if I have gotten any details wrong.

Comment on lines +550 to +572
if ((self.apiCompliance.canReplaceText) && ([context length] > codePointsToDelete)) {
int codePointsToReplace = codePointsToDelete + 1;
NSRange replacementStringRange = NSMakeRange([context length] - codePointsToReplace, 1);
NSString *replacementString = [context substringWithRange:replacementStringRange];

// replace only works for non-control characters
// if replacementString contains control characters, then return without handling event
NSString *message = nil;
if ([self containsControlCharacter:replacementString]) {
message = @"handleDeleteByReplace, replacementString contains control characters, cannot delete with replace";
os_log_debug([KMLogs keyTraceLog], "%@", message);
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
return NO;
} else {
message = @"handleDeleteByReplace, canReplaceText == true";
os_log_debug([KMLogs keyTraceLog], "%@", message);
[KMSentryHelper addDebugBreadCrumb:@"event" message:message];
handledEvent = YES;
}

NSRange replacementRange = NSMakeRange(replacementStringRange.location, codePointsToReplace);
[client insertText:replacementString replacementRange:replacementRange];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to make your life more difficult here, but I think we need to make sure this works even with non-BMP characters (U+10000 - U+10FFFF) because it's not clear to me that splitting a surrogate pair in the replacement will work consistently -- even though it's effectively not changing, some apps may reject the transform due to encoding boundaries or safety concerns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a worthwhile test! I tried with the Malar Tirhuta keyboard, and after typing 𑒏𑒹 (U+1148F U+114B9) and hitting backspace, it only deleted the second half of the surrogate pair, leaving me with U+1148F U+FFFD

I could check the character that core tells me to delete and behave differently if it is a surrogate pair: either resort to the passthrough/generate backspace or, probably riskier, look for a match between the delete string returned from core and the context and try to replace more of the string.

I tried finding APIs that know more about the Unicode string without checking for specific character ranges, but what I found didn't work. There may be better support with the Swift Unicode APIs, so we may have better options when we move the Input Method to Swift in Keyman 20+.

sgschantz and others added 2 commits February 10, 2026 21:53
Co-authored-by: Marc Durdin <marc@durdin.net>
Co-authored-by: Marc Durdin <marc@durdin.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

bug(mac): backspace does not follow rules

3 participants