diff --git a/package.json b/package.json index a630dabfc..6c796a711 100644 --- a/package.json +++ b/package.json @@ -77,12 +77,12 @@ "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.11.0", "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.29.1", + "autocomplete-plus": "2.29.2", "autocomplete-snippets": "1.10.0", "autoflow": "0.27.0", "autosave": "0.23.1", "background-tips": "0.26.0", - "bookmarks": "0.38.2", + "bookmarks": "0.38.3", "bracket-matcher": "0.82.0", "command-palette": "0.38.0", "deprecation-cop": "0.54.1", @@ -107,7 +107,7 @@ "settings-view": "0.235.1", "snippets": "1.0.2", "spell-check": "0.67.0", - "status-bar": "1.2.0", + "status-bar": "1.2.1", "styleguide": "0.45.2", "symbols-view": "0.112.0", "tabs": "0.92.0", @@ -138,7 +138,7 @@ "language-php": "0.37.0", "language-property-list": "0.8.0", "language-python": "0.43.1", - "language-ruby": "0.68.4", + "language-ruby": "0.68.5", "language-ruby-on-rails": "0.25.0", "language-sass": "0.46.0", "language-shellscript": "0.21.1", diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 43f473e9d..d74d7ebd6 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -3747,6 +3747,21 @@ describe('TextEditorComponent', function () { return event } + function buildKeydownEvent ({keyCode, target}) { + let event = new KeyboardEvent('keydown') + Object.defineProperty(event, 'keyCode', { + get: function () { + return keyCode + } + }) + Object.defineProperty(event, 'target', { + get: function () { + return target + } + }) + return event + } + let inputNode beforeEach(function () { @@ -3769,11 +3784,12 @@ describe('TextEditorComponent', function () { expect(editor.lineTextForBufferRow(0)).toBe('xyvar quicksort = function () {') }) - it('replaces the last character if the length of the input\'s value does not increase, as occurs with the accented character menu', async function () { - componentNode.dispatchEvent(buildTextInputEvent({ - data: 'u', - target: inputNode - })) + it('replaces the last character if a keypress event is bracketed by keydown events with matching keyCodes, which occurs when the accented character menu is shown', async function () { + componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode})) + componentNode.dispatchEvent(buildTextInputEvent({data: 'u', target: inputNode})) + componentNode.dispatchEvent(new KeyboardEvent('keypress')) + componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode})) + componentNode.dispatchEvent(new KeyboardEvent('keyup')) await nextViewUpdatePromise() expect(editor.lineTextForBufferRow(0)).toBe('uvar quicksort = function () {') diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index c5dc2929d..7e47e5907 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -247,9 +247,50 @@ class TextEditorComponent @scrollViewNode.addEventListener 'mousedown', @onMouseDown @scrollViewNode.addEventListener 'scroll', @onScrollViewScroll + @detectAccentedCharacterMenu() @listenForIMEEvents() @trackSelectionClipboard() if process.platform is 'linux' + detectAccentedCharacterMenu: -> + # We need to get clever to detect when the accented character menu is + # opened on OS X. Usually, every keydown event that could cause input is + # followed by a corresponding keypress. However, pressing and holding + # long enough to open the accented character menu causes additional keydown + # events to fire that aren't followed by their own keypress and textInput + # events. + # + # Therefore, we assume the accented character menu has been deployed if, + # before observing any keyup event, we observe events in the following + # sequence: + # + # keydown(keyCode: X), keypress, keydown(keyCode: X) + # + # The keyCode X must be the same in the keydown events that bracket the + # keypress, meaning we're *holding* the _same_ key we intially pressed. + # Got that? + lastKeydown = null + lastKeydownBeforeKeypress = null + + @domNode.addEventListener 'keydown', (event) => + if lastKeydownBeforeKeypress + if lastKeydownBeforeKeypress.keyCode is event.keyCode + @openedAccentedCharacterMenu = true + lastKeydownBeforeKeypress = null + else + lastKeydown = event + + @domNode.addEventListener 'keypress', => + lastKeydownBeforeKeypress = lastKeydown + lastKeydown = null + + # This cancels the accented character behavior if we type a key normally + # with the menu open. + @openedAccentedCharacterMenu = false + + @domNode.addEventListener 'keyup', => + lastKeydownBeforeKeypress = null + lastKeydown = null + listenForIMEEvents: -> # The IME composition events work like this: # @@ -266,6 +307,9 @@ class TextEditorComponent checkpoint = null @domNode.addEventListener 'compositionstart', => + if @openedAccentedCharacterMenu + @editor.selectLeft() + @openedAccentedCharacterMenu = false checkpoint = @editor.createCheckpoint() @domNode.addEventListener 'compositionupdate', (event) => @editor.insertText(event.data, select: true) @@ -321,24 +365,21 @@ class TextEditorComponent onTextInput: (event) => event.stopPropagation() - - # If we prevent the insertion of a space character, then the browser - # interprets the spacebar keypress as a page-down command. - event.preventDefault() unless event.data is ' ' + event.preventDefault() return unless @isInputEnabled() - inputNode = event.target + # Workaround of the accented character suggestion feature in OS X. + # This will only occur when the user is not composing in IME mode. + # When the user selects a modified character from the OSX menu, `textInput` + # will occur twice, once for the initial character, and once for the + # modified character. However, only a single keypress will have fired. If + # this is the case, select backward to replace the original character. + if @openedAccentedCharacterMenu + @editor.selectLeft() + @openedAccentedCharacterMenu = false - # Work around of the accented character suggestion feature in OS X. - # Text input fires before a character is inserted, and if the browser is - # replacing the previous un-accented character with an accented variant, it - # will select backward over it. - selectedLength = inputNode.selectionEnd - inputNode.selectionStart - @editor.selectLeft() if selectedLength is 1 - - insertedRange = @editor.insertText(event.data, groupUndo: true) - inputNode.value = event.data if insertedRange + @editor.insertText(event.data, groupUndo: true) onVerticalScroll: (scrollTop) => return if @updateRequested or scrollTop is @presenter.getScrollTop()