Merge branch 'master' of https://github.com/atom/atom into upstream

This commit is contained in:
Jesse Grosjean
2015-04-02 12:23:03 -04:00
19 changed files with 471 additions and 288 deletions

View File

@@ -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.152.0"
"atom-package-manager": "0.157.0"
}
}

View File

@@ -12,7 +12,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,7 +17,7 @@
"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": "^5",
@@ -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

@@ -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]
@@ -1582,7 +1442,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])
@@ -1596,7 +1456,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()
@@ -1879,7 +1739,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", ->
@@ -2045,7 +1905,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
@@ -2056,7 +1916,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')
@@ -2070,7 +1930,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
@@ -2083,7 +1943,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())
@@ -2093,7 +1953,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
@@ -2102,7 +1962,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()
@@ -2116,21 +1976,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'
@@ -2139,7 +1999,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 """
@@ -2168,21 +2028,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'
@@ -2191,7 +2051,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()
@@ -2205,7 +2065,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)", ->
@@ -2249,7 +2109,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)
@@ -2262,7 +2122,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)
@@ -2305,7 +2165,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)
@@ -2320,7 +2180,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
@@ -2390,6 +2250,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
@@ -2716,7 +2577,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

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

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

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

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