Merge branch 'master' into pb-remove-unneeded-files-win

This commit is contained in:
Ian Olsen
2017-08-15 16:12:15 -07:00
16 changed files with 811 additions and 309 deletions

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.18.3"
"atom-package-manager": "1.18.4"
}
}

View File

@@ -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:

View File

@@ -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`.

View File

@@ -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",

View File

@@ -875,10 +875,6 @@
"hasDeprecations": true,
"latestHasDeprecations": false
},
"language-typescript": {
"hasAlternative": true,
"alternative": "atom-typescript"
},
"laravel-facades": {
"version": "<=1.0.0",
"hasDeprecations": true,

View File

@@ -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",

View File

@@ -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))

View File

@@ -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)
})
})
})

View File

@@ -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))
}

View File

@@ -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: []}

View File

@@ -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', () => {

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -3527,6 +3527,10 @@ class TextEditor extends Model
else
1
Object.defineProperty(@prototype, 'rowsPerPage', {
get: -> @getRowsPerPage()
})
###
Section: Config
###

View File

@@ -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(