diff --git a/packages/autocomplete-plus/lib/suggestion-list.js b/packages/autocomplete-plus/lib/suggestion-list.js index 08e401a58d..5f70385ddf 100644 --- a/packages/autocomplete-plus/lib/suggestion-list.js +++ b/packages/autocomplete-plus/lib/suggestion-list.js @@ -326,7 +326,8 @@ class SuggestionList { } else if (this.overlayDecoration && this.overlayDecoration.destroy) { this.overlayDecoration.destroy() } - const editorElement = atom.views.getView(this.activeEditor) + const activeEditor = this.activeEditor + const editorElement = atom.views.getView(activeEditor) if (editorElement && editorElement.classList) { let timestamp = this.lastActiveAt atom.views.updateDocument(() => { @@ -334,6 +335,12 @@ class SuggestionList { // shouldn't remove this class name anymore. if (this.lastActiveAt > timestamp) return editorElement.classList.remove('autocomplete-active') + // If the user clicked on the suggestion, focus moved onto the overlay + // before it was destroyed, so we'll move it back onto the editor. But + // first we ensure that this is still the active editor! + if (atom.workspace.getActiveTextEditor() === activeEditor) { + editorElement.focus() + } }) } this.suggestionMarker = undefined diff --git a/packages/autocomplete-plus/spec/autocomplete-manager-integration-spec.js b/packages/autocomplete-plus/spec/autocomplete-manager-integration-spec.js index b3736e1783..2bed4a15c8 100644 --- a/packages/autocomplete-plus/spec/autocomplete-manager-integration-spec.js +++ b/packages/autocomplete-plus/spec/autocomplete-manager-integration-spec.js @@ -17,6 +17,12 @@ const path = require('path') let NodeTypeText = 3 +function simulateClick(element) { + element.dispatchEvent(new PointerEvent('mousedown', { bubbles: true, cancelable: true })); + element.dispatchEvent(new PointerEvent('mouseup', { bubbles: true, cancelable: true })); + element.dispatchEvent(new PointerEvent('click', { bubbles: true, cancelable: true })); +} + describe('Autocomplete Manager', () => { let autocompleteManager, editor, editorView, gutterWidth, mainModule, workspaceElement @@ -141,7 +147,7 @@ describe('Autocomplete Manager', () => { expect(editorView.querySelector('.autocomplete-plus')).not.toExist() }) - it('it refocuses the editor after pressing enter', async () => { + it('refocuses the editor after pressing enter', async () => { expect(editorView.querySelector('.autocomplete-plus')).not.toExist() editor.insertText('a') await waitForAutocomplete(editor) @@ -1311,6 +1317,30 @@ describe('Autocomplete Manager', () => { expect(editorView.querySelector('.autocomplete-plus')).not.toExist() }) + it('hides the suggestions list when a suggestion is clicked on', async () => { + triggerAutocompletion(editor, false, 'a') + await waitForAutocomplete(editor) + + expect(editorView.querySelector('.autocomplete-plus')).toExist() + + // Accept suggestion + let suggestionListView = editorView.querySelector('.autocomplete-plus autocomplete-suggestion-list') + let firstOption = suggestionListView.querySelector('li') + + // Manually blurring the editor here matches our observation that, when + // an actual human clicks on a suggestion, it blurs the editor and ends + // up focusing the BODY indirectly. + document.activeElement.blur() + simulateClick(firstOption) + + // Ensure the menu is closed… + expect(editorView.querySelector('.autocomplete-plus')).not.toExist() + // …and our editor still has focus. + await conditionPromise(() => { + return document.activeElement.closest('atom-text-editor') === editorView + }) + }) + describe('when the replacementPrefix is empty', () => { beforeEach(() => { spyOn(provider, 'getSuggestions').andCallFake(() => [{text: 'someMethod()', replacementPrefix: ''}]) @@ -2350,7 +2380,7 @@ defm` expect(items[0].innerText.trim()).toEqual('center') }) - it('stops providing autocompletions when disposed.', async () => { + it('stops providing autocompletions when disposed', async () => { autocompleteDisposable.dispose() bottomEditorView.focus() triggerAutocompletion(bottomEditor)