mirror of
https://github.com/atom/atom.git
synced 2026-01-14 17:38:03 -05:00
Merge branch 'master' into pb-remove-unneeded-files-win
This commit is contained in:
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "1.18.3"
|
||||
"atom-package-manager": "1.18.4"
|
||||
}
|
||||
}
|
||||
|
||||
28
appveyor.yml
28
appveyor.yml
@@ -19,9 +19,11 @@ environment:
|
||||
global:
|
||||
ATOM_DEV_RESOURCE_PATH: c:\projects\atom
|
||||
TEST_JUNIT_XML_ROOT: c:\projects\junit-test-results
|
||||
NODE_VERSION: 6.9.4
|
||||
|
||||
matrix:
|
||||
- NODE_VERSION: 6.9.4
|
||||
- TASK: test
|
||||
- TASK: installer
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
@@ -34,14 +36,26 @@ install:
|
||||
|
||||
build_script:
|
||||
- CD %APPVEYOR_BUILD_FOLDER%
|
||||
- script\build.cmd --code-sign --compress-artifacts
|
||||
- IF NOT EXIST C:\tmp MKDIR C:\tmp
|
||||
- SET SQUIRREL_TEMP=C:\tmp
|
||||
- IF [%TASK%]==[installer] (
|
||||
IF [%APPVEYOR_REPO_BRANCH:~-9%]==[-releases] (
|
||||
script\build.cmd --code-sign --compress-artifacts --create-windows-installer
|
||||
) ELSE (
|
||||
ECHO Skipping installer and Atom build on non-release branch
|
||||
)
|
||||
) ELSE (
|
||||
ECHO Skipping installer build on non-installer build matrix row &&
|
||||
script\build.cmd --code-sign --compress-artifacts
|
||||
)
|
||||
|
||||
test_script:
|
||||
- script\lint.cmd
|
||||
- script\test.cmd
|
||||
|
||||
after_test:
|
||||
- IF [%APPVEYOR_REPO_BRANCH:~-9%]==[-releases] ( script\create-installer.cmd )
|
||||
- IF [%TASK%]==[test] (
|
||||
script\lint.cmd &&
|
||||
script\test.cmd
|
||||
) ELSE (
|
||||
ECHO Skipping tests on installer build matrix row
|
||||
)
|
||||
|
||||
deploy: off
|
||||
artifacts:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Open the dev tools with `alt-cmd-i`
|
||||
* Evaluate `process.versions.electron` in the console.
|
||||
* Based on this version, download the appropriate Electron symbols from the [releases](https://github.com/atom/electron/releases) page.
|
||||
* The file name should look like `electron-v0.X.Y-darwin-x64-dsym.zip`.
|
||||
* The file name should look like `electron-v1.X.Y-darwin-x64-dsym.zip`.
|
||||
* Decompress these symbols in your `~/Downloads` directory.
|
||||
* Now create a time profile in Instruments.
|
||||
* Open `Instruments.app`.
|
||||
|
||||
10
package.json
10
package.json
@@ -99,7 +99,7 @@
|
||||
"autosave": "0.24.3",
|
||||
"background-tips": "0.27.1",
|
||||
"bookmarks": "0.44.4",
|
||||
"bracket-matcher": "0.87.0",
|
||||
"bracket-matcher": "0.87.3",
|
||||
"command-palette": "0.40.4",
|
||||
"dalek": "0.2.1",
|
||||
"deprecation-cop": "0.56.7",
|
||||
@@ -108,7 +108,7 @@
|
||||
"exception-reporting": "0.41.4",
|
||||
"find-and-replace": "0.209.5",
|
||||
"fuzzy-finder": "1.5.8",
|
||||
"github": "0.4.0",
|
||||
"github": "0.4.2",
|
||||
"git-diff": "1.3.6",
|
||||
"go-to-line": "0.32.1",
|
||||
"grammar-selector": "0.49.5",
|
||||
@@ -139,14 +139,14 @@
|
||||
"language-clojure": "0.22.4",
|
||||
"language-coffee-script": "0.48.9",
|
||||
"language-csharp": "0.14.2",
|
||||
"language-css": "0.42.3",
|
||||
"language-css": "0.42.4",
|
||||
"language-gfm": "0.90.0",
|
||||
"language-git": "0.19.1",
|
||||
"language-go": "0.44.2",
|
||||
"language-html": "0.47.3",
|
||||
"language-hyperlink": "0.16.2",
|
||||
"language-java": "0.27.2",
|
||||
"language-javascript": "0.127.1",
|
||||
"language-java": "0.27.3",
|
||||
"language-javascript": "0.127.2",
|
||||
"language-json": "0.19.1",
|
||||
"language-less": "0.33.0",
|
||||
"language-make": "0.22.3",
|
||||
|
||||
@@ -875,10 +875,6 @@
|
||||
"hasDeprecations": true,
|
||||
"latestHasDeprecations": false
|
||||
},
|
||||
"language-typescript": {
|
||||
"hasAlternative": true,
|
||||
"alternative": "atom-typescript"
|
||||
},
|
||||
"laravel-facades": {
|
||||
"version": "<=1.0.0",
|
||||
"hasDeprecations": true,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"csslint": "1.0.2",
|
||||
"donna": "1.0.16",
|
||||
"electron-chromedriver": "~1.6",
|
||||
"electron-link": "0.1.0",
|
||||
"electron-link": "0.1.1",
|
||||
"electron-mksnapshot": "~1.6",
|
||||
"electron-packager": "7.3.0",
|
||||
"electron-winstaller": "2.6.2",
|
||||
|
||||
@@ -14,10 +14,11 @@ const ATOM_RESOURCE_PATH = path.resolve(__dirname, '..', '..')
|
||||
describe('AtomApplication', function () {
|
||||
this.timeout(60 * 1000)
|
||||
|
||||
let originalAppQuit, originalAtomHome, atomApplicationsToDestroy
|
||||
let originalAppQuit, originalShowMessageBox, originalAtomHome, atomApplicationsToDestroy
|
||||
|
||||
beforeEach(function () {
|
||||
originalAppQuit = electron.app.quit
|
||||
originalShowMessageBox = electron.dialog.showMessageBox
|
||||
mockElectronAppQuit()
|
||||
originalAtomHome = process.env.ATOM_HOME
|
||||
process.env.ATOM_HOME = makeTempDir('atom-home')
|
||||
@@ -39,6 +40,7 @@ describe('AtomApplication', function () {
|
||||
}
|
||||
await clearElectronSession()
|
||||
electron.app.quit = originalAppQuit
|
||||
electron.dialog.showMessageBox = originalShowMessageBox
|
||||
})
|
||||
|
||||
describe('launch', function () {
|
||||
@@ -462,20 +464,42 @@ describe('AtomApplication', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('before quitting', function () {
|
||||
it('waits until all the windows have saved their state and then quits', async function () {
|
||||
const dirAPath = makeTempDir("a")
|
||||
const dirBPath = makeTempDir("b")
|
||||
const atomApplication = buildAtomApplication()
|
||||
const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')]))
|
||||
await focusWindow(window1)
|
||||
const window2 = atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')]))
|
||||
await focusWindow(window2)
|
||||
electron.app.quit()
|
||||
assert(!electron.app.hasQuitted())
|
||||
await Promise.all([window1.lastPrepareToUnloadPromise, window2.lastPrepareToUnloadPromise])
|
||||
assert(electron.app.hasQuitted())
|
||||
it('waits until all the windows have saved their state before quitting', async function () {
|
||||
const dirAPath = makeTempDir("a")
|
||||
const dirBPath = makeTempDir("b")
|
||||
const atomApplication = buildAtomApplication()
|
||||
const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')]))
|
||||
await focusWindow(window1)
|
||||
const window2 = atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')]))
|
||||
await focusWindow(window2)
|
||||
electron.app.quit()
|
||||
assert(!electron.app.hasQuitted())
|
||||
await Promise.all([window1.lastPrepareToUnloadPromise, window2.lastPrepareToUnloadPromise])
|
||||
assert(electron.app.hasQuitted())
|
||||
})
|
||||
|
||||
it('prevents quitting if user cancels when prompted to save an item', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const window1 = atomApplication.launch(parseCommandLine([]))
|
||||
const window2 = atomApplication.launch(parseCommandLine([]))
|
||||
await Promise.all([window1.loadedPromise, window2.loadedPromise])
|
||||
await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.getActiveTextEditor().insertText('unsaved text')
|
||||
sendBackToMainProcess()
|
||||
})
|
||||
|
||||
// Choosing "Cancel"
|
||||
mockElectronShowMessageBox({choice: 1})
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(!electron.app.hasQuitted())
|
||||
assert.equal(electron.app.quit.callCount, 1) // Ensure choosing "Cancel" doesn't try to quit the electron app more than once (regression)
|
||||
|
||||
// Choosing "Don't save"
|
||||
mockElectronShowMessageBox({choice: 2})
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(electron.app.hasQuitted())
|
||||
})
|
||||
|
||||
function buildAtomApplication () {
|
||||
@@ -496,6 +520,12 @@ describe('AtomApplication', function () {
|
||||
function mockElectronAppQuit () {
|
||||
let quitted = false
|
||||
electron.app.quit = function () {
|
||||
if (electron.app.quit.callCount) {
|
||||
electron.app.quit.callCount++
|
||||
} else {
|
||||
electron.app.quit.callCount = 1
|
||||
}
|
||||
|
||||
let shouldQuit = true
|
||||
electron.app.emit('before-quit', {preventDefault: () => { shouldQuit = false }})
|
||||
if (shouldQuit) {
|
||||
@@ -507,6 +537,12 @@ describe('AtomApplication', function () {
|
||||
}
|
||||
}
|
||||
|
||||
function mockElectronShowMessageBox ({choice}) {
|
||||
electron.dialog.showMessageBox = function () {
|
||||
return choice
|
||||
}
|
||||
}
|
||||
|
||||
function makeTempDir (name) {
|
||||
const temp = require('temp').track()
|
||||
return fs.realpathSync(temp.mkdirSync(name))
|
||||
|
||||
@@ -551,10 +551,11 @@ describe('Pane', () => {
|
||||
itemURI = 'test'
|
||||
confirm.andReturn(0)
|
||||
|
||||
await pane.destroyItem(item1)
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
expect(pane.getItems().includes(item1)).toBe(false)
|
||||
expect(item1.isDestroyed()).toBe(true)
|
||||
expect(success).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -565,11 +566,12 @@ describe('Pane', () => {
|
||||
showSaveDialog.andReturn('/selected/path')
|
||||
confirm.andReturn(0)
|
||||
|
||||
await pane.destroyItem(item1)
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(showSaveDialog).toHaveBeenCalled()
|
||||
expect(item1.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
expect(pane.getItems().includes(item1)).toBe(false)
|
||||
expect(item1.isDestroyed()).toBe(true)
|
||||
expect(success).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -578,10 +580,11 @@ describe('Pane', () => {
|
||||
it('removes and destroys the item without saving it', async () => {
|
||||
confirm.andReturn(2)
|
||||
|
||||
await pane.destroyItem(item1)
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().includes(item1)).toBe(false)
|
||||
expect(item1.isDestroyed()).toBe(true)
|
||||
expect(success).toBe(true);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -589,19 +592,21 @@ describe('Pane', () => {
|
||||
it('does not save, remove, or destroy the item', async () => {
|
||||
confirm.andReturn(1)
|
||||
|
||||
await pane.destroyItem(item1)
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().includes(item1)).toBe(true)
|
||||
expect(item1.isDestroyed()).toBe(false)
|
||||
expect(success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when force=true', () => {
|
||||
it('destroys the item immediately', async () => {
|
||||
await pane.destroyItem(item1, true)
|
||||
const success = await pane.destroyItem(item1, true)
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().includes(item1)).toBe(false)
|
||||
expect(item1.isDestroyed()).toBe(true)
|
||||
expect(success).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -630,18 +635,20 @@ describe('Pane', () => {
|
||||
})
|
||||
|
||||
describe('when passed a permanent dock item', () => {
|
||||
it("doesn't destroy the item", () => {
|
||||
it("doesn't destroy the item", async () => {
|
||||
spyOn(item1, 'isPermanentDockItem').andReturn(true)
|
||||
pane.destroyItem(item1)
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(pane.getItems().includes(item1)).toBe(true)
|
||||
expect(item1.isDestroyed()).toBe(false)
|
||||
expect(success).toBe(false);
|
||||
})
|
||||
|
||||
it('destroy the item if force=true', () => {
|
||||
it('destroy the item if force=true', async () => {
|
||||
spyOn(item1, 'isPermanentDockItem').andReturn(true)
|
||||
pane.destroyItem(item1, true)
|
||||
const success = await pane.destroyItem(item1, true)
|
||||
expect(pane.getItems().includes(item1)).toBe(false)
|
||||
expect(item1.isDestroyed()).toBe(true)
|
||||
expect(success).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -119,6 +119,44 @@ describe('TextEditorComponent', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('re-renders lines when their height changes', async () => {
|
||||
const {component, element, editor} = buildComponent({rowsPerTile: 3, autoHeight: false})
|
||||
element.style.height = 4 * component.measurements.lineHeight + 'px'
|
||||
await component.getNextUpdatePromise()
|
||||
expect(element.querySelectorAll('.line-number:not(.dummy)').length).toBe(9)
|
||||
expect(element.querySelectorAll('.line:not(.dummy)').length).toBe(9)
|
||||
|
||||
element.style.lineHeight = '2.0'
|
||||
TextEditor.didUpdateStyles()
|
||||
await component.getNextUpdatePromise()
|
||||
expect(element.querySelectorAll('.line-number:not(.dummy)').length).toBe(6)
|
||||
expect(element.querySelectorAll('.line:not(.dummy)').length).toBe(6)
|
||||
|
||||
element.style.lineHeight = '0.7'
|
||||
TextEditor.didUpdateStyles()
|
||||
await component.getNextUpdatePromise()
|
||||
expect(element.querySelectorAll('.line-number:not(.dummy)').length).toBe(12)
|
||||
expect(element.querySelectorAll('.line:not(.dummy)').length).toBe(12)
|
||||
|
||||
element.style.lineHeight = '0.05'
|
||||
TextEditor.didUpdateStyles()
|
||||
await component.getNextUpdatePromise()
|
||||
expect(element.querySelectorAll('.line-number:not(.dummy)').length).toBe(13)
|
||||
expect(element.querySelectorAll('.line:not(.dummy)').length).toBe(13)
|
||||
|
||||
element.style.lineHeight = '0'
|
||||
TextEditor.didUpdateStyles()
|
||||
await component.getNextUpdatePromise()
|
||||
expect(element.querySelectorAll('.line-number:not(.dummy)').length).toBe(13)
|
||||
expect(element.querySelectorAll('.line:not(.dummy)').length).toBe(13)
|
||||
|
||||
element.style.lineHeight = '1'
|
||||
TextEditor.didUpdateStyles()
|
||||
await component.getNextUpdatePromise()
|
||||
expect(element.querySelectorAll('.line-number:not(.dummy)').length).toBe(9)
|
||||
expect(element.querySelectorAll('.line:not(.dummy)').length).toBe(9)
|
||||
})
|
||||
|
||||
it('makes the content at least as tall as the scroll container client height', async () => {
|
||||
const {component, element, editor} = buildComponent({text: 'a', height: 100})
|
||||
expect(component.refs.content.offsetHeight).toBe(100)
|
||||
@@ -1524,6 +1562,27 @@ describe('TextEditorComponent', () => {
|
||||
await setScrollTop(component, component.getLineHeight() * 3)
|
||||
expect(element.querySelectorAll('.highlight.a').length).toBe(0)
|
||||
})
|
||||
|
||||
it('does not move existing highlights when adding or removing other highlight decorations (regression)', async () => {
|
||||
const {component, element, editor} = buildComponent()
|
||||
|
||||
const marker1 = editor.markScreenRange([[1, 6], [1, 10]])
|
||||
editor.decorateMarker(marker1, {type: 'highlight', class: 'a'})
|
||||
await component.getNextUpdatePromise()
|
||||
const marker1Region = element.querySelector('.highlight.a')
|
||||
expect(Array.from(marker1Region.parentElement.children).indexOf(marker1Region)).toBe(0)
|
||||
|
||||
const marker2 = editor.markScreenRange([[1, 2], [1, 4]])
|
||||
editor.decorateMarker(marker2, {type: 'highlight', class: 'b'})
|
||||
await component.getNextUpdatePromise()
|
||||
const marker2Region = element.querySelector('.highlight.b')
|
||||
expect(Array.from(marker1Region.parentElement.children).indexOf(marker1Region)).toBe(0)
|
||||
expect(Array.from(marker2Region.parentElement.children).indexOf(marker2Region)).toBe(1)
|
||||
|
||||
marker2.destroy()
|
||||
await component.getNextUpdatePromise()
|
||||
expect(Array.from(marker1Region.parentElement.children).indexOf(marker1Region)).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('overlay decorations', () => {
|
||||
@@ -2094,6 +2153,39 @@ describe('TextEditorComponent', () => {
|
||||
expect(component.refs.blockDecorationMeasurementArea.offsetWidth).toBe(component.getScrollWidth())
|
||||
})
|
||||
|
||||
it('does not change the cursor position when clicking on a block decoration', async () => {
|
||||
const {editor, component} = buildComponent()
|
||||
|
||||
const decorationElement = document.createElement('div')
|
||||
decorationElement.textContent = 'Parent'
|
||||
const childElement = document.createElement('div')
|
||||
childElement.textContent = 'Child'
|
||||
decorationElement.appendChild(childElement)
|
||||
const marker = editor.markScreenPosition([4, 0])
|
||||
editor.decorateMarker(marker, {type: 'block', item: decorationElement})
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
const decorationElementClientRect = decorationElement.getBoundingClientRect()
|
||||
component.didMouseDownOnContent({
|
||||
target: decorationElement,
|
||||
detail: 1,
|
||||
button: 0,
|
||||
clientX: decorationElementClientRect.left,
|
||||
clientY: decorationElementClientRect.top
|
||||
})
|
||||
expect(editor.getCursorScreenPosition()).toEqual([0, 0])
|
||||
|
||||
const childElementClientRect = childElement.getBoundingClientRect()
|
||||
component.didMouseDownOnContent({
|
||||
target: childElement,
|
||||
detail: 1,
|
||||
button: 0,
|
||||
clientX: childElementClientRect.left,
|
||||
clientY: childElementClientRect.top
|
||||
})
|
||||
expect(editor.getCursorScreenPosition()).toEqual([0, 0])
|
||||
})
|
||||
|
||||
function createBlockDecorationAtScreenRow(editor, screenRow, {height, margin, position}) {
|
||||
const marker = editor.markScreenPosition([screenRow, 0], {invalidate: 'never'})
|
||||
const item = document.createElement('div')
|
||||
@@ -3028,198 +3120,421 @@ describe('TextEditorComponent', () => {
|
||||
})
|
||||
|
||||
describe('keyboard input', () => {
|
||||
it('handles inserted accented characters via the press-and-hold menu on macOS correctly', () => {
|
||||
const {editor, component, element} = buildComponent({text: ''})
|
||||
editor.insertText('x')
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
describe('on Chrome 56', () => {
|
||||
it('handles inserted accented characters via the press-and-hold menu on macOS correctly', async () => {
|
||||
const {editor, component, element} = buildComponent({text: '', chromeVersion: 56})
|
||||
editor.insertText('x')
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
expect(editor.getText()).toBe('xa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xaa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
expect(editor.getText()).toBe('xa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xaa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then selecting an alternative by typing a number.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'Digit2'})
|
||||
component.didKeyup({code: 'Digit2'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then selecting an alternative by typing a number.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'Digit2'})
|
||||
component.didKeyup({code: 'Digit2'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then selecting an alternative by clicking on it.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then selecting an alternative by clicking on it.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then selecting one of them with Enter.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didKeydown({code: 'Enter'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'á', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Enter'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then selecting one of them with Enter.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.getHiddenInput().value = 'à'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.getHiddenInput().value = 'á'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didKeydown({code: 'Enter'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.getHiddenInput().value = 'á'
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'á', target: component.getHiddenInput()})
|
||||
component.didKeyup({code: 'Enter'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xá')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didCompositionUpdate({data: 'a'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
expect(editor.getText()).toBe('xa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xaa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate pressing the O key and holding the A key to open the press-and-hold menu right before releasing the O key,
|
||||
// cycling through the alternatives with the arrows, then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyO'})
|
||||
component.didKeypress({code: 'KeyO'})
|
||||
component.didTextInput({data: 'o', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyO'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xoà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xoá')
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didCompositionUpdate({data: 'a'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
expect(editor.getText()).toBe('xoa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.getHiddenInput().value = 'à'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.getHiddenInput().value = 'á'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didCompositionUpdate({data: 'a'})
|
||||
component.getHiddenInput().value = 'a'
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xaa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then closing it by changing focus.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'á', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
// Simulate pressing the O key and holding the A key to open the press-and-hold menu right before releasing the O key,
|
||||
// cycling through the alternatives with the arrows, then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyO'})
|
||||
component.didKeypress({code: 'KeyO'})
|
||||
component.didTextInput({data: 'o', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyO'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.getHiddenInput().value = 'à'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xoà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.getHiddenInput().value = 'á'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xoá')
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didCompositionUpdate({data: 'a'})
|
||||
component.getHiddenInput().value = 'a'
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xoa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then closing it by changing focus.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.getHiddenInput().value = 'à'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.getHiddenInput().value = 'á'
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.getHiddenInput().value = 'á'
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'á', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
await getNextTickPromise()
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on other versions of Chrome', () => {
|
||||
it('handles inserted accented characters via the press-and-hold menu on macOS correctly', () => {
|
||||
const {editor, component, element} = buildComponent({text: '', chromeVersion: 57})
|
||||
editor.insertText('x')
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
expect(editor.getText()).toBe('xa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xaa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then selecting an alternative by typing a number.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'Digit2'})
|
||||
component.didKeyup({code: 'Digit2'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// then selecting an alternative by clicking on it.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then selecting one of them with Enter.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didKeydown({code: 'Enter'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'á', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Enter'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didCompositionUpdate({data: 'a'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
expect(editor.getText()).toBe('xa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xaa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate pressing the O key and holding the A key to open the press-and-hold menu right before releasing the O key,
|
||||
// cycling through the alternatives with the arrows, then closing it via ESC.
|
||||
component.didKeydown({code: 'KeyO'})
|
||||
component.didKeypress({code: 'KeyO'})
|
||||
component.didTextInput({data: 'o', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyO'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xoà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xoá')
|
||||
component.didKeydown({code: 'Escape'})
|
||||
component.didCompositionUpdate({data: 'a'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
component.didKeyup({code: 'Escape'})
|
||||
expect(editor.getText()).toBe('xoa')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
|
||||
// Simulate holding the A key to open the press-and-hold menu,
|
||||
// cycling through the alternatives with the arrows, then closing it by changing focus.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionStart({data: ''})
|
||||
component.didCompositionUpdate({data: 'à'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xà')
|
||||
component.didKeydown({code: 'ArrowRight'})
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didKeyup({code: 'ArrowRight'})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
component.didCompositionUpdate({data: 'á'})
|
||||
component.didTextInput({data: 'á', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didCompositionEnd({data: 'á', target: component.refs.cursorsAndInput.refs.hiddenInput})
|
||||
expect(editor.getText()).toBe('xá')
|
||||
// Ensure another "a" can be typed correctly.
|
||||
component.didKeydown({code: 'KeyA'})
|
||||
component.didKeypress({code: 'KeyA'})
|
||||
component.didTextInput({data: 'a', stopPropagation: () => {}, preventDefault: () => {}})
|
||||
component.didKeyup({code: 'KeyA'})
|
||||
expect(editor.getText()).toBe('xáa')
|
||||
editor.undo()
|
||||
expect(editor.getText()).toBe('x')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3378,6 +3693,15 @@ describe('TextEditorComponent', () => {
|
||||
expect(top).toBe(clientTopForLine(referenceComponent, 12) - referenceContentRect.top)
|
||||
expect(left).toBe(clientLeftForCharacter(referenceComponent, 12, 1) - referenceContentRect.left)
|
||||
}
|
||||
|
||||
// Measuring a currently rendered line while an autoscroll that causes
|
||||
// that line to go off-screen is in progress.
|
||||
{
|
||||
editor.setCursorScreenPosition([10, 0])
|
||||
const {top, left} = component.pixelPositionForScreenPosition({row: 3, column: 5})
|
||||
expect(top).toBe(clientTopForLine(referenceComponent, 3) - referenceContentRect.top)
|
||||
expect(left).toBe(clientLeftForCharacter(referenceComponent, 3, 5) - referenceContentRect.left)
|
||||
}
|
||||
})
|
||||
|
||||
it('does not get the component into an inconsistent state when the model has unflushed changes (regression)', async () => {
|
||||
@@ -3424,6 +3748,16 @@ describe('TextEditorComponent', () => {
|
||||
pixelPosition.left += component.getBaseCharacterWidth() / 3
|
||||
expect(component.screenPositionForPixelPosition(pixelPosition)).toEqual([12, 1])
|
||||
}
|
||||
|
||||
// Measuring a currently rendered line while an autoscroll that causes
|
||||
// that line to go off-screen is in progress.
|
||||
{
|
||||
const pixelPosition = referenceComponent.pixelPositionForScreenPosition({row: 3, column: 4})
|
||||
pixelPosition.top += component.getLineHeight() / 3
|
||||
pixelPosition.left += component.getBaseCharacterWidth() / 3
|
||||
editor.setCursorBufferPosition([10, 0])
|
||||
expect(component.screenPositionForPixelPosition(pixelPosition)).toEqual([3, 4])
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3525,6 +3859,7 @@ function buildComponent (params = {}) {
|
||||
rowsPerTile: params.rowsPerTile,
|
||||
updatedSynchronously: params.updatedSynchronously || false,
|
||||
platform: params.platform,
|
||||
chromeVersion: params.chromeVersion,
|
||||
mouseWheelScrollSensitivity: params.mouseWheelScrollSensitivity
|
||||
})
|
||||
const {element} = component
|
||||
@@ -3658,3 +3993,7 @@ function getElementHeight (element) {
|
||||
bottomRuler.remove()
|
||||
return height
|
||||
}
|
||||
|
||||
function getNextTickPromise () {
|
||||
return new Promise((resolve) => process.nextTick(resolve))
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ describe "TokenizedBuffer", ->
|
||||
it "schedules the invalidated lines to be tokenized in the background", ->
|
||||
buffer.insert([5, 30], '/* */')
|
||||
buffer.setTextInRange([[2, 0], [3, 0]], '/*')
|
||||
expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
|
||||
expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.begin.js']
|
||||
expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js']
|
||||
|
||||
advanceClock()
|
||||
@@ -214,7 +214,7 @@ describe "TokenizedBuffer", ->
|
||||
it "schedules the invalidated lines to be tokenized in the background", ->
|
||||
buffer.insert([5, 30], '/* */')
|
||||
buffer.insert([2, 0], '/*\nabcde\nabcder')
|
||||
expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
|
||||
expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.begin.js']
|
||||
expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
|
||||
expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
|
||||
expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js']
|
||||
@@ -596,10 +596,10 @@ describe "TokenizedBuffer", ->
|
||||
{position: Point(0, 9), closeTags: ["syntax--keyword syntax--operator syntax--assignment syntax--js"], openTags: []}
|
||||
{position: Point(0, 10), closeTags: [], openTags: ["syntax--constant syntax--numeric syntax--decimal syntax--js"]}
|
||||
{position: Point(0, 11), closeTags: ["syntax--constant syntax--numeric syntax--decimal syntax--js"], openTags: []}
|
||||
{position: Point(0, 12), closeTags: [], openTags: ["syntax--comment syntax--block syntax--js", "syntax--punctuation syntax--definition syntax--comment syntax--js"]}
|
||||
{position: Point(0, 14), closeTags: ["syntax--punctuation syntax--definition syntax--comment syntax--js"], openTags: []}
|
||||
{position: Point(1, 5), closeTags: [], openTags: ["syntax--punctuation syntax--definition syntax--comment syntax--js"]}
|
||||
{position: Point(1, 7), closeTags: ["syntax--punctuation syntax--definition syntax--comment syntax--js", "syntax--comment syntax--block syntax--js"], openTags: ["syntax--storage syntax--type syntax--var syntax--js"]}
|
||||
{position: Point(0, 12), closeTags: [], openTags: ["syntax--comment syntax--block syntax--js", "syntax--punctuation syntax--definition syntax--comment syntax--begin syntax--js"]}
|
||||
{position: Point(0, 14), closeTags: ["syntax--punctuation syntax--definition syntax--comment syntax--begin syntax--js"], openTags: []}
|
||||
{position: Point(1, 5), closeTags: [], openTags: ["syntax--punctuation syntax--definition syntax--comment syntax--end syntax--js"]}
|
||||
{position: Point(1, 7), closeTags: ["syntax--punctuation syntax--definition syntax--comment syntax--end syntax--js", "syntax--comment syntax--block syntax--js"], openTags: ["syntax--storage syntax--type syntax--var syntax--js"]}
|
||||
{position: Point(1, 10), closeTags: ["syntax--storage syntax--type syntax--var syntax--js"], openTags: []}
|
||||
{position: Point(1, 15), closeTags: [], openTags: ["syntax--keyword syntax--operator syntax--assignment syntax--js"]}
|
||||
{position: Point(1, 16), closeTags: ["syntax--keyword syntax--operator syntax--assignment syntax--js"], openTags: []}
|
||||
|
||||
@@ -2394,6 +2394,22 @@ i = /test/; #FIXME\
|
||||
expect(results[0].replacements).toBe(6)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not discard the multiline flag', () => {
|
||||
const filePath = path.join(projectDir, 'sample.js')
|
||||
fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath)
|
||||
|
||||
const results = []
|
||||
waitsForPromise(() =>
|
||||
atom.workspace.replace(/;$/gmi, 'items', [filePath], result => results.push(result))
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
expect(results).toHaveLength(1)
|
||||
expect(results[0].filePath).toBe(filePath)
|
||||
expect(results[0].replacements).toBe(8)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a buffer is already open', () => {
|
||||
|
||||
@@ -269,10 +269,19 @@ class AtomApplication
|
||||
@openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md'))
|
||||
|
||||
@disposable.add ipcHelpers.on app, 'before-quit', (event) =>
|
||||
unless @quitting
|
||||
resolveBeforeQuitPromise = null
|
||||
@lastBeforeQuitPromise = new Promise((resolve) -> resolveBeforeQuitPromise = resolve)
|
||||
if @quitting
|
||||
resolveBeforeQuitPromise()
|
||||
else
|
||||
event.preventDefault()
|
||||
@quitting = true
|
||||
Promise.all(@windows.map((window) -> window.prepareToUnload())).then(-> app.quit())
|
||||
windowUnloadPromises = @windows.map((window) -> window.prepareToUnload())
|
||||
Promise.all(windowUnloadPromises).then((windowUnloadedResults) ->
|
||||
didUnloadAllWindows = windowUnloadedResults.every((didUnloadWindow) -> didUnloadWindow)
|
||||
app.quit() if didUnloadAllWindows
|
||||
resolveBeforeQuitPromise()
|
||||
)
|
||||
|
||||
@disposable.add ipcHelpers.on app, 'will-quit', =>
|
||||
@killAllProcesses()
|
||||
|
||||
@@ -621,12 +621,15 @@ class Pane
|
||||
destroyItem: (item, force) ->
|
||||
index = @items.indexOf(item)
|
||||
if index isnt -1
|
||||
return false if not force and @getContainer()?.getLocation() isnt 'center' and item.isPermanentDockItem?()
|
||||
if not force and @getContainer()?.getLocation() isnt 'center' and item.isPermanentDockItem?()
|
||||
return Promise.resolve(false)
|
||||
|
||||
@emitter.emit 'will-destroy-item', {item, index}
|
||||
@container?.willDestroyPaneItem({item, index, pane: this})
|
||||
if force or not item?.shouldPromptToSave?()
|
||||
@removeItem(item, false)
|
||||
item.destroy?()
|
||||
Promise.resolve(true)
|
||||
else
|
||||
@promptToSaveItem(item).then (result) =>
|
||||
if result
|
||||
|
||||
@@ -110,8 +110,8 @@ class TextEditorComponent {
|
||||
this.cursorsBlinking = false
|
||||
this.cursorsBlinkedOff = false
|
||||
this.nextUpdateOnlyBlinksCursors = null
|
||||
this.extraLinesToMeasure = null
|
||||
this.extraRenderedScreenLines = null
|
||||
this.linesToMeasure = new Map()
|
||||
this.extraRenderedScreenLines = new Map()
|
||||
this.horizontalPositionsToMeasure = new Map() // Keys are rows with positions we want to measure, values are arrays of columns to measure
|
||||
this.horizontalPixelPositionsByScreenLineId = new Map() // Values are maps from column to horiontal pixel positions
|
||||
this.blockDecorationsToMeasure = new Set()
|
||||
@@ -355,6 +355,7 @@ class TextEditorComponent {
|
||||
this.queryLineNumbersToRender()
|
||||
this.queryGuttersToRender()
|
||||
this.queryDecorationsToRender()
|
||||
this.queryExtraScreenLinesToRender()
|
||||
this.shouldRenderDummyScrollbars = !this.remeasureScrollbars
|
||||
etch.updateSync(this)
|
||||
this.updateClassList()
|
||||
@@ -369,8 +370,6 @@ class TextEditorComponent {
|
||||
}
|
||||
const wasHorizontalScrollbarVisible = this.isHorizontalScrollbarVisible()
|
||||
|
||||
this.extraRenderedScreenLines = this.extraLinesToMeasure
|
||||
this.extraLinesToMeasure = null
|
||||
this.measureLongestLineWidth()
|
||||
this.measureHorizontalPositions()
|
||||
this.updateAbsolutePositionedDecorations()
|
||||
@@ -606,21 +605,19 @@ class TextEditorComponent {
|
||||
})
|
||||
}
|
||||
|
||||
if (this.extraLinesToMeasure) {
|
||||
this.extraLinesToMeasure.forEach((screenLine, screenRow) => {
|
||||
if (screenRow < startRow || screenRow >= endRow) {
|
||||
tileNodes.push($(LineComponent, {
|
||||
key: 'extra-' + screenLine.id,
|
||||
screenLine,
|
||||
screenRow,
|
||||
displayLayer,
|
||||
nodePool: this.lineNodesPool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
this.extraRenderedScreenLines.forEach((screenLine, screenRow) => {
|
||||
if (screenRow < startRow || screenRow >= endRow) {
|
||||
tileNodes.push($(LineComponent, {
|
||||
key: 'extra-' + screenLine.id,
|
||||
screenLine,
|
||||
screenRow,
|
||||
displayLayer,
|
||||
nodePool: this.lineNodesPool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
return $.div({
|
||||
key: 'lineTiles',
|
||||
@@ -830,12 +827,22 @@ class TextEditorComponent {
|
||||
const longestLineRow = model.getApproximateLongestScreenRow()
|
||||
const longestLine = model.screenLineForScreenRow(longestLineRow)
|
||||
if (longestLine !== this.previousLongestLine) {
|
||||
this.requestExtraLineToMeasure(longestLineRow, longestLine)
|
||||
this.requestLineToMeasure(longestLineRow, longestLine)
|
||||
this.longestLineToMeasure = longestLine
|
||||
this.previousLongestLine = longestLine
|
||||
}
|
||||
}
|
||||
|
||||
queryExtraScreenLinesToRender () {
|
||||
this.extraRenderedScreenLines.clear()
|
||||
this.linesToMeasure.forEach((screenLine, row) => {
|
||||
if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) {
|
||||
this.extraRenderedScreenLines.set(row, screenLine)
|
||||
}
|
||||
})
|
||||
this.linesToMeasure.clear()
|
||||
}
|
||||
|
||||
queryLineNumbersToRender () {
|
||||
const {model} = this.props
|
||||
if (!model.isLineNumberGutterVisible()) return
|
||||
@@ -906,7 +913,7 @@ class TextEditorComponent {
|
||||
renderedScreenLineForRow (row) {
|
||||
return (
|
||||
this.renderedScreenLines[row - this.getRenderedStartRow()] ||
|
||||
(this.extraRenderedScreenLines ? this.extraRenderedScreenLines.get(row) : null)
|
||||
this.extraRenderedScreenLines.get(row)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1563,6 +1570,10 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
didTextInput (event) {
|
||||
// Workaround for Chromium not preventing composition events when
|
||||
// preventDefault is called on the keydown event that precipitated them.
|
||||
if (this.lastKeydown && this.lastKeydown.defaultPrevented) return
|
||||
|
||||
if (!this.isInputEnabled()) return
|
||||
|
||||
event.stopPropagation()
|
||||
@@ -1619,7 +1630,6 @@ class TextEditorComponent {
|
||||
|
||||
didKeypress (event) {
|
||||
this.lastKeydownBeforeKeypress = this.lastKeydown
|
||||
this.lastKeydown = null
|
||||
|
||||
// This cancels the accented character behavior if we type a key normally
|
||||
// with the menu open.
|
||||
@@ -1629,7 +1639,6 @@ class TextEditorComponent {
|
||||
didKeyup (event) {
|
||||
if (this.lastKeydownBeforeKeypress && this.lastKeydownBeforeKeypress.code === event.code) {
|
||||
this.lastKeydownBeforeKeypress = null
|
||||
this.lastKeydown = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1644,6 +1653,10 @@ class TextEditorComponent {
|
||||
// 4. compositionend fired
|
||||
// 5. textInput fired; event.data == the completion string
|
||||
didCompositionStart () {
|
||||
if (this.getChromeVersion() === 56) {
|
||||
this.getHiddenInput().value = ''
|
||||
}
|
||||
|
||||
this.compositionCheckpoint = this.props.model.createCheckpoint()
|
||||
if (this.accentedCharacterMenuIsOpen) {
|
||||
this.props.model.selectLeft()
|
||||
@@ -1651,7 +1664,20 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
didCompositionUpdate (event) {
|
||||
this.props.model.insertText(event.data, {select: true})
|
||||
// Workaround for Chromium not preventing composition events when
|
||||
// preventDefault is called on the keydown event that precipitated them.
|
||||
if (this.lastKeydown && this.lastKeydown.defaultPrevented) return
|
||||
|
||||
if (this.getChromeVersion() === 56) {
|
||||
process.nextTick(() => {
|
||||
if (this.compositionCheckpoint != null) {
|
||||
const previewText = this.getHiddenInput().value
|
||||
this.props.model.insertText(previewText, {select: true})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.props.model.insertText(event.data, {select: true})
|
||||
}
|
||||
}
|
||||
|
||||
didCompositionEnd (event) {
|
||||
@@ -1663,6 +1689,18 @@ class TextEditorComponent {
|
||||
const {target, button, detail, ctrlKey, shiftKey, metaKey} = event
|
||||
const platform = this.getPlatform()
|
||||
|
||||
// Ignore clicks on block decorations.
|
||||
if (target) {
|
||||
let element = target
|
||||
while (element && element !== this.element) {
|
||||
if (this.blockDecorationsByElement.has(element)) {
|
||||
return
|
||||
}
|
||||
|
||||
element = element.parentElement
|
||||
}
|
||||
}
|
||||
|
||||
// On Linux, position the cursor on middle mouse button click. A
|
||||
// textInput event with the contents of the selection clipboard will be
|
||||
// dispatched by the browser automatically on mouseup.
|
||||
@@ -2046,7 +2084,7 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
measureCharacterDimensions () {
|
||||
this.measurements.lineHeight = this.refs.characterMeasurementLine.getBoundingClientRect().height
|
||||
this.measurements.lineHeight = Math.max(1, this.refs.characterMeasurementLine.getBoundingClientRect().height)
|
||||
this.measurements.baseCharacterWidth = this.refs.normalWidthCharacterSpan.getBoundingClientRect().width
|
||||
this.measurements.doubleWidthCharacterWidth = this.refs.doubleWidthCharacterSpan.getBoundingClientRect().width
|
||||
this.measurements.halfWidthCharacterWidth = this.refs.halfWidthCharacterSpan.getBoundingClientRect().width
|
||||
@@ -2125,29 +2163,24 @@ class TextEditorComponent {
|
||||
}
|
||||
}
|
||||
|
||||
requestExtraLineToMeasure (row, screenLine) {
|
||||
if (!this.extraLinesToMeasure) this.extraLinesToMeasure = new Map()
|
||||
this.extraLinesToMeasure.set(row, screenLine)
|
||||
requestLineToMeasure (row, screenLine) {
|
||||
this.linesToMeasure.set(row, screenLine)
|
||||
}
|
||||
|
||||
requestHorizontalMeasurement (row, column) {
|
||||
if (column === 0) return
|
||||
|
||||
if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) {
|
||||
const screenLine = this.props.model.screenLineForScreenRow(row)
|
||||
if (screenLine) {
|
||||
this.requestExtraLineToMeasure(row, screenLine)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
const screenLine = this.props.model.screenLineForScreenRow(row)
|
||||
if (screenLine) {
|
||||
this.requestLineToMeasure(row, screenLine)
|
||||
|
||||
let columns = this.horizontalPositionsToMeasure.get(row)
|
||||
if (columns == null) {
|
||||
columns = []
|
||||
this.horizontalPositionsToMeasure.set(row, columns)
|
||||
let columns = this.horizontalPositionsToMeasure.get(row)
|
||||
if (columns == null) {
|
||||
columns = []
|
||||
this.horizontalPositionsToMeasure.set(row, columns)
|
||||
}
|
||||
columns.push(column)
|
||||
}
|
||||
columns.push(column)
|
||||
}
|
||||
|
||||
measureHorizontalPositions () {
|
||||
@@ -2260,7 +2293,7 @@ class TextEditorComponent {
|
||||
|
||||
let screenLine = this.renderedScreenLineForRow(row)
|
||||
if (!screenLine) {
|
||||
this.requestExtraLineToMeasure(row, model.screenLineForScreenRow(row))
|
||||
this.requestLineToMeasure(row, model.screenLineForScreenRow(row))
|
||||
this.updateSyncBeforeMeasuringContent()
|
||||
this.measureContentDuringUpdateSync()
|
||||
screenLine = this.renderedScreenLineForRow(row)
|
||||
@@ -2808,9 +2841,17 @@ class TextEditorComponent {
|
||||
return this.props.inputEnabled != null ? this.props.inputEnabled : true
|
||||
}
|
||||
|
||||
getHiddenInput () {
|
||||
return this.refs.cursorsAndInput.refs.hiddenInput
|
||||
}
|
||||
|
||||
getPlatform () {
|
||||
return this.props.platform || process.platform
|
||||
}
|
||||
|
||||
getChromeVersion () {
|
||||
return this.props.chromeVersion || parseInt(process.versions.chrome)
|
||||
}
|
||||
}
|
||||
|
||||
class DummyScrollbarComponent {
|
||||
@@ -2851,20 +2892,22 @@ class DummyScrollbarComponent {
|
||||
outerStyle.bottom = 0
|
||||
outerStyle.left = 0
|
||||
outerStyle.right = right + 'px'
|
||||
outerStyle.height = '20px'
|
||||
outerStyle.height = '15px'
|
||||
outerStyle.overflowY = 'hidden'
|
||||
outerStyle.overflowX = this.props.forceScrollbarVisible ? 'scroll' : 'auto'
|
||||
innerStyle.height = '20px'
|
||||
outerStyle.cursor = 'default'
|
||||
innerStyle.height = '15px'
|
||||
innerStyle.width = (this.props.scrollWidth || 0) + 'px'
|
||||
} else {
|
||||
let bottom = (this.props.horizontalScrollbarHeight || 0)
|
||||
outerStyle.right = 0
|
||||
outerStyle.top = 0
|
||||
outerStyle.bottom = bottom + 'px'
|
||||
outerStyle.width = '20px'
|
||||
outerStyle.width = '15px'
|
||||
outerStyle.overflowX = 'hidden'
|
||||
outerStyle.overflowY = this.props.forceScrollbarVisible ? 'scroll' : 'auto'
|
||||
innerStyle.width = '20px'
|
||||
outerStyle.cursor = 'default'
|
||||
innerStyle.width = '15px'
|
||||
innerStyle.height = (this.props.scrollHeight || 0) + 'px'
|
||||
}
|
||||
|
||||
@@ -3417,8 +3460,10 @@ class CursorsAndInputComponent {
|
||||
|
||||
class LinesTileComponent {
|
||||
constructor (props) {
|
||||
this.highlightComponentsByKey = new Map()
|
||||
this.props = props
|
||||
etch.initialize(this)
|
||||
this.updateHighlights()
|
||||
this.createLines()
|
||||
this.updateBlockDecorations({}, props)
|
||||
}
|
||||
@@ -3432,13 +3477,22 @@ class LinesTileComponent {
|
||||
this.updateLines(oldProps, newProps)
|
||||
this.updateBlockDecorations(oldProps, newProps)
|
||||
}
|
||||
this.updateHighlights()
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.highlightComponentsByKey.forEach((highlightComponent) => {
|
||||
highlightComponent.destroy()
|
||||
})
|
||||
this.highlightComponentsByKey.clear()
|
||||
|
||||
for (let i = 0; i < this.lineComponents.length; i++) {
|
||||
this.lineComponents[i].destroy()
|
||||
}
|
||||
this.lineComponents.length = 0
|
||||
|
||||
return etch.destroy(this)
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -3456,34 +3510,12 @@ class LinesTileComponent {
|
||||
backgroundColor: 'inherit'
|
||||
}
|
||||
},
|
||||
this.renderHighlights()
|
||||
// Lines and block decorations will be manually inserted here for efficiency
|
||||
)
|
||||
}
|
||||
|
||||
renderHighlights () {
|
||||
const {top, lineHeight, highlightDecorations} = this.props
|
||||
|
||||
let children = null
|
||||
if (highlightDecorations) {
|
||||
const decorationCount = highlightDecorations.length
|
||||
children = new Array(decorationCount)
|
||||
for (let i = 0; i < decorationCount; i++) {
|
||||
const highlightProps = Object.assign(
|
||||
{parentTileTop: top, lineHeight},
|
||||
highlightDecorations[i]
|
||||
)
|
||||
children[i] = $(HighlightComponent, highlightProps)
|
||||
highlightDecorations[i].flashRequested = false
|
||||
}
|
||||
}
|
||||
|
||||
return $.div(
|
||||
{
|
||||
$.div({
|
||||
ref: 'highlights',
|
||||
className: 'highlights',
|
||||
style: {contain: 'layout'}
|
||||
},
|
||||
children
|
||||
style: {layout: 'contain'}
|
||||
})
|
||||
// Lines and block decorations will be manually inserted here for efficiency
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3676,6 +3708,40 @@ class LinesTileComponent {
|
||||
}
|
||||
}
|
||||
|
||||
updateHighlights () {
|
||||
const {top, lineHeight, highlightDecorations} = this.props
|
||||
|
||||
const visibleHighlightDecorations = new Set()
|
||||
if (highlightDecorations) {
|
||||
for (let i = 0; i < highlightDecorations.length; i++) {
|
||||
const highlightDecoration = highlightDecorations[i]
|
||||
|
||||
const highlightProps = Object.assign(
|
||||
{parentTileTop: top, lineHeight},
|
||||
highlightDecorations[i]
|
||||
)
|
||||
let highlightComponent = this.highlightComponentsByKey.get(highlightDecoration.key)
|
||||
if (highlightComponent) {
|
||||
highlightComponent.update(highlightProps)
|
||||
} else {
|
||||
highlightComponent = new HighlightComponent(highlightProps)
|
||||
this.refs.highlights.appendChild(highlightComponent.element)
|
||||
this.highlightComponentsByKey.set(highlightDecoration.key, highlightComponent)
|
||||
}
|
||||
|
||||
highlightDecorations[i].flashRequested = false
|
||||
visibleHighlightDecorations.add(highlightDecoration.key)
|
||||
}
|
||||
}
|
||||
|
||||
this.highlightComponentsByKey.forEach((highlightComponent, key) => {
|
||||
if (!visibleHighlightDecorations.has(key)) {
|
||||
highlightComponent.destroy()
|
||||
this.highlightComponentsByKey.delete(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
shouldUpdate (newProps) {
|
||||
const oldProps = this.props
|
||||
if (oldProps.top !== newProps.top) return true
|
||||
@@ -3876,6 +3942,17 @@ class HighlightComponent {
|
||||
if (this.props.flashRequested) this.performFlash()
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if (this.timeoutsByClassName) {
|
||||
this.timeoutsByClassName.forEach((timeout) => {
|
||||
window.clearTimeout(timeout)
|
||||
})
|
||||
this.timeoutsByClassName.clear()
|
||||
}
|
||||
|
||||
return etch.destroy(this)
|
||||
}
|
||||
|
||||
update (newProps) {
|
||||
this.props = newProps
|
||||
etch.updateSync(this)
|
||||
|
||||
@@ -3527,6 +3527,10 @@ class TextEditor extends Model
|
||||
else
|
||||
1
|
||||
|
||||
Object.defineProperty(@prototype, 'rowsPerPage', {
|
||||
get: -> @getRowsPerPage()
|
||||
})
|
||||
|
||||
###
|
||||
Section: Config
|
||||
###
|
||||
|
||||
@@ -1950,6 +1950,7 @@ module.exports = class Workspace extends Model {
|
||||
|
||||
if (!outOfProcessFinished.length) {
|
||||
let flags = 'g'
|
||||
if (regex.multiline) { flags += 'm' }
|
||||
if (regex.ignoreCase) { flags += 'i' }
|
||||
|
||||
const task = Task.once(
|
||||
|
||||
Reference in New Issue
Block a user