Merge branch 'master' into asar

Conflicts:
	apm/package.json
This commit is contained in:
Cheng Zhao 2015-04-02 10:08:33 +08:00
commit 32f2e4c4bc
23 changed files with 533 additions and 306 deletions

View File

@ -6,7 +6,7 @@ The following is a set of guidelines for contributing to Atom and its packages,
which are hosted in the [Atom Organization](https://github.com/atom) on GitHub.
If you're unsure which package is causing your problem or if you're having an
issue with Atom core, please open an issue on the [main atom repository](https://github.com/atom/atom/issues).
These are just guidelines, not rules, use your best judgement and feel free to
These are just guidelines, not rules, use your best judgment and feel free to
propose changes to this document in a pull request.
## Submitting Issues
@ -48,7 +48,7 @@ For more information on how to work with Atom's official packages, see
[JavaScript](https://github.com/styleguide/javascript),
and [CSS](https://github.com/styleguide/css) styleguides.
* Include thoughtfully-worded, well-structured
[Jasmine](http://jasmine.github.io/) specs in the `./spec` folder. Run them using `apm test`.
[Jasmine](http://jasmine.github.io/) specs in the `./spec` folder. Run them using `apm test`. See the [Specs Styleguide](#specs-styleguide) below.
* Document new code based on the
[Documentation Styleguide](#documentation-styleguide)
* End files with a newline.
@ -108,6 +108,24 @@ For more information on how to work with Atom's official packages, see
* Add an explicit `return` when your function ends with a `for`/`while` loop and
you don't want it to return a collected array.
## Specs Styleguide
- Include thoughtfully-worded, well-structured
[Jasmine](http://jasmine.github.io/) specs in the `./spec` folder.
- treat `describe` as a noun or situation.
- treat `it` as a statement about state or how an operation changes state.
### Example
```coffee
describe 'a dog', ->
it 'barks', ->
# spec here
describe 'when the dog is happy', ->
it 'wags its tail', ->
# spec here
```
## Documentation Styleguide
* Use [AtomDoc](https://github.com/atom/atomdoc).

View File

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

View File

@ -13,7 +13,7 @@
"fs-plus": "2.x",
"github-releases": "~0.2.0",
"grunt": "~0.4.1",
"grunt-atom-shell-installer": "^0.25.0",
"grunt-atom-shell-installer": "^0.28.0",
"grunt-cli": "~0.1.9",
"grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git#cfb99aa99811d52687969532bd5a98011ed95bfe",
"grunt-contrib-coffee": "~0.12.0",

View File

@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "0.189.0",
"version": "0.190.0",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@ -17,10 +17,10 @@
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
}
],
"atomShellVersion": "0.22.2",
"atomShellVersion": "0.22.3",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^4",
"atom-keymap": "^5",
"atom-space-pen-views": "^2.0.4",
"babel-core": "^4.0.2",
"bootstrap": "git+https://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
@ -90,30 +90,30 @@
"bookmarks": "0.35.0",
"bracket-matcher": "0.73.0",
"command-palette": "0.34.0",
"deprecation-cop": "0.38.0",
"deprecation-cop": "0.39.0",
"dev-live-reload": "0.45.0",
"encoding-selector": "0.19.0",
"exception-reporting": "0.24.0",
"feedback": "0.36.0",
"feedback": "0.38.0",
"find-and-replace": "0.159.0",
"fuzzy-finder": "0.72.0",
"git-diff": "0.54.0",
"go-to-line": "0.30.0",
"grammar-selector": "0.46.0",
"image-view": "0.53.0",
"image-view": "0.54.0",
"incompatible-packages": "0.24.0",
"keybinding-resolver": "0.29.0",
"link": "0.30.0",
"markdown-preview": "0.145.0",
"markdown-preview": "0.146.0",
"metrics": "0.45.0",
"notifications": "0.35.0",
"open-on-github": "0.36.0",
"package-generator": "0.38.0",
"release-notes": "0.52.0",
"settings-view": "0.186.0",
"snippets": "0.86.0",
"settings-view": "0.187.0",
"snippets": "0.87.0",
"spell-check": "0.55.0",
"status-bar": "0.64.0",
"status-bar": "0.66.0",
"styleguide": "0.44.0",
"symbols-view": "0.93.0",
"tabs": "0.67.0",
@ -131,19 +131,19 @@
"language-gfm": "0.67.0",
"language-git": "0.10.0",
"language-go": "0.22.0",
"language-html": "0.30.0",
"language-html": "0.31.0",
"language-hyperlink": "0.12.2",
"language-java": "0.14.0",
"language-javascript": "0.64.0",
"language-javascript": "0.67.0",
"language-json": "0.14.0",
"language-less": "0.25.0",
"language-make": "0.14.0",
"language-mustache": "0.11.0",
"language-objective-c": "0.15.0",
"language-perl": "0.21.0",
"language-php": "0.21.0",
"language-perl": "0.22.0",
"language-php": "0.22.0",
"language-property-list": "0.8.0",
"language-python": "0.32.0",
"language-python": "0.33.0",
"language-ruby": "0.50.0",
"language-ruby-on-rails": "0.21.0",
"language-sass": "0.36.0",

View File

@ -46,8 +46,38 @@ function removeNodeModules() {
}
}
function removeTempFolders() {
var fsPlus;
try {
fsPlus = require('fs-plus');
} catch (error) {
return;
}
var temp = require('os').tmpdir();
if (!fsPlus.isDirectorySync(temp))
return;
var deletedFolders = 0;
fsPlus.readdirSync(temp).filter(function(folderName) {
return folderName.indexOf('npm-') === 0;
}).forEach(function(folderName) {
try {
fsPlus.removeSync(path.join(temp, folderName));
deletedFolders++;
} catch (error) {
console.error("Failed to delete npm temp folder: " + error.message);
}
});
if (deletedFolders > 0)
console.log("Deleted " + deletedFolders + " npm folders from temp directory");
}
readEnvironmentVariables();
removeNodeModules();
removeTempFolders();
cp.safeExec.bind(global, 'npm install npm --loglevel error', {cwd: path.resolve(__dirname, '..', 'build')}, function() {
cp.safeExec.bind(global, 'node script/bootstrap', function(error) {
if (error)

View File

@ -638,8 +638,11 @@ describe "DisplayBuffer", ->
expect(displayBuffer.outermostFoldsInBufferRowRange(3, 18)).toEqual [fold1, fold3, fold5]
expect(displayBuffer.outermostFoldsInBufferRowRange(5, 16)).toEqual [fold3]
describe "::clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", ->
describe "::clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, clip: 'closest')", ->
beforeEach ->
tabLength = 4
displayBuffer.setTabLength(tabLength)
displayBuffer.setSoftWrapped(true)
displayBuffer.setEditorWidthInChars(50)
@ -698,19 +701,28 @@ describe "DisplayBuffer", ->
expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 4]
expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 4]
describe "when skipAtomicTokens is false (the default)", ->
it "clips screen positions in the middle of atomic tab characters to the beginning of the character", ->
describe "when clip is 'closest' (the default)", ->
it "clips screen positions in the middle of atomic tab characters to the closest edge of the character", ->
buffer.insert([0, 0], '\t')
expect(displayBuffer.clipScreenPosition([0, 0])).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 1])).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 2])).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, tabLength-1])).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength])).toEqual [0, tabLength]
describe "when skipAtomicTokens is true", ->
describe "when clip is 'backward'", ->
it "clips screen positions in the middle of atomic tab characters to the beginning of the character", ->
buffer.insert([0, 0], '\t')
expect(displayBuffer.clipScreenPosition([0, 0], clip: 'backward')).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, tabLength-1], clip: 'backward')).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, tabLength], clip: 'backward')).toEqual [0, tabLength]
describe "when clip is 'forward'", ->
it "clips screen positions in the middle of atomic tab characters to the end of the character", ->
buffer.insert([0, 0], '\t')
expect(displayBuffer.clipScreenPosition([0, 0], skipAtomicTokens: true)).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 1], skipAtomicTokens: true)).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength], skipAtomicTokens: true)).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, 0], clip: 'forward')).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 1], clip: 'forward')).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength], clip: 'forward')).toEqual [0, tabLength]
describe "::screenPositionForBufferPosition(bufferPosition, options)", ->
it "clips the specified buffer position", ->
@ -719,6 +731,25 @@ describe "DisplayBuffer", ->
expect(displayBuffer.screenPositionForBufferPosition([100000, 0])).toEqual [12, 2]
expect(displayBuffer.screenPositionForBufferPosition([100000, 100000])).toEqual [12, 2]
it "clips to the (left or right) edge of an atomic token without simply rounding up", ->
tabLength = 4
displayBuffer.setTabLength(tabLength)
buffer.insert([0, 0], '\t')
expect(displayBuffer.screenPositionForBufferPosition([0, 0])).toEqual [0, 0]
expect(displayBuffer.screenPositionForBufferPosition([0, 1])).toEqual [0, tabLength]
it "clips to the edge closest to the given position when it's inside a soft tab", ->
tabLength = 4
displayBuffer.setTabLength(tabLength)
buffer.insert([0, 0], ' ')
expect(displayBuffer.screenPositionForBufferPosition([0, 0])).toEqual [0, 0]
expect(displayBuffer.screenPositionForBufferPosition([0, 1])).toEqual [0, 0]
expect(displayBuffer.screenPositionForBufferPosition([0, 2])).toEqual [0, 0]
expect(displayBuffer.screenPositionForBufferPosition([0, 3])).toEqual [0, 4]
expect(displayBuffer.screenPositionForBufferPosition([0, 4])).toEqual [0, 4]
describe "position translation in the presence of hard tabs", ->
it "correctly translates positions on either side of a tab", ->
buffer.setText('\t')

