mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 07:28:08 +03:00
Merge branch 'master' into asar
Conflicts: apm/package.json
This commit is contained in:
commit
32f2e4c4bc
@ -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).
|
||||
|
@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.155.0"
|
||||
"atom-package-manager": "0.157.0"
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
30
package.json
30
package.json
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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 ''
|
||||
|
||||
|
@ -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", ->
|
||||
|
@ -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)
|
||||
|
@ -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: ->
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
.editor-contents--private {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
cursor: text;
|
||||
display: -webkit-flex;
|
||||
-webkit-user-select: none;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user