View File

@ -370,7 +370,7 @@ window.setEditorWidthInChars = (editorView, widthInChars, charWidth=editorView.c
window.setEditorHeightInLines = (editorView, heightInLines, lineHeight=editorView.lineHeight) ->
editorView.height(editorView.getEditor().getLineHeightInPixels() * heightInLines)
editorView.component?.measureHeightAndWidth()
editorView.component?.measureDimensions()
$.fn.resultOfTrigger = (type) ->
event = $.Event(type)

View File

@ -50,7 +50,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode = componentNode.querySelector('.vertical-scrollbar')
horizontalScrollbarNode = componentNode.querySelector('.horizontal-scrollbar')
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
afterEach ->
@ -70,7 +70,7 @@ describe "TextEditorComponent", ->
describe "line rendering", ->
it "renders the currently-visible lines plus the overdraw margin", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
linesNode = componentNode.querySelector('.lines')
@ -113,7 +113,7 @@ describe "TextEditorComponent", ->
it "updates the lines when lines are inserted or removed above the rendered row range", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
verticalScrollbarNode.scrollTop = 5 * lineHeightInPixels
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
@ -163,7 +163,7 @@ describe "TextEditorComponent", ->
it "renders the .lines div at the full height of the editor if there aren't enough lines to scroll vertically", ->
editor.setText('')
wrapperNode.style.height = '300px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
linesNode = componentNode.querySelector('.lines')
@ -175,7 +175,7 @@ describe "TextEditorComponent", ->
lineNodes = componentNode.querySelectorAll('.line')
componentNode.style.width = gutterWidth + (30 * charWidth) + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(editor.getScrollWidth()).toBeGreaterThan scrollViewNode.offsetWidth
@ -187,7 +187,7 @@ describe "TextEditorComponent", ->
expect(lineNode.style.width).toBe editor.getScrollWidth() + 'px'
componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
scrollViewWidth = scrollViewNode.offsetWidth
@ -339,7 +339,7 @@ describe "TextEditorComponent", ->
editor.setSoftWrapped(true)
nextAnimationFrame()
componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
it "doesn't show end of line invisibles at the end of wrapped lines", ->
@ -480,7 +480,7 @@ describe "TextEditorComponent", ->
describe "gutter rendering", ->
it "renders the currently-visible line numbers", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number
@ -524,7 +524,7 @@ describe "TextEditorComponent", ->
editor.setSoftWrapped(true)
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 30 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line componentNode
@ -562,7 +562,7 @@ describe "TextEditorComponent", ->
it "renders the .line-numbers div at the full height of the editor even if it's taller than its content", ->
wrapperNode.style.height = componentNode.offsetHeight + 100 + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(componentNode.querySelector('.line-numbers').offsetHeight).toBe componentNode.offsetHeight
@ -653,7 +653,7 @@ describe "TextEditorComponent", ->
editor.setSoftWrapped(true)
nextAnimationFrame()
componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
it "doesn't add the foldable class for soft-wrapped lines", ->
@ -697,7 +697,7 @@ describe "TextEditorComponent", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 20 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
cursorNodes = componentNode.querySelectorAll('.cursor')
@ -992,7 +992,7 @@ describe "TextEditorComponent", ->
# Shrink editor vertically
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
# Add decorations that are out of range
@ -1016,7 +1016,7 @@ describe "TextEditorComponent", ->
editor.setText("a line that wraps, ok")
editor.setSoftWrapped(true)
componentNode.style.width = 16 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
marker.destroy()
@ -1132,7 +1132,7 @@ describe "TextEditorComponent", ->
it "does not render highlights for off-screen lines until they come on-screen", ->
wrapperNode.style.height = 2.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
marker = editor.displayBuffer.markBufferRange([[9, 2], [9, 4]], invalidate: 'inside')
@ -1279,10 +1279,12 @@ describe "TextEditorComponent", ->
expect(componentNode.querySelector('.new-test-highlight')).toBeTruthy()
describe "overlay decoration rendering", ->
[item] = []
[item, gutterWidth] = []
beforeEach ->
item = document.createElement('div')
item.classList.add 'overlay-test'
item.style.background = 'red'
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
describe "when the marker is empty", ->
it "renders an overlay decoration when added and removes the overlay when the decoration is destroyed", ->
@ -1299,71 +1301,29 @@ describe "TextEditorComponent", ->
overlay = component.getTopmostDOMNode().querySelector('atom-overlay .overlay-test')
expect(overlay).toBe null
it "renders in the correct position on initial display and when the marker moves", ->
editor.setCursorBufferPosition([2, 5])
marker = editor.getLastCursor().getMarker()
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([2, 5])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
editor.moveRight()
editor.moveRight()
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([2, 7])
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
describe "when the marker is not empty", ->
it "renders at the head of the marker by default", ->
marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([2, 10])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
it "renders at the head of the marker when the marker is reversed", ->
marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], invalidate: 'never', reversed: true)
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([2, 5])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
it "renders at the tail of the marker when the 'position' option is 'tail'", ->
marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', position: 'tail', item})
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([2, 5])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.left).toBe position.left + gutterWidth + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
describe "positioning the overlay when near the edge of the editor", ->
[itemWidth, itemHeight] = []
[itemWidth, itemHeight, windowWidth, windowHeight] = []
beforeEach ->
atom.storeWindowDimensions()
itemWidth = 4 * editor.getDefaultCharWidth()
itemHeight = 4 * editor.getLineHeightInPixels()
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
windowWidth = gutterWidth + 30 * editor.getDefaultCharWidth()
windowHeight = 9 * editor.getLineHeightInPixels()
windowHeight = 10 * editor.getLineHeightInPixels()
item.style.width = itemWidth + 'px'
item.style.height = itemHeight + 'px'
@ -1371,139 +1331,39 @@ describe "TextEditorComponent", ->
wrapperNode.style.width = windowWidth + 'px'
wrapperNode.style.height = windowHeight + 'px'
component.measureHeightAndWidth()
atom.setWindowDimensions({width: windowWidth, height: windowHeight})
component.measureDimensions()
component.measureWindowSize()
nextAnimationFrame()
it "flips horizontally when near the right edge", ->
afterEach ->
atom.restoreWindowDimensions()
it "slides horizontally left when near the right edge", ->
marker = editor.displayBuffer.markBufferRange([[0, 26], [0, 26]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([0, 26])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.left).toBe position.left + gutterWidth + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
editor.insertText('a')
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([0, 27])
expect(overlay.style.left).toBe position.left - itemWidth + 'px'
expect(overlay.style.left).toBe windowWidth - itemWidth + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
it "flips vertically when near the bottom edge", ->
marker = editor.displayBuffer.markBufferRange([[4, 0], [4, 0]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
editor.insertText('b')
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([4, 0])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.left).toBe windowWidth - itemWidth + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
editor.insertNewline()
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([5, 0])
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top - itemHeight + 'px'
describe "when the editor is very small", ->
beforeEach ->
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
windowWidth = gutterWidth + 6 * editor.getDefaultCharWidth()
windowHeight = 6 * editor.getLineHeightInPixels()
wrapperNode.style.width = windowWidth + 'px'
wrapperNode.style.height = windowHeight + 'px'
component.measureHeightAndWidth()
nextAnimationFrame()
it "does not flip horizontally and force the overlay to have a negative left", ->
marker = editor.displayBuffer.markBufferRange([[0, 2], [0, 2]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([0, 2])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
editor.insertText('a')
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([0, 3])
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
it "does not flip vertically and force the overlay to have a negative top", ->
marker = editor.displayBuffer.markBufferRange([[1, 0], [1, 0]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([1, 0])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
editor.insertNewline()
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([2, 0])
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
describe "when editor scroll position is not 0", ->
it "flips horizontally when near the right edge", ->
editor.setScrollLeft(2 * editor.getDefaultCharWidth())
marker = editor.displayBuffer.markBufferRange([[0, 28], [0, 28]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([0, 28])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
editor.insertText('a')
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([0, 29])
expect(overlay.style.left).toBe position.left - itemWidth + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
it "flips vertically when near the bottom edge", ->
editor.setScrollTop(2 * editor.getLineHeightInPixels())
marker = editor.displayBuffer.markBufferRange([[6, 0], [6, 0]], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([6, 0])
overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'
editor.insertNewline()
nextAnimationFrame()
position = wrapperNode.pixelPositionForBufferPosition([7, 0])
expect(overlay.style.left).toBe position.left + 'px'
expect(overlay.style.top).toBe position.top - itemHeight + 'px'
describe "hidden input field", ->
it "renders the hidden input field at the position of the last cursor if the cursor is on screen and the editor is focused", ->
editor.setVerticalScrollMargin(0)
@ -1512,7 +1372,7 @@ describe "TextEditorComponent", ->
inputNode = componentNode.querySelector('.hidden-input')
wrapperNode.style.height = 5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(editor.getCursorScreenPosition()).toEqual [0, 0]
@ -1566,7 +1426,7 @@ describe "TextEditorComponent", ->
height = 4.5 * lineHeightInPixels
wrapperNode.style.height = height + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
coordinates = clientCoordinatesForScreenPosition([0, 2])
@ -1580,7 +1440,7 @@ describe "TextEditorComponent", ->
it "moves the cursor to the nearest screen position", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
editor.setScrollTop(3.5 * lineHeightInPixels)
editor.setScrollLeft(2 * charWidth)
nextAnimationFrame()
@ -1863,7 +1723,7 @@ describe "TextEditorComponent", ->
editor.setSoftWrapped(true)
nextAnimationFrame()
componentNode.style.width = 21 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
describe "when the gutter is clicked", ->
@ -2029,7 +1889,7 @@ describe "TextEditorComponent", ->
describe "scrolling", ->
it "updates the vertical scrollbar when the scrollTop is changed in the model", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(verticalScrollbarNode.scrollTop).toBe 0
@ -2040,7 +1900,7 @@ describe "TextEditorComponent", ->
it "updates the horizontal scrollbar and the x transform of the lines based on the scrollLeft of the model", ->
componentNode.style.width = 30 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
linesNode = componentNode.querySelector('.lines')
@ -2054,7 +1914,7 @@ describe "TextEditorComponent", ->
it "updates the scrollLeft of the model when the scrollLeft of the horizontal scrollbar changes", ->
componentNode.style.width = 30 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(editor.getScrollLeft()).toBe 0
@ -2067,7 +1927,7 @@ describe "TextEditorComponent", ->
it "does not obscure the last line with the horizontal scrollbar", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
editor.setScrollBottom(editor.getScrollHeight())
nextAnimationFrame()
lastLineNode = component.lineNodeForScreenRow(editor.getLastScreenRow())
@ -2077,7 +1937,7 @@ describe "TextEditorComponent", ->
# Scroll so there's no space below the last line when the horizontal scrollbar disappears
wrapperNode.style.width = 100 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
bottomOfLastLine = lastLineNode.getBoundingClientRect().bottom
bottomOfEditor = componentNode.getBoundingClientRect().bottom
@ -2086,7 +1946,7 @@ describe "TextEditorComponent", ->
it "does not obscure the last character of the longest line with the vertical scrollbar", ->
wrapperNode.style.height = 7 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
editor.setScrollLeft(Infinity)
nextAnimationFrame()
@ -2100,21 +1960,21 @@ describe "TextEditorComponent", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = '1000px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(verticalScrollbarNode.style.display).toBe ''
expect(horizontalScrollbarNode.style.display).toBe 'none'
componentNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(verticalScrollbarNode.style.display).toBe ''
expect(horizontalScrollbarNode.style.display).toBe ''
wrapperNode.style.height = 20 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(verticalScrollbarNode.style.display).toBe 'none'
@ -2123,7 +1983,7 @@ describe "TextEditorComponent", ->
it "makes the dummy scrollbar divs only as tall/wide as the actual scrollbars", ->
wrapperNode.style.height = 4 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
atom.styles.addStyleSheet """
@ -2152,21 +2012,21 @@ describe "TextEditorComponent", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = '1000px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(verticalScrollbarNode.style.bottom).toBe '0px'
expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px'
expect(scrollbarCornerNode.style.display).toBe 'none'
componentNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px'
expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px'
expect(scrollbarCornerNode.style.display).toBe ''
wrapperNode.style.height = 20 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px'
expect(horizontalScrollbarNode.style.right).toBe '0px'
@ -2175,7 +2035,7 @@ describe "TextEditorComponent", ->
it "accounts for the width of the gutter in the scrollWidth of the horizontal scrollbar", ->
gutterNode = componentNode.querySelector('.gutter')
componentNode.style.width = 10 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(horizontalScrollbarNode.scrollWidth).toBe editor.getScrollWidth()
@ -2189,7 +2049,7 @@ describe "TextEditorComponent", ->
beforeEach ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 20 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
it "updates the scrollLeft or scrollTop on mousewheel events depending on which delta is greater (x or y)", ->
@ -2233,7 +2093,7 @@ describe "TextEditorComponent", ->
it "keeps the line on the DOM if it is scrolled off-screen", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 20 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
lineNode = componentNode.querySelector('.line')
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -500)
@ -2246,7 +2106,7 @@ describe "TextEditorComponent", ->
it "does not set the mouseWheelScreenRow if scrolling horizontally", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 20 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
lineNode = componentNode.querySelector('.line')
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 10, wheelDeltaY: 0)
@ -2289,7 +2149,7 @@ describe "TextEditorComponent", ->
it "keeps the line number on the DOM if it is scrolled off-screen", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 20 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
lineNumberNode = componentNode.querySelectorAll('.line-number')[1]
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -500)
@ -2304,7 +2164,7 @@ describe "TextEditorComponent", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 20 * charWidth + 'px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
# try to scroll past the top, which is impossible
@ -2374,6 +2234,7 @@ describe "TextEditorComponent", ->
expect(editor.lineTextForBufferRow(0)).toBe 'üvar quicksort = function () {'
it "does not handle input events when input is disabled", ->
nextAnimationFrame = noAnimationFrame # This spec is flaky on the build machine, so this.
component.setInputEnabled(false)
componentNode.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode))
expect(nextAnimationFrame).toBe noAnimationFrame
@ -2700,7 +2561,7 @@ describe "TextEditorComponent", ->
describe "when the wrapper view has an explicit height", ->
it "does not assign a height on the component node", ->
wrapperNode.style.height = '200px'
component.measureHeightAndWidth()
component.measureDimensions()
nextAnimationFrame()
expect(componentNode.style.height).toBe ''

View File

@ -29,6 +29,9 @@ describe "TextEditorPresenter", ->
model: editor
explicitHeight: 130
contentFrameWidth: 500
windowWidth: 500
windowHeight: 130
boundingClientRect: {left: 0, top: 0, width: 500, height: 130}
lineHeight: 10
baseCharacterWidth: 10
horizontalScrollbarHeight: 10
@ -1485,11 +1488,11 @@ describe "TextEditorPresenter", ->
}
describe ".overlays", ->
[item] = []
stateForOverlay = (presenter, decoration) ->
presenter.getState().content.overlays[decoration.id]
it "contains state for overlay decorations both initially and when their markers move", ->
item = {}
marker = editor.markBufferPosition([2, 13], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
@ -1497,14 +1500,14 @@ describe "TextEditorPresenter", ->
# Initial state
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 10, left: 13 * 10}
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
}
# Change range
expectStateUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]])
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 4 * 10, left: 6 * 10}
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
}
# Valid -> invalid
@ -1515,14 +1518,14 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> editor.undo()
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 4 * 10, left: 6 * 10}
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
}
# Reverse direction
expectStateUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]], reversed: true)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 10, left: 13 * 10}
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
}
# Destroy
@ -1533,69 +1536,237 @@ describe "TextEditorPresenter", ->
decoration2 = editor.decorateMarker(marker, {type: 'overlay', item})
expectValues stateForOverlay(presenter, decoration2), {
item: item
pixelPosition: {top: 2 * 10, left: 13 * 10}
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
}
it "updates when ::baseCharacterWidth changes", ->
item = {}
scrollTop = 20
marker = editor.markBufferPosition([2, 13], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter({explicitHeight: 30, scrollTop})
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 10, left: 13 * 10}
pixelPosition: {top: 3 * 10 - scrollTop, left: 13 * 10}
}
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(5)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 10, left: 13 * 5}
pixelPosition: {top: 3 * 10 - scrollTop, left: 13 * 5}
}
it "updates when ::lineHeight changes", ->
item = {}
scrollTop = 20
marker = editor.markBufferPosition([2, 13], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter({explicitHeight: 30, scrollTop})
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 10, left: 13 * 10}
pixelPosition: {top: 3 * 10 - scrollTop, left: 13 * 10}
}
expectStateUpdate presenter, -> presenter.setLineHeight(5)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 5, left: 13 * 10}
pixelPosition: {top: 3 * 5 - scrollTop, left: 13 * 10}
}
it "honors the 'position' option on overlay decorations", ->
item = {}
scrollTop = 20
marker = editor.markBufferRange([[2, 13], [4, 14]], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', position: 'tail', item})
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter({explicitHeight: 30, scrollTop})
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 10, left: 13 * 10}
pixelPosition: {top: 3 * 10 - scrollTop, left: 13 * 10}
}
it "is empty until all of the required measurements are assigned", ->
item = {}
marker = editor.markBufferRange([[2, 13], [4, 14]], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', position: 'tail', item})
presenter = buildPresenter(baseCharacterWidth: null, lineHeight: null)
presenter = buildPresenter(baseCharacterWidth: null, lineHeight: null, windowWidth: null, windowHeight: null, boundingClientRect: null)
expect(presenter.getState().content.overlays).toEqual({})
presenter.setBaseCharacterWidth(10)
expect(presenter.getState().content.overlays).toEqual({})
presenter.setLineHeight(10)
expect(presenter.getState().content.overlays).toEqual({})
presenter.setWindowSize(500, 100)
expect(presenter.getState().content.overlays).toEqual({})
presenter.setBoundingClientRect({top: 0, left: 0, height: 100, width: 500})
expect(presenter.getState().content.overlays).not.toEqual({})
describe "when the overlay has been measured", ->
[gutterWidth, windowWidth, windowHeight, itemWidth, itemHeight, contentMargin, boundingClientRect, contentFrameWidth] = []
beforeEach ->
item = {}
gutterWidth = 5 * 10 # 5 chars wide
contentFrameWidth = 30 * 10
windowWidth = gutterWidth + contentFrameWidth
windowHeight = 9 * 10
itemWidth = 4 * 10
itemHeight = 4 * 10
contentMargin = 0
boundingClientRect =
top: 0
left: 0,
width: windowWidth
height: windowHeight
it "slides horizontally left when near the right edge", ->
scrollLeft = 20
marker = editor.markBufferPosition([0, 26], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter({scrollLeft, windowWidth, windowHeight, contentFrameWidth, boundingClientRect})
expectStateUpdate presenter, ->
presenter.setOverlayDimensions(decoration.id, itemWidth, itemHeight, contentMargin)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: 26 * 10 + gutterWidth - scrollLeft}
}
expectStateUpdate presenter, -> editor.insertText('a')
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: windowWidth - itemWidth}
}
expectStateUpdate presenter, -> editor.insertText('b')
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: windowWidth - itemWidth}
}
it "flips vertically when near the bottom edge", ->
scrollTop = 10
marker = editor.markBufferPosition([5, 0], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter({scrollTop, windowWidth, windowHeight, contentFrameWidth, boundingClientRect})
expectStateUpdate presenter, ->
presenter.setOverlayDimensions(decoration.id, itemWidth, itemHeight, contentMargin)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 6 * 10 - scrollTop, left: gutterWidth}
}
expectStateUpdate presenter, ->
editor.insertNewline()
editor.setScrollTop(scrollTop) # I'm fighting the editor
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 6 * 10 - scrollTop - itemHeight, left: gutterWidth}
}
describe "when the overlay item has a margin", ->
beforeEach ->
itemWidth = 12 * 10
contentMargin = -(gutterWidth + 2 * 10)
it "slides horizontally right when near the left edge with margin", ->
editor.setCursorBufferPosition([0, 3])
cursor = editor.getLastCursor()
marker = cursor.marker
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter({windowWidth, windowHeight, contentFrameWidth, boundingClientRect})
expectStateUpdate presenter, ->
presenter.setOverlayDimensions(decoration.id, itemWidth, itemHeight, contentMargin)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: 3 * 10 + gutterWidth}
}
expectStateUpdate presenter, -> cursor.moveLeft()
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: -contentMargin}
}
expectStateUpdate presenter, -> cursor.moveLeft()
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: -contentMargin}
}
describe "when the editor is very small", ->
beforeEach ->
windowWidth = gutterWidth + 6 * 10
windowHeight = 6 * 10
contentFrameWidth = windowWidth - gutterWidth
boundingClientRect.width = windowWidth
boundingClientRect.height = windowHeight
it "does not flip vertically and force the overlay to have a negative top", ->
marker = editor.markBufferPosition([1, 0], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter({windowWidth, windowHeight, contentFrameWidth, boundingClientRect})
expectStateUpdate presenter, ->
presenter.setOverlayDimensions(decoration.id, itemWidth, itemHeight, contentMargin)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 2 * 10, left: 0 * 10 + gutterWidth}
}
expectStateUpdate presenter, -> editor.insertNewline()
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 3 * 10, left: gutterWidth}
}
it "does not adjust horizontally and force the overlay to have a negative left", ->
itemWidth = 6 * 10
marker = editor.markBufferPosition([0, 0], invalidate: 'never')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
presenter = buildPresenter({windowWidth, windowHeight, contentFrameWidth, boundingClientRect})
expectStateUpdate presenter, ->
presenter.setOverlayDimensions(decoration.id, itemWidth, itemHeight, contentMargin)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 10, left: gutterWidth}
}
windowWidth = gutterWidth + 5 * 10
expectStateUpdate presenter, -> presenter.setWindowSize(windowWidth, windowHeight)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 10, left: windowWidth - itemWidth}
}
windowWidth = gutterWidth + 1 * 10
expectStateUpdate presenter, -> presenter.setWindowSize(windowWidth, windowHeight)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 10, left: 0}
}
windowWidth = gutterWidth
expectStateUpdate presenter, -> presenter.setWindowSize(windowWidth, windowHeight)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 10, left: 0}
}
describe ".gutter", ->
describe ".scrollHeight", ->
it "is initialized based on ::lineHeight, the number of lines, and ::explicitHeight", ->

View File

@ -137,6 +137,21 @@ describe "ViewRegistry", ->
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['write', 'read', 'poll', 'poll']
it "polls the document after updating when ::pollAfterNextUpdate() has been called", ->
events = []
registry.pollDocument -> events.push('poll')
registry.updateDocument -> events.push('write')
registry.readDocument -> events.push('read')
frameRequests.shift()()
expect(events).toEqual ['write', 'read']
events = []
registry.pollAfterNextUpdate()
registry.updateDocument -> events.push('write')
registry.readDocument -> events.push('read')
frameRequests.shift()()
expect(events).toEqual ['write', 'read', 'poll']
describe "::pollDocument(fn)", ->
it "calls all registered reader functions on an interval until they are disabled via a returned disposable", ->
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)

View File

@ -94,8 +94,6 @@ class Cursor extends Model
Grim.deprecate("Use Cursor::onDidChangePosition instead")
when 'destroyed'
Grim.deprecate("Use Cursor::onDidDestroy instead")
when 'destroyed'
Grim.deprecate("Use Cursor::onDidDestroy instead")
else
Grim.deprecate("::on is no longer supported. Use the event subscription methods instead")
super
@ -305,7 +303,7 @@ class Cursor extends Model
columnCount-- # subtract 1 for the row move
column = column - columnCount
@setScreenPosition({row, column})
@setScreenPosition({row, column}, clip: 'backward')
# Public: Moves the cursor right one screen column.
#
@ -332,7 +330,7 @@ class Cursor extends Model
columnsRemainingInLine = rowLength
column = column + columnCount
@setScreenPosition({row, column}, skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
@setScreenPosition({row, column}, clip: 'forward', wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
# Public: Moves the cursor to the top of the buffer.
moveToTop: ->

View File

@ -20,7 +20,7 @@ nextId = -> idCounter++
# decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'})
# ```
#
# Best practice for destorying the decoration is by destroying the {Marker}.
# Best practice for destroying the decoration is by destroying the {Marker}.
#
# ```coffee
# marker.destroy()
@ -56,7 +56,6 @@ class Decoration
@properties.id = @id
@flashQueue = null
@destroyed = false
@markerDestroyDisposable = @marker.onDidDestroy => @destroy()
# Essential: Destroy this marker.

View File

@ -4,7 +4,6 @@ _ = require 'underscore-plus'
CursorsComponent = require './cursors-component'
HighlightsComponent = require './highlights-component'
OverlayManager = require './overlay-manager'
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
@ -40,13 +39,6 @@ class LinesComponent
insertionPoint.setAttribute('select', '.overlayer')
@domNode.appendChild(insertionPoint)
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@overlayManager = new OverlayManager(@presenter, @hostElement)
@domNode.appendChild(insertionPoint)
else
@overlayManager = new OverlayManager(@presenter, @domNode)
updateSync: (state) ->
@newState = state.content
@oldState ?= {lines: {}}
@ -82,8 +74,6 @@ class LinesComponent
@cursorsComponent.updateSync(state)
@highlightsComponent.updateSync(state)
@overlayManager?.render(state)
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth

View File

@ -286,7 +286,6 @@ class Marker
# * `screenPosition` The new {Point} to use
# * `properties` (optional) {Object} properties to associate with the marker.
setHeadScreenPosition: (screenPosition, properties) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, properties)
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, properties))
# Extended: Retrieves the buffer position of the marker's tail.
@ -313,7 +312,6 @@ class Marker
# * `screenPosition` The new {Point} to use
# * `properties` (optional) {Object} properties to associate with the marker.
setTailScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
# Extended: Returns a {Boolean} indicating whether the marker has a tail.

View File

@ -1,39 +1,44 @@
module.exports =
class OverlayManager
constructor: (@presenter, @container) ->
@overlayNodesById = {}
@overlaysById = {}
render: (state) ->
for decorationId, {pixelPosition, item} of state.content.overlays
@renderOverlay(state, decorationId, item, pixelPosition)
for decorationId, overlay of state.content.overlays
if @shouldUpdateOverlay(decorationId, overlay)
@renderOverlay(state, decorationId, overlay)
for id, overlayNode of @overlayNodesById
for id, {overlayNode} of @overlaysById
unless state.content.overlays.hasOwnProperty(id)
delete @overlayNodesById[id]
delete @overlaysById[id]
overlayNode.remove()
return
shouldUpdateOverlay: (decorationId, overlay) ->
cachedOverlay = @overlaysById[decorationId]
return true unless cachedOverlay?
cachedOverlay.pixelPosition?.top isnt overlay.pixelPosition?.top or
cachedOverlay.pixelPosition?.left isnt overlay.pixelPosition?.left
renderOverlay: (state, decorationId, item, pixelPosition) ->
item = atom.views.getView(item)
unless overlayNode = @overlayNodesById[decorationId]
overlayNode = @overlayNodesById[decorationId] = document.createElement('atom-overlay')
overlayNode.appendChild(item)
measureOverlays: ->
for decorationId, {itemView} of @overlaysById
@measureOverlay(decorationId, itemView)
measureOverlay: (decorationId, itemView) ->
contentMargin = parseInt(getComputedStyle(itemView)['margin-left']) ? 0
@presenter.setOverlayDimensions(decorationId, itemView.offsetWidth, itemView.offsetHeight, contentMargin)
renderOverlay: (state, decorationId, {item, pixelPosition}) ->
itemView = atom.views.getView(item)
cachedOverlay = @overlaysById[decorationId]
unless overlayNode = cachedOverlay?.overlayNode
overlayNode = document.createElement('atom-overlay')
@container.appendChild(overlayNode)
@overlaysById[decorationId] = cachedOverlay = {overlayNode, itemView}
itemWidth = item.offsetWidth
itemHeight = item.offsetHeight
# The same node may be used in more than one overlay. This steals the node
# back if it has been displayed in another overlay.
overlayNode.appendChild(itemView) if overlayNode.childNodes.length == 0
{scrollTop, scrollLeft} = state.content
left = pixelPosition.left
if left + itemWidth - scrollLeft > @presenter.contentFrameWidth and left - itemWidth >= scrollLeft
left -= itemWidth
top = pixelPosition.top + @presenter.lineHeight
if top + itemHeight - scrollTop > @presenter.height and top - itemHeight - @presenter.lineHeight >= scrollTop
top -= itemHeight + @presenter.lineHeight
overlayNode.style.top = top + 'px'
overlayNode.style.left = left + 'px'
cachedOverlay.pixelPosition = pixelPosition
overlayNode.style.top = pixelPosition.top + 'px'
overlayNode.style.left = pixelPosition.left + 'px'

View File

@ -393,7 +393,7 @@ class Selection extends Model
if options.select
@setBufferRange(newBufferRange, reversed: wasReversed)
else
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
@cursor.setBufferPosition(newBufferRange.end, clip: 'forward') if wasReversed
if autoIndentFirstLine
@editor.setIndentationForBufferRow(oldBufferRange.start.row, desiredIndentLevel)

View File

@ -11,6 +11,7 @@ InputComponent = require './input-component'
LinesComponent = require './lines-component'
ScrollbarComponent = require './scrollbar-component'
ScrollbarCornerComponent = require './scrollbar-corner-component'
OverlayManager = require './overlay-manager'
module.exports =
class TextEditorComponent
@ -56,8 +57,14 @@ class TextEditorComponent
@domNode = document.createElement('div')
if @useShadowDOM
@domNode.classList.add('editor-contents--private')
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@domNode.appendChild(insertionPoint)
@overlayManager = new OverlayManager(@presenter, @hostElement)
else
@domNode.classList.add('editor-contents')
@overlayManager = new OverlayManager(@presenter, @domNode)
@scrollViewNode = document.createElement('div')
@scrollViewNode.classList.add('scroll-view')
@ -140,6 +147,8 @@ class TextEditorComponent
@verticalScrollbarComponent.updateSync(@newState)
@scrollbarCornerComponent.updateSync(@newState)
@overlayManager?.render(@newState)
if @editor.isAlive()
@updateParentViewFocusedClassIfNeeded()
@updateParentViewMiniClass()
@ -149,6 +158,7 @@ class TextEditorComponent
readAfterUpdateSync: =>
@linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically
@overlayManager?.measureOverlays()
mountGutterComponent: ->
@gutterComponent = new GutterComponent({@editor, onMouseDown: @onGutterMouseDown})
@ -159,7 +169,8 @@ class TextEditorComponent
@measureScrollbars() if @measureScrollbarsWhenShown
@sampleFontStyling()
@sampleBackgroundColors()
@measureHeightAndWidth()
@measureWindowSize()
@measureDimensions()
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
@editor.setVisible(true)
@ -556,8 +567,9 @@ class TextEditorComponent
pollDOM: =>
unless @checkForVisibilityChange()
@sampleBackgroundColors()
@measureHeightAndWidth()
@measureDimensions()
@sampleFontStyling()
@overlayManager?.measureOverlays()
checkForVisibilityChange: ->
if @isVisible()
@ -575,13 +587,14 @@ class TextEditorComponent
@heightAndWidthMeasurementRequested = true
requestAnimationFrame =>
@heightAndWidthMeasurementRequested = false
@measureHeightAndWidth()
@measureDimensions()
@measureWindowSize()
# Measure explicitly-styled height and width and relay them to the model. If
# these values aren't explicitly styled, we assume the editor is unconstrained
# and use the scrollHeight / scrollWidth as its height and width in
# calculations.
measureHeightAndWidth: ->
measureDimensions: ->
return unless @mounted
{position} = getComputedStyle(@hostElement)
@ -602,6 +615,12 @@ class TextEditorComponent
if clientWidth > 0
@presenter.setContentFrameWidth(clientWidth)
@presenter.setBoundingClientRect(@hostElement.getBoundingClientRect())
measureWindowSize: ->
return unless @mounted
@presenter.setWindowSize(window.innerWidth, window.innerHeight)
sampleFontStyling: =>
oldFontSize = @fontSize
oldFontFamily = @fontFamily

View File

@ -9,9 +9,10 @@ class TextEditorPresenter
stoppedScrollingTimeoutId: null
mouseWheelScreenRow: null
scopedCharacterWidthsChangeCount: 0
overlayDimensions: {}
constructor: (params) ->
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft} = params
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@ -314,7 +315,7 @@ class TextEditorPresenter
@emitDidUpdateState()
updateOverlaysState: -> @batch "shouldUpdateOverlaysState", ->
return unless @hasPixelRectRequirements()
return unless @hasOverlayPositionRequirements()
visibleDecorationIds = {}
@ -327,13 +328,39 @@ class TextEditorPresenter
else
screenPosition = decoration.getMarker().getHeadScreenPosition()
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
{scrollTop, scrollLeft} = @state.content
gutterWidth = @boundingClientRect.width - @contentFrameWidth
top = pixelPosition.top + @lineHeight - scrollTop
left = pixelPosition.left + gutterWidth - scrollLeft
if overlayDimensions = @overlayDimensions[decoration.id]
{itemWidth, itemHeight, contentMargin} = overlayDimensions
rightDiff = left + @boundingClientRect.left + itemWidth + contentMargin - @windowWidth
left -= rightDiff if rightDiff > 0
leftDiff = left + @boundingClientRect.left + contentMargin
left -= leftDiff if leftDiff < 0
if top + @boundingClientRect.top + itemHeight > @windowHeight and top - (itemHeight + @lineHeight) >= 0
top -= itemHeight + @lineHeight
pixelPosition.top = top
pixelPosition.left = left
@state.content.overlays[decoration.id] ?= {item}
@state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition)
@state.content.overlays[decoration.id].pixelPosition = pixelPosition
visibleDecorationIds[decoration.id] = true
for id of @state.content.overlays
delete @state.content.overlays[id] unless visibleDecorationIds[id]
for id of @overlayDimensions
delete @overlayDimensions[id] unless visibleDecorationIds[id]
return
updateGutterState: -> @batch "shouldUpdateGutterState", ->
@ -566,6 +593,7 @@ class TextEditorPresenter
@updateLinesState()
@updateCursorsState()
@updateLineNumbersState()
@updateOverlaysState()
didStartScrolling: ->
if @stoppedScrollingTimeoutId?
@ -593,6 +621,7 @@ class TextEditorPresenter
@updateHorizontalScrollState()
@updateHiddenInputState()
@updateCursorsState() unless oldScrollLeft?
@updateOverlaysState()
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight
@ -657,6 +686,24 @@ class TextEditorPresenter
@updateLinesState()
@updateCursorsState() unless oldContentFrameWidth?
setBoundingClientRect: (boundingClientRect) ->
unless @clientRectsEqual(@boundingClientRect, boundingClientRect)
@boundingClientRect = boundingClientRect
@updateOverlaysState()
clientRectsEqual: (clientRectA, clientRectB) ->
clientRectA? and clientRectB? and
clientRectA.top is clientRectB.top and
clientRectA.left is clientRectB.left and
clientRectA.width is clientRectB.width and
clientRectA.height is clientRectB.height
setWindowSize: (width, height) ->
if @windowWidth isnt width or @windowHeight isnt height
@windowWidth = width
@windowHeight = height
@updateOverlaysState()
setBackgroundColor: (backgroundColor) ->
unless @backgroundColor is backgroundColor
@backgroundColor = backgroundColor
@ -777,6 +824,9 @@ class TextEditorPresenter
hasPixelRectRequirements: ->
@hasPixelPositionRequirements() and @scrollWidth?
hasOverlayPositionRequirements: ->
@hasPixelRectRequirements() and @boundingClientRect? and @windowWidth and @windowHeight
pixelRectForScreenRange: (screenRange) ->
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start).top
@ -994,6 +1044,18 @@ class TextEditorPresenter
regions
setOverlayDimensions: (decorationId, itemWidth, itemHeight, contentMargin) ->
@overlayDimensions[decorationId] ?= {}
overlayState = @overlayDimensions[decorationId]
dimensionsAreEqual = overlayState.itemWidth is itemWidth and
overlayState.itemHeight is itemHeight and
overlayState.contentMargin is contentMargin
unless dimensionsAreEqual
overlayState.itemWidth = itemWidth
overlayState.itemHeight = itemHeight
overlayState.contentMargin = contentMargin
@updateOverlaysState()
observeCursor: (cursor) ->
didChangePositionDisposable = cursor.onDidChangePosition =>
@updateHiddenInputState() if cursor.isLastCursor()

View File

@ -41,10 +41,20 @@ class TokenizedLine
copy: ->
new TokenizedLine({@tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold})
# This clips a given screen column to a valid column that's within the line
# and not in the middle of any atomic tokens.
#
# column - A {Number} representing the column to clip
# options - A hash with the key clip. Valid values for this key:
# 'closest' (default): clip to the closest edge of an atomic token.
# 'forward': clip to the forward edge.
# 'backward': clip to the backward edge.
#
# Returns a {Number} representing the clipped column.
clipScreenColumn: (column, options={}) ->
return 0 if @tokens.length == 0
{ skipAtomicTokens } = options
{ clip } = options
column = Math.min(column, @getMaxScreenColumn())
tokenStartColumn = 0
@ -55,10 +65,15 @@ class TokenizedLine
if @isColumnInsideSoftWrapIndentation(tokenStartColumn)
@softWrapIndentationDelta
else if token.isAtomic and tokenStartColumn < column
if skipAtomicTokens
if clip == 'forward'
tokenStartColumn + token.screenDelta
else
else if clip == 'backward'
tokenStartColumn
else #'closest'
if column > tokenStartColumn + (token.screenDelta / 2)
tokenStartColumn + token.screenDelta
else
tokenStartColumn
else
column
@ -67,7 +82,7 @@ class TokenizedLine
screenColumn = 0
currentBufferColumn = 0
for token in @tokens
break if currentBufferColumn > bufferColumn
break if currentBufferColumn + token.bufferDelta > bufferColumn
screenColumn += token.screenDelta
currentBufferColumn += token.bufferDelta
@clipScreenColumn(screenColumn + (bufferColumn - currentBufferColumn))

View File

@ -178,6 +178,9 @@ class ViewRegistry
@documentPollers = @documentPollers.filter (poller) -> poller isnt fn
@stopPollingDocument() if @documentPollers.length is 0
pollAfterNextUpdate: ->
@performDocumentPollAfterUpdate = true
clearDocumentRequests: ->
@documentReaders = []
@documentWriters = []
@ -194,6 +197,7 @@ class ViewRegistry
writer() while writer = @documentWriters.shift()
reader() while reader = @documentReaders.shift()
@performDocumentPoll() if @performDocumentPollAfterUpdate
@performDocumentPollAfterUpdate = false
startPollingDocument: ->
@pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval)
@ -205,6 +209,5 @@ class ViewRegistry
if @documentUpdateRequested
@performDocumentPollAfterUpdate = true
else
@performDocumentPollAfterUpdate = false
poller() for poller in @documentPollers
return

View File

@ -24,7 +24,7 @@ atom-pane-container {
display: -webkit-flex;
-webkit-flex: 1;
-webkit-flex-direction: column;
overflow: hidden;
overflow: visible;
.item-views {
-webkit-flex: 1;

View File

@ -9,7 +9,6 @@
.editor-contents--private {
width: 100%;
overflow: hidden;
cursor: text;
display: -webkit-flex;
-webkit-user-select: none;

View File

@ -28,3 +28,16 @@
@syntax-color-modified: orange;
@syntax-color-removed: red;
@syntax-color-renamed: blue;
// For language entity colors
@syntax-color-variable: #DF6A73;
@syntax-color-constant: #DF6A73;
@syntax-color-property: #DF6A73;
@syntax-color-value: #D29B67;
@syntax-color-function: #61AEEF;
@syntax-color-method: @syntax-color-function;
@syntax-color-class: #E5C17C;
@syntax-color-keyword: #555;
@syntax-color-tag: #555;
@syntax-color-import: #97C378;
@syntax-color-snippet: #97C378;