Merge branch 'master' into mb-optimize-marker-observation

Conflicts:
	src/text-editor-component.coffee
	src/text-editor-presenter.coffee
This commit is contained in:
Max Brunsfeld 2015-06-03 09:12:07 -07:00
commit 76c696f1a2
30 changed files with 1084 additions and 687 deletions

View File

@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "0.205.0",
"version": "0.206.0",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@ -32,7 +32,7 @@
"delegato": "^1",
"emissary": "^1.3.3",
"event-kit": "^1.2.0",
"first-mate": "^4.1.5",
"first-mate": "^4.1.6",
"fs-plus": "^2.8.0",
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
@ -77,10 +77,10 @@
"atom-light-ui": "0.41.0",
"base16-tomorrow-dark-theme": "0.26.0",
"base16-tomorrow-light-theme": "0.9.0",
"one-dark-ui": "0.8.2",
"one-dark-syntax": "0.5.0",
"one-light-syntax": "0.6.0",
"one-light-ui": "0.8.2",
"one-dark-ui": "0.9.0",
"one-dark-syntax": "0.7.0",
"one-light-syntax": "0.7.0",
"one-light-ui": "0.9.0",
"solarized-dark-syntax": "0.35.0",
"solarized-light-syntax": "0.21.0",
"archive-view": "0.57.0",
@ -88,12 +88,12 @@
"autocomplete-css": "0.7.2",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.17.3",
"autocomplete-snippets": "1.6.3",
"autocomplete-snippets": "1.7.0",
"autoflow": "0.24.0",
"autosave": "0.20.0",
"background-tips": "0.25.0",
"bookmarks": "0.35.0",
"bracket-matcher": "0.74.0",
"bracket-matcher": "0.75.0",
"command-palette": "0.36.0",
"deprecation-cop": "0.52.0",
"dev-live-reload": "0.46.0",
@ -115,7 +115,7 @@
"package-generator": "0.39.0",
"release-notes": "0.52.0",
"settings-view": "0.206.0",
"snippets": "0.91.0",
"snippets": "0.93.0",
"spell-check": "0.58.0",
"status-bar": "0.74.0",
"styleguide": "0.44.0",
@ -139,7 +139,7 @@
"language-hyperlink": "0.13.0",
"language-java": "0.15.0",
"language-javascript": "0.78.0",
"language-json": "0.14.0",
"language-json": "0.15.0",
"language-less": "0.27.0",
"language-make": "0.14.0",
"language-mustache": "0.11.0",
@ -153,7 +153,7 @@
"language-sass": "0.38.0",
"language-shellscript": "0.15.0",
"language-source": "0.9.0",
"language-sql": "0.15.0",
"language-sql": "0.16.0",
"language-text": "0.6.0",
"language-todo": "0.23.0",
"language-toml": "0.16.0",

View File

@ -805,7 +805,6 @@ describe "Config", ->
atom.config.loadUserConfig()
expect(atom.config.get("foo.bar")).toBe "baz"
describe ".observeUserConfig()", ->
updatedHandler = null
@ -1381,6 +1380,16 @@ describe "Config", ->
expect(atom.config.set('foo.bar.aString', nope: 'nope')).toBe false
expect(atom.config.get('foo.bar.aString')).toBe 'ok'
describe 'when the schema has a "maximumLength" key', ->
it "trims the string to be no longer than the specified maximum", ->
schema =
type: 'string'
default: 'ok'
maximumLength: 3
atom.config.setSchema('foo.bar.aString', schema)
atom.config.set('foo.bar.aString', 'abcdefg')
expect(atom.config.get('foo.bar.aString')).toBe 'abc'
describe 'when the value has an "object" type', ->
beforeEach ->
schema =

View File

@ -31,6 +31,12 @@ describe "DefaultDirectoryProvider", ->
directory = provider.directoryForURISync(file)
expect(directory.getPath()).toEqual tmp
it "creates a Directory with a path as a uri when passed a uri", ->
provider = new DefaultDirectoryProvider()
uri = 'remote://server:6792/path/to/a/dir'
directory = provider.directoryForURISync(uri)
expect(directory.getPath()).toEqual uri
describe ".directoryForURI(uri)", ->
it "returns a Promise that resolves to a Directory with a path that matches the uri", ->
provider = new DefaultDirectoryProvider()

View File

@ -227,3 +227,13 @@ describe "Starting Atom", ->
[tempDirPath]
[otherTempDirPath]
].sort()
describe "opening a remote directory", ->
it "opens the parent directory and creates an empty text editor", ->
remoteDirectory = 'remote://server:3437/some/directory/path'
runAtom [remoteDirectory], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([remoteDirectory])

View File

@ -57,3 +57,16 @@ describe "Task", ->
expect(deprecations.length).toBe 1
expect(deprecations[0].getStacks()[0][1].fileName).toBe handlerPath
jasmine.restoreDeprecationsSnapshot()
it "adds data listeners to standard out and error to report output", ->
task = new Task(require.resolve('./fixtures/task-spec-handler'))
{stdout, stderr} = task.childProcess
task.start()
task.start()
expect(stdout.listeners('data').length).toBe 1
expect(stderr.listeners('data').length).toBe 1
task.terminate()
expect(stdout.listeners('data').length).toBe 0
expect(stderr.listeners('data').length).toBe 0

View File

@ -7,10 +7,10 @@ nbsp = String.fromCharCode(160)
describe "TextEditorComponent", ->
[contentNode, editor, wrapperView, wrapperNode, component, componentNode, verticalScrollbarNode, horizontalScrollbarNode] = []
[lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, lineOverdrawMargin] = []
[lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, tileSize] = []
beforeEach ->
lineOverdrawMargin = 2
tileSize = 3
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
@ -34,7 +34,7 @@ describe "TextEditorComponent", ->
contentNode = document.querySelector('#jasmine-content')
contentNode.style.width = '1000px'
wrapperView = new TextEditorView(editor, {lineOverdrawMargin})
wrapperView = new TextEditorView(editor, {tileSize})
wrapperView.attachToDom()
wrapperNode = wrapperView.element
wrapperNode.setUpdatedSynchronously(false)
@ -68,48 +68,111 @@ describe "TextEditorComponent", ->
expect(nextAnimationFrame).not.toThrow()
describe "line rendering", ->
it "renders the currently-visible lines plus the overdraw margin", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
expectTileContainsRow = (tileNode, screenRow, {top}) ->
lineNode = tileNode.querySelector("[data-screen-row='#{screenRow}']")
tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
expect(lineNode.offsetTop).toBe(top)
if tokenizedLine.text is ""
expect(lineNode.innerHTML).toBe(" ")
else
expect(lineNode.textContent).toBe(tokenizedLine.text)
it "renders the currently-visible lines in a tiled fashion", ->
wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
tileHeight = tileSize * lineHeightInPixels
component.measureDimensions()
nextAnimationFrame()
linesNode = componentNode.querySelector('.lines')
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expect(componentNode.querySelectorAll('.line').length).toBe 6 + 2 # no margin above
expect(component.lineNodeForScreenRow(0).textContent).toBe editor.tokenizedLineForScreenRow(0).text
expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0
expect(component.lineNodeForScreenRow(5).textContent).toBe editor.tokenizedLineForScreenRow(5).text
expect(component.lineNodeForScreenRow(5).offsetTop).toBe 5 * lineHeightInPixels
tilesNodes = componentNode.querySelectorAll(".tile")
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
expect(tilesNodes.length).toBe(3)
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expect(tilesNodes[0].children.length).toBe(tileSize)
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
expect(tilesNodes[1].children.length).toBe(tileSize)
expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
expect(tilesNodes[2].children.length).toBe(tileSize)
expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)
expect(component.lineNodeForScreenRow(9)).toBeUndefined()
verticalScrollbarNode.scrollTop = tileSize * lineHeightInPixels + 5
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, #{-4.5 * lineHeightInPixels}px, 0px)"
expect(componentNode.querySelectorAll('.line').length).toBe 6 + 4 # margin above and below
expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
expect(component.lineNodeForScreenRow(2).textContent).toBe editor.tokenizedLineForScreenRow(2).text
expect(component.lineNodeForScreenRow(9).offsetTop).toBe 9 * lineHeightInPixels
expect(component.lineNodeForScreenRow(9).textContent).toBe editor.tokenizedLineForScreenRow(9).text
tilesNodes = componentNode.querySelectorAll(".tile")
it "updates the top position of subsequent lines when lines are inserted or removed", ->
expect(component.lineNodeForScreenRow(2)).toBeUndefined()
expect(tilesNodes.length).toBe(3)
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)"
expect(tilesNodes[0].children.length).toBe(tileSize)
expectTileContainsRow(tilesNodes[0], 3, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 4, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 5, top: 2 * lineHeightInPixels)
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight - 5}px, 0px)"
expect(tilesNodes[1].children.length).toBe(tileSize)
expectTileContainsRow(tilesNodes[1], 6, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 7, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 8, top: 2 * lineHeightInPixels)
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight - 5}px, 0px)"
expect(tilesNodes[2].children.length).toBe(tileSize)
expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels)
it "updates the top position of subsequent tiles when lines are inserted or removed", ->
wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
tileHeight = tileSize * lineHeightInPixels
component.measureDimensions()
editor.getBuffer().deleteRows(0, 1)
nextAnimationFrame()
lineNodes = componentNode.querySelectorAll('.line')
expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels
expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
tilesNodes = componentNode.querySelectorAll(".tile")
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)
editor.getBuffer().insert([0, 0], '\n\n')
nextAnimationFrame()
lineNodes = componentNode.querySelectorAll('.line')
expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 * lineHeightInPixels
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels
expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
expect(component.lineNodeForScreenRow(3).offsetTop).toBe 3 * lineHeightInPixels
expect(component.lineNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels
tilesNodes = componentNode.querySelectorAll(".tile")
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)
it "updates the lines when lines are inserted or removed above the rendered row range", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
@ -483,7 +546,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # visible line-numbers + dummy line number
expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1"
expect(component.lineNumberNodeForScreenRow(5).textContent).toBe "#{nbsp}6"
@ -491,7 +554,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 4 + 1 # line overdraw margin above/below + dummy line number
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # visible line-numbers + dummy line number
expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}3"
expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
@ -527,7 +590,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line componentNode
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # 1 dummy line
expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1"
expect(component.lineNumberNodeForScreenRow(1).textContent).toBe "#{nbsp}"
expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}2"
@ -725,13 +788,13 @@ describe "TextEditorComponent", ->
cursorNodes = componentNode.querySelectorAll('.cursor')
expect(cursorNodes.length).toBe 2
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)"
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
editor.onDidChangeCursorPosition cursorMovedListener = jasmine.createSpy('cursorMovedListener')
cursor3.setScreenPosition([4, 11], autoscroll: false)
nextAnimationFrame()
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)"
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
expect(cursorMovedListener).toHaveBeenCalled()
cursor3.destroy()
@ -739,7 +802,7 @@ describe "TextEditorComponent", ->
cursorNodes = componentNode.querySelectorAll('.cursor')
expect(cursorNodes.length).toBe 1
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
it "accounts for character widths when positioning cursors", ->
atom.config.set('editor.fontFamily', 'sans-serif')
@ -1001,7 +1064,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
# Scroll decorations into view
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
expect(lineAndLineNumberHaveClass(9, 'b')).toBe true
@ -1147,15 +1210,16 @@ describe "TextEditorComponent", ->
# Nothing when outside the rendered row range
expect(regions.length).toBe 0
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
verticalScrollbarNode.scrollTop = 6 * lineHeightInPixels
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
expect(component.presenter.endRow).toBeGreaterThan(8)
regions = componentNode.querySelectorAll('.some-highlight .region')
expect(regions.length).toBe 1
regionRect = regions[0].style
expect(regionRect.top).toBe 9 * lineHeightInPixels + 'px'
expect(regionRect.top).toBe (9 * lineHeightInPixels - verticalScrollbarNode.scrollTop) + 'px'
expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px'
expect(regionRect.left).toBe 2 * charWidth + 'px'
expect(regionRect.width).toBe 2 * charWidth + 'px'
@ -1920,13 +1984,23 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
linesNode = componentNode.querySelector('.lines')
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
tilesNodes = componentNode.querySelectorAll(".tile")
top = 0
for tileNode in tilesNodes
expect(tileNode.style['-webkit-transform']).toBe "translate3d(0px, #{top}px, 0px)"
top += tileNode.offsetHeight
expect(horizontalScrollbarNode.scrollLeft).toBe 0
editor.setScrollLeft(100)
nextAnimationFrame()
expect(linesNode.style['-webkit-transform']).toBe "translate3d(-100px, 0px, 0px)"
top = 0
for tileNode in tilesNodes
expect(tileNode.style['-webkit-transform']).toBe "translate3d(-100px, #{top}px, 0px)"
top += tileNode.offsetHeight
expect(horizontalScrollbarNode.scrollLeft).toBe 100
it "updates the scrollLeft of the model when the scrollLeft of the horizontal scrollbar changes", ->
@ -2387,7 +2461,7 @@ describe "TextEditorComponent", ->
hiddenParent.style.display = 'none'
contentNode.appendChild(hiddenParent)
wrapperView = new TextEditorView(editor, {lineOverdrawMargin})
wrapperView = new TextEditorView(editor, {tileSize})
wrapperNode = wrapperView.element
wrapperView.appendTo(hiddenParent)
@ -2508,7 +2582,7 @@ describe "TextEditorComponent", ->
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1)
expect(componentNode.querySelectorAll('.line')).toHaveLength(6)
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px'

View File

@ -39,7 +39,6 @@ describe "TextEditorPresenter", ->
verticalScrollbarWidth: 10
scrollTop: 0
scrollLeft: 0
lineOverdrawMargin: 0
new TextEditorPresenter(params)
@ -657,160 +656,125 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> editor.setPlaceholderText("new-placeholder-text")
expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text"
describe ".lines", ->
lineStateForScreenRow = (presenter, screenRow) ->
presenter.getState().content.lines[presenter.model.tokenizedLineForScreenRow(screenRow).id]
describe ".tiles", ->
lineStateForScreenRow = (presenter, row) ->
lineId = presenter.model.tokenizedLineForScreenRow(row).id
tileRow = presenter.tileForRow(row)
presenter.getState().content.tiles[tileRow]?.lines[lineId]
it "contains states for lines that are visible on screen, plus and minus the overdraw margin", ->
presenter = buildPresenter(explicitHeight: 15, scrollTop: 50, lineHeight: 10, lineOverdrawMargin: 1)
it "contains states for tiles that are visible on screen", ->
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
expect(lineStateForScreenRow(presenter, 3)).toBeUndefined()
line4 = editor.tokenizedLineForScreenRow(4)
expectValues lineStateForScreenRow(presenter, 4), {
screenRow: 4
text: line4.text
tags: line4.tags
specialTokens: line4.specialTokens
firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex
invisibles: line4.invisibles
top: 10 * 4
expectValues presenter.getState().content.tiles[0], {
top: 0
}
expectValues presenter.getState().content.tiles[2], {
top: 2
}
expectValues presenter.getState().content.tiles[4], {
top: 4
}
expectValues presenter.getState().content.tiles[6], {
top: 6
}
line5 = editor.tokenizedLineForScreenRow(5)
expectValues lineStateForScreenRow(presenter, 5), {
screenRow: 5
text: line5.text
tags: line5.tags
specialTokens: line5.specialTokens
firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex
invisibles: line5.invisibles
top: 10 * 5
expect(presenter.getState().content.tiles[8]).toBeUndefined()
expectStateUpdate presenter, -> presenter.setScrollTop(3)
expect(presenter.getState().content.tiles[0]).toBeUndefined()
expectValues presenter.getState().content.tiles[2], {
top: -1
}
expectValues presenter.getState().content.tiles[4], {
top: 1
}
expectValues presenter.getState().content.tiles[6], {
top: 3
}
expectValues presenter.getState().content.tiles[8], {
top: 5
}
expectValues presenter.getState().content.tiles[10], {
top: 7
}
line6 = editor.tokenizedLineForScreenRow(6)
expectValues lineStateForScreenRow(presenter, 6), {
screenRow: 6
text: line6.text
tags: line6.tags
specialTokens: line6.specialTokens
firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex
invisibles: line6.invisibles
top: 10 * 6
}
expect(presenter.getState().content.tiles[12]).toBeUndefined()
line7 = editor.tokenizedLineForScreenRow(7)
expectValues lineStateForScreenRow(presenter, 7), {
screenRow: 7
text: line7.text
tags: line7.tags
specialTokens: line7.specialTokens
firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex
invisibles: line7.invisibles
top: 10 * 7
}
line8 = editor.tokenizedLineForScreenRow(8)
expectValues lineStateForScreenRow(presenter, 8), {
screenRow: 8
text: line8.text
tags: line8.tags
specialTokens: line8.specialTokens
firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex
invisibles: line8.invisibles
top: 10 * 8
}
expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
it "does not overdraw above the first row", ->
presenter = buildPresenter(explicitHeight: 15, scrollTop: 10, lineHeight: 10, lineOverdrawMargin: 2)
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
expect(lineStateForScreenRow(presenter, 1)).toBeDefined()
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
expect(lineStateForScreenRow(presenter, 3)).toBeDefined()
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
expect(lineStateForScreenRow(presenter, 5)).toBeDefined()
expect(lineStateForScreenRow(presenter, 6)).toBeUndefined()
it "does not overdraw below the last row", ->
presenter = buildPresenter(explicitHeight: 25, scrollTop: 105, lineHeight: 10, lineOverdrawMargin: 2)
expect(lineStateForScreenRow(presenter, 7)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 8)).toBeDefined()
expect(lineStateForScreenRow(presenter, 9)).toBeDefined()
expect(lineStateForScreenRow(presenter, 10)).toBeDefined()
expect(lineStateForScreenRow(presenter, 11)).toBeDefined()
expect(lineStateForScreenRow(presenter, 12)).toBeDefined()
it "includes state for all lines if no external ::explicitHeight is assigned", ->
presenter = buildPresenter(explicitHeight: null)
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
expect(lineStateForScreenRow(presenter, 12)).toBeDefined()
it "includes state for all tiles if no external ::explicitHeight is assigned", ->
presenter = buildPresenter(explicitHeight: null, tileSize: 2)
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[12]).toBeDefined()
it "is empty until all of the required measurements are assigned", ->
presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null)
expect(presenter.getState().content.lines).toEqual({})
expect(presenter.getState().content.tiles).toEqual({})
presenter.setExplicitHeight(25)
expect(presenter.getState().content.lines).toEqual({})
expect(presenter.getState().content.tiles).toEqual({})
presenter.setLineHeight(10)
expect(presenter.getState().content.lines).toEqual({})
expect(presenter.getState().content.tiles).toEqual({})
presenter.setScrollTop(0)
expect(presenter.getState().content.lines).not.toEqual({})
expect(presenter.getState().content.tiles).not.toEqual({})
it "updates when ::scrollTop changes", ->
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1)
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
expect(lineStateForScreenRow(presenter, 5)).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[2]).toBeDefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[6]).toBeDefined()
expect(presenter.getState().content.tiles[8]).toBeUndefined()
expectStateUpdate presenter, -> presenter.setScrollTop(25)
expectStateUpdate presenter, -> presenter.setScrollTop(2)
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 1)).toBeDefined()
expect(lineStateForScreenRow(presenter, 6)).toBeDefined()
expect(lineStateForScreenRow(presenter, 7)).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeUndefined()
expect(presenter.getState().content.tiles[2]).toBeDefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[6]).toBeDefined()
expect(presenter.getState().content.tiles[8]).toBeDefined()
expect(presenter.getState().content.tiles[10]).toBeUndefined()
it "updates when ::explicitHeight changes", ->
presenter = buildPresenter(explicitHeight: 15, scrollTop: 15, lineHeight: 10, lineOverdrawMargin: 1)
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
line5 = editor.tokenizedLineForScreenRow(5)
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[2]).toBeDefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[6]).toBeDefined()
expect(presenter.getState().content.tiles[8]).toBeUndefined()
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
expect(lineStateForScreenRow(presenter, 5)).toBeUndefined()
expectStateUpdate presenter, -> presenter.setExplicitHeight(8)
expectStateUpdate presenter, -> presenter.setExplicitHeight(35)
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[2]).toBeDefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[6]).toBeDefined()
expect(presenter.getState().content.tiles[8]).toBeDefined()
expect(presenter.getState().content.tiles[10]).toBeUndefined()
expect(lineStateForScreenRow(presenter, 5)).toBeDefined()
expect(lineStateForScreenRow(presenter, 6)).toBeDefined()
expect(lineStateForScreenRow(presenter, 7)).toBeUndefined()
it "updates when ::lineHeight changes", ->
presenter = buildPresenter(explicitHeight: 15, scrollTop: 10, lineHeight: 10, lineOverdrawMargin: 0)
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 1)).toBeDefined()
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
expect(lineStateForScreenRow(presenter, 4)).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[2]).toBeDefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[6]).toBeDefined()
expect(presenter.getState().content.tiles[8]).toBeUndefined()
expectStateUpdate presenter, -> presenter.setLineHeight(5)
expectStateUpdate presenter, -> presenter.setLineHeight(2)
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 1)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
expect(lineStateForScreenRow(presenter, 5)).toBeDefined()
expect(lineStateForScreenRow(presenter, 6)).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[2]).toBeDefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[6]).toBeUndefined()
it "updates when the editor's content changes", ->
presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10, tileSize: 2)
expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n")
@ -832,53 +796,125 @@ describe "TextEditorPresenter", ->
tags: line3.tags
}
it "does not remove out-of-view lines corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", ->
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
it "does not remove out-of-view tiles corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", ->
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200)
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
expect(lineStateForScreenRow(presenter, 5)).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[6]).toBeDefined()
expect(presenter.getState().content.tiles[8]).toBeUndefined()
presenter.setMouseWheelScreenRow(0)
expectStateUpdate presenter, -> presenter.setScrollTop(35)
expectStateUpdate presenter, -> presenter.setScrollTop(4)
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
expect(lineStateForScreenRow(presenter, 1)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 7)).toBeDefined()
expect(lineStateForScreenRow(presenter, 8)).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeDefined()
expect(presenter.getState().content.tiles[2]).toBeUndefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[12]).toBeUndefined()
expectStateUpdate presenter, -> advanceClock(200)
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 1)).toBeUndefined()
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
expect(lineStateForScreenRow(presenter, 7)).toBeDefined()
expect(lineStateForScreenRow(presenter, 8)).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeUndefined()
expect(presenter.getState().content.tiles[2]).toBeUndefined()
expect(presenter.getState().content.tiles[4]).toBeDefined()
expect(presenter.getState().content.tiles[12]).toBeUndefined()
# should clear ::mouseWheelScreenRow after stoppedScrollingDelay elapses even if we don't scroll first
presenter.setMouseWheelScreenRow(2)
presenter.setMouseWheelScreenRow(4)
advanceClock(200)
expectStateUpdate presenter, -> presenter.setScrollTop(45)
expect(lineStateForScreenRow(presenter, 2)).toBeUndefined()
expectStateUpdate presenter, -> presenter.setScrollTop(6)
expect(presenter.getState().content.tiles[4]).toBeUndefined()
it "does not preserve on-screen lines even if they correspond to ::mouseWheelScreenRow", ->
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
oldLine3 = editor.tokenizedLineForScreenRow(6)
it "does not preserve deleted on-screen tiles even if they correspond to ::mouseWheelScreenRow", ->
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200)
presenter.setMouseWheelScreenRow(3)
presenter.setMouseWheelScreenRow(2)
expectStateUpdate presenter, -> editor.getBuffer().insert([3, Infinity], 'xyz')
newLine3 = editor.tokenizedLineForScreenRow(3)
expectStateUpdate presenter, -> editor.setText("")
expect(presenter.getState().content.lines[oldLine3.id]).toBeUndefined()
expect(presenter.getState().content.lines[newLine3.id]).toBeDefined()
expect(presenter.getState().content.tiles[2]).toBeUndefined()
expect(presenter.getState().content.tiles[0]).toBeDefined()
it "does not attempt to preserve lines corresponding to ::mouseWheelScreenRow if they have been deleted", ->
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
presenter.setMouseWheelScreenRow(10)
editor.setText('')
describe "[tileId].lines[lineId]", -> # line state objects
it "includes the state for visible lines in a tile", ->
presenter = buildPresenter(explicitHeight: 3, scrollTop: 4, lineHeight: 1, tileSize: 3, stoppedScrollingDelay: 200)
expect(lineStateForScreenRow(presenter, 2)).toBeUndefined()
line3 = editor.tokenizedLineForScreenRow(3)
expectValues lineStateForScreenRow(presenter, 3), {
screenRow: 3
text: line3.text
tags: line3.tags
specialTokens: line3.specialTokens
firstNonWhitespaceIndex: line3.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line3.firstTrailingWhitespaceIndex
invisibles: line3.invisibles
top: 0
}
line4 = editor.tokenizedLineForScreenRow(4)
expectValues lineStateForScreenRow(presenter, 4), {
screenRow: 4
text: line4.text
tags: line4.tags
specialTokens: line4.specialTokens
firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex
invisibles: line4.invisibles
top: 1
}
line5 = editor.tokenizedLineForScreenRow(5)
expectValues lineStateForScreenRow(presenter, 5), {
screenRow: 5
text: line5.text
tags: line5.tags
specialTokens: line5.specialTokens
firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex
invisibles: line5.invisibles
top: 2
}
line6 = editor.tokenizedLineForScreenRow(6)
expectValues lineStateForScreenRow(presenter, 6), {
screenRow: 6
text: line6.text
tags: line6.tags
specialTokens: line6.specialTokens
firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex
invisibles: line6.invisibles
top: 0
}
line7 = editor.tokenizedLineForScreenRow(7)
expectValues lineStateForScreenRow(presenter, 7), {
screenRow: 7
text: line7.text
tags: line7.tags
specialTokens: line7.specialTokens
firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex
invisibles: line7.invisibles
top: 1
}
line8 = editor.tokenizedLineForScreenRow(8)
expectValues lineStateForScreenRow(presenter, 8), {
screenRow: 8
text: line8.text
tags: line8.tags
specialTokens: line8.specialTokens
firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex
invisibles: line8.invisibles
top: 2
}
expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
describe "[lineId]", -> # line state objects
it "includes the .endOfLineInvisibles if the editor.showInvisibles config option is true", ->
editor.setText("hello\nworld\r\n")
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10)
@ -1031,9 +1067,9 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 1)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10 - 20, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 4)).toBeUndefined()
it "is empty until all of the required measurements are assigned", ->
@ -1069,8 +1105,21 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toBeUndefined()
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 3)).toEqual {top: 0, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10 - 50, left: 4 * 10, width: 10, height: 10}
it "updates when ::scrollTop changes after the model was changed", ->
editor.setCursorBufferPosition([8, 22])
presenter = buildPresenter(explicitHeight: 50, scrollTop: 10 * 8)
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 10 * 22, width: 10, height: 10}
expectStateUpdate presenter, ->
editor.getBuffer().deleteRow(12)
editor.getBuffer().deleteRow(11)
editor.getBuffer().deleteRow(10)
expect(stateForCursor(presenter, 0)).toEqual {top: 20, left: 10 * 22, width: 10, height: 10}
it "updates when ::explicitHeight changes", ->
editor.setSelectedBufferRanges([
@ -1084,9 +1133,9 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setExplicitHeight(30)
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 1)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10 - 20, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 4)).toBeUndefined()
it "updates when ::lineHeight changes", ->
@ -1103,15 +1152,15 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toBeUndefined()
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 5, left: 12 * 10, width: 10, height: 5}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5, left: 4 * 10, width: 10, height: 5}
expect(stateForCursor(presenter, 3)).toEqual {top: 5, left: 12 * 10, width: 10, height: 5}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5 - 20, left: 4 * 10, width: 10, height: 5}
it "updates when ::baseCharacterWidth changes", ->
editor.setCursorBufferPosition([2, 4])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 20)
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20)
expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 20, width: 20, height: 10}
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 20, width: 20, height: 10}
it "updates when scoped character widths change", ->
waitsForPromise ->
@ -1137,11 +1186,11 @@ describe "TextEditorPresenter", ->
# moving into view
expect(stateForCursor(presenter, 0)).toBeUndefined()
editor.getCursors()[0].setBufferPosition([2, 4])
expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10}
# showing
expectStateUpdate presenter, -> editor.getSelections()[1].clear()
expect(stateForCursor(presenter, 1)).toEqual {top: 3 * 10, left: 5 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 1)).toEqual {top: 5, left: 5 * 10, width: 10, height: 10}
# hiding
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 5]])
@ -1153,11 +1202,11 @@ describe "TextEditorPresenter", ->
# adding
expectStateUpdate presenter, -> editor.addCursorAtBufferPosition([4, 4])
expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 4 * 10, width: 10, height: 10}
# moving added cursor
expectStateUpdate presenter, -> editor.getCursors()[2].setBufferPosition([4, 6])
expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 6 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 6 * 10, width: 10, height: 10}
# destroying
destroyedCursor = editor.getCursors()[2]
@ -1275,39 +1324,39 @@ describe "TextEditorPresenter", ->
expectValues stateForHighlight(presenter, highlight2), {
class: 'b'
regions: [
{top: 2 * 10, left: 0 * 10, width: 6 * 10, height: 1 * 10}
{top: 2 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10}
]
}
expectValues stateForHighlight(presenter, highlight3), {
class: 'c'
regions: [
{top: 2 * 10, left: 0 * 10, right: 0, height: 1 * 10}
{top: 3 * 10, left: 0 * 10, width: 6 * 10, height: 1 * 10}
{top: 2 * 10 - 20, left: 0 * 10, right: 0, height: 1 * 10}
{top: 3 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10}
]
}
expectValues stateForHighlight(presenter, highlight4), {
class: 'd'
regions: [
{top: 2 * 10, left: 6 * 10, right: 0, height: 1 * 10}
{top: 3 * 10, left: 0, right: 0, height: 1 * 10}
{top: 4 * 10, left: 0, width: 6 * 10, height: 1 * 10}
{top: 2 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
{top: 3 * 10 - 20, left: 0, right: 0, height: 1 * 10}
{top: 4 * 10 - 20, left: 0, width: 6 * 10, height: 1 * 10}
]
}
expectValues stateForHighlight(presenter, highlight5), {
class: 'e'
regions: [
{top: 3 * 10, left: 6 * 10, right: 0, height: 1 * 10}
{top: 4 * 10, left: 0 * 10, right: 0, height: 2 * 10}
{top: 3 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
{top: 4 * 10 - 20, left: 0 * 10, right: 0, height: 2 * 10}
]
}
expectValues stateForHighlight(presenter, highlight6), {
class: 'f'
regions: [
{top: 5 * 10, left: 6 * 10, right: 0, height: 1 * 10}
{top: 5 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
]
}
@ -1913,20 +1962,18 @@ describe "TextEditorPresenter", ->
getLineNumberGutterState(presenter).content.lineNumbers[key]
it "contains states for line numbers that are visible on screen, plus and minus the overdraw margin", ->
it "contains states for line numbers that are visible on screen", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, lineOverdrawMargin: 1)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10)
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false, top: 2 * 10}
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false, top: 3 * 10}
expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true, top: 4 * 10}
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false, top: 5 * 10}
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false, top: 6 * 10}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false, top: 7 * 10}
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
it "includes states for all line numbers if no ::explicitHeight is assigned", ->
presenter = buildPresenter(explicitHeight: null)
@ -1937,43 +1984,43 @@ describe "TextEditorPresenter", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineOverdrawMargin: 1)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30)
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8}
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7}
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
expectStateUpdate presenter, -> presenter.setScrollTop(20)
expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 1), {bufferRow: 1}
expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7}
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 4}
expect(lineNumberStateForScreenRow(presenter, 6)).toBeUndefined()
it "updates when ::explicitHeight changes", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineOverdrawMargin: 1)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30)
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8}
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7}
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
expectStateUpdate presenter, -> presenter.setExplicitHeight(35)
expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
expectValues lineNumberStateForScreenRow(presenter, 8), {bufferRow: 8}
expect(lineNumberStateForScreenRow(presenter, 9)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8}
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
it "updates when ::lineHeight changes", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineOverdrawMargin: 0)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0)
expectValues lineNumberStateForScreenRow(presenter, 0), {bufferRow: 0}
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
@ -1989,7 +2036,7 @@ describe "TextEditorPresenter", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 35, scrollTop: 30, lineOverdrawMargin: 0)
presenter = buildPresenter(explicitHeight: 35, scrollTop: 30)
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
@ -2011,26 +2058,26 @@ describe "TextEditorPresenter", ->
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
it "does not remove out-of-view line numbers corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", ->
presenter = buildPresenter(explicitHeight: 25, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
presenter = buildPresenter(explicitHeight: 25, stoppedScrollingDelay: 200)
expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 4)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 5)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 3)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 4)).toBeUndefined()
presenter.setMouseWheelScreenRow(0)
expectStateUpdate presenter, -> presenter.setScrollTop(35)
expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 7)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 6)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
expectStateUpdate presenter, -> advanceClock(200)
expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 7)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 6)).toBeDefined()
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
it "correctly handles the first screen line being soft-wrapped", ->
editor.setSoftWrapped(true)
@ -2213,12 +2260,11 @@ describe "TextEditorPresenter", ->
scrollTop = 0
lineHeight = 10
explicitHeight = lineHeight * 10
lineOverdrawMargin = 1
beforeEach ->
# At the beginning of each test, decoration1 and decoration2 are in visible range,
# but not decoration3.
presenter = buildPresenter({explicitHeight, scrollTop, lineHeight, lineOverdrawMargin})
presenter = buildPresenter({explicitHeight, scrollTop, lineHeight})
gutter = editor.addGutter({name: 'test-gutter', visible: true})
decorationItem = document.createElement('div')
decorationItem.class = 'decoration-item'
@ -2589,7 +2635,7 @@ describe "TextEditorPresenter", ->
editor.setEditorWidthInChars(80)
presenterParams =
model: editor
lineOverdrawMargin: 1
presenter = new TextEditorPresenter(presenterParams)
statements = []

View File

@ -1,3 +1,6 @@
fs = require 'fs-plus'
path = require 'path'
temp = require 'temp'
clipboard = require '../src/safe-clipboard'
TextEditor = require '../src/text-editor'
@ -19,6 +22,17 @@ describe "TextEditor", ->
atom.packages.activatePackage('language-javascript')
describe "when the editor is deserialized", ->
it "returns undefined when the path cannot be read", ->
pathToOpen = path.join(temp.mkdirSync(), 'file.txt')
editor1 = null
waitsForPromise ->
atom.project.open(pathToOpen).then (o) -> editor1 = o
runs ->
fs.mkdirSync(pathToOpen)
expect(editor1.testSerialization()).toBeUndefined()
it "restores selections and folds based on markers in the buffer", ->
editor.setSelectedBufferRange([[1, 2], [3, 4]])
editor.addSelectionForBufferRange([[5, 6], [7, 5]], reversed: true)

View File

@ -293,3 +293,14 @@ describe "Window", ->
pathToOpen = __dirname
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
expect(atom.workspace.open.callCount).toBe 0
describe "when the opened path is a uri", ->
it "adds it to the project's paths as is", ->
pathToOpen = 'remote://server:7644/some/dir/path'
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
waitsFor ->
atom.project.getPaths().length is 1
runs ->
expect(atom.project.getPaths()[0]).toBe pathToOpen

View File

@ -370,7 +370,12 @@ class AtomApplication
# :windowDimensions - Object with height and width keys.
# :window - {AtomWindow} to open file paths in.
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, windowDimensions, profileStartup, window}={}) ->
pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen)
pathsToOpen = pathsToOpen.map (pathToOpen) ->
if fs.existsSync(pathToOpen)
fs.normalize(pathToOpen)
else
pathToOpen
locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen)
unless pidToKillWhenClosed or newWindow
@ -522,6 +527,7 @@ class AtomApplication
locationForPathToOpen: (pathToOpen) ->
return {pathToOpen} unless pathToOpen
return {pathToOpen} if url.parse(pathToOpen).protocol?
return {pathToOpen} if fs.existsSync(pathToOpen)
pathToOpen = pathToOpen.replace(/[:\s]+$/, '')

View File

@ -5,6 +5,7 @@ app = require 'app'
fs = require 'fs-plus'
path = require 'path'
yargs = require 'yargs'
url = require 'url'
nslog = require 'nslog'
console.log = nslog
@ -45,9 +46,11 @@ start = ->
cwd = args.executedFrom?.toString() or process.cwd()
args.pathsToOpen = args.pathsToOpen.map (pathToOpen) ->
pathToOpen = fs.normalize(pathToOpen)
if cwd
path.resolve(cwd, pathToOpen)
normalizedPath = fs.normalize(pathToOpen)
if url.parse(pathToOpen).protocol?
pathToOpen
else if cwd
path.resolve(cwd, normalizedPath)
else
path.resolve(pathToOpen)

View File

@ -18,6 +18,12 @@ SequenceCount = 0
# command event listeners globally on `atom.commands` and constrain them to
# specific kinds of elements with CSS selectors.
#
# Command names must follow the `namespace:action` pattern, where `namespace`
# will typically be the name of your package, and `action` describes the
# behavior of your command. If either part consists of multiple words, these
# must be separated by hyphens. E.g. `awesome-package:turn-it-up-to-eleven`.
# All words should be lowercased.
#
# As the event bubbles upward through the DOM, all registered event listeners
# with matching selectors are invoked in order of specificity. In the event of a
# specificity tie, the most recently registered listener is invoked first. This

View File

@ -179,15 +179,19 @@ module.exports =
eol:
type: ['boolean', 'string']
default: '\u00ac'
maximumLength: 1
space:
type: ['boolean', 'string']
default: '\u00b7'
maximumLength: 1
tab:
type: ['boolean', 'string']
default: '\u00bb'
maximumLength: 1
cr:
type: ['boolean', 'string']
default: '\u00a4'
maximumLength: 1
zoomFontWhenCtrlScrolling:
type: 'boolean'
default: process.platform isnt 'darwin'

View File

@ -1060,6 +1060,12 @@ Config.addSchemaEnforcers
throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be a string")
value
validateMaximumLength: (keyPath, value, schema) ->
if typeof schema.maximumLength is 'number' and value.length > schema.maximumLength
value.slice(0, schema.maximumLength)
else
value
'null':
# null sort of isnt supported. It will just unset in this case
coerce: (keyPath, value, schema) ->
@ -1152,6 +1158,10 @@ withoutEmptyObjects = (object) ->
resultObject = object
resultObject
# TODO remove in 1.0 API
Config::unobserve = (keyPath) ->
Grim.deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.'
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(Config)
@ -1201,9 +1211,6 @@ if Grim.includeDeprecatedAPIs
Grim.deprecate 'Config::toggle is no longer supported. Please remove from your code.'
@set(keyPath, not @get(keyPath))
Config::unobserve = (keyPath) ->
Grim.deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.'
Config::addScopedSettings = (source, selector, value, options) ->
Grim.deprecate("Use ::set instead")
settingsBySelector = {}

View File

@ -100,6 +100,9 @@ class ContextMenuManager
# whether to display this item on a given context menu deployment. Called
# with the following argument:
# * `event` The click event that deployed the context menu.
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# added menu items.
add: (itemsBySelector) ->
if Grim.includeDeprecatedAPIs
# Detect deprecated file path as first argument

View File

@ -1,6 +1,7 @@
{Directory} = require 'pathwatcher'
fs = require 'fs-plus'
path = require 'path'
url = require 'url'
module.exports =
class DefaultDirectoryProvider
@ -14,14 +15,22 @@ class DefaultDirectoryProvider
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURISync: (uri) ->
projectPath = path.normalize(uri)
directoryPath = if not fs.isDirectorySync(projectPath) and fs.isDirectorySync(path.dirname(projectPath))
path.dirname(projectPath)
normalizedPath = path.normalize(uri)
{protocol} = url.parse(uri)
directoryPath = if protocol?
uri
else if not fs.isDirectorySync(normalizedPath) and fs.isDirectorySync(path.dirname(normalizedPath))
path.dirname(normalizedPath)
else
projectPath
normalizedPath
new Directory(directoryPath)
# TODO: Stop normalizing the path in pathwatcher's Directory.
directory = new Directory(directoryPath)
if protocol?
directory.path = directoryPath
if fs.isCaseInsensitive()
directory.lowerCasePath = directoryPath.toLowerCase()
directory
# Public: Create a Directory that corresponds to the specified URI.
#

View File

@ -1,16 +1,10 @@
_ = require 'underscore-plus'
{toArray} = require 'underscore-plus'
{$$} = require 'space-pen'
CursorsComponent = require './cursors-component'
HighlightsComponent = require './highlights-component'
TokenIterator = require './token-iterator'
TileComponent = require './tile-component'
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
WrapperDiv = document.createElement('div')
TokenTextEscapeRegex = /[&"'<>]/g
MaxTokenLength = 20000
cloneObject = (object) ->
clone = {}
@ -22,12 +16,7 @@ class LinesComponent
placeholderTextDiv: null
constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
@tokenIterator = new TokenIterator
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@renderedDecorationsByLineId = {}
@tileComponentsByTileId = {}
@domNode = document.createElement('div')
@domNode.classList.add('lines')
@ -48,17 +37,12 @@ class LinesComponent
updateSync: (state) ->
@newState = state.content
@oldState ?= {lines: {}}
@oldState ?= {tiles: {}}
if @newState.scrollHeight isnt @oldState.scrollHeight
@domNode.style.height = @newState.scrollHeight + 'px'
@oldState.scrollHeight = @newState.scrollHeight
if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft
@domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, #{-@newState.scrollTop}px, 0px)"
@oldState.scrollTop = @newState.scrollTop
@oldState.scrollLeft = @newState.scrollLeft
if @newState.backgroundColor isnt @oldState.backgroundColor
@domNode.style.backgroundColor = @newState.backgroundColor
@oldState.backgroundColor = @newState.backgroundColor
@ -71,8 +55,8 @@ class LinesComponent
@placeholderTextDiv.textContent = @newState.placeholderText
@domNode.appendChild(@placeholderTextDiv)
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
@updateLineNodes()
@removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
@updateTileNodes()
if @newState.scrollWidth isnt @oldState.scrollWidth
@domNode.style.width = @newState.scrollWidth + 'px'
@ -84,248 +68,35 @@ class LinesComponent
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
removeLineNodes: ->
@removeLineNode(id) for id of @oldState.lines
removeTileNodes: ->
@removeTileNode(id) for id of @oldState.tiles
return
removeLineNode: (id) ->
@lineNodesByLineId[id].remove()
delete @lineNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
delete @oldState.lines[id]
removeTileNode: (id) ->
node = @tileComponentsByTileId[id].getDomNode()
updateLineNodes: ->
for id of @oldState.lines
unless @newState.lines.hasOwnProperty(id)
@removeLineNode(id)
node.remove()
delete @tileComponentsByTileId[id]
delete @oldState.tiles[id]
newLineIds = null
newLinesHTML = null
updateTileNodes: ->
for id of @oldState.tiles
unless @newState.tiles.hasOwnProperty(id)
@removeTileNode(id)
for id, lineState of @newState.lines
if @oldState.lines.hasOwnProperty(id)
@updateLineNode(id)
for id, tileState of @newState.tiles
if @oldState.tiles.hasOwnProperty(id)
tileComponent = @tileComponentsByTileId[id]
else
newLineIds ?= []
newLinesHTML ?= ""
newLineIds.push(id)
newLinesHTML += @buildLineHTML(id)
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldState.lines[id] = cloneObject(lineState)
tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter})
return unless newLineIds?
@domNode.appendChild(tileComponent.getDomNode())
@oldState.tiles[id] = cloneObject(tileState)
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = _.toArray(WrapperDiv.children)
for id, i in newLineIds
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
@domNode.appendChild(lineNode)
tileComponent.updateSync(@newState)
return
buildLineHTML: (id) ->
{scrollWidth} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
classes = ''
if decorationClasses?
for decorationClass in decorationClasses
classes += decorationClass + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
else
lineHTML += @buildLineInnerHTML(id)
lineHTML += '<span class="fold-marker"></span>' if fold
lineHTML += "</div>"
lineHTML
buildEmptyLineInnerHTML: (id) ->
{indentGuidesVisible} = @newState
{indentLevel, tabLength, endOfLineInvisibles} = @newState.lines[id]
if indentGuidesVisible and indentLevel > 0
invisibleIndex = 0
lineHTML = ''
for i in [0...indentLevel]
lineHTML += "<span class='indent-guide'>"
for j in [0...tabLength]
if invisible = endOfLineInvisibles?[invisibleIndex++]
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
else
lineHTML += ' '
lineHTML += "</span>"
while invisibleIndex < endOfLineInvisibles?.length
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
lineHTML
else
@buildEndOfLineHTML(id) or '&nbsp;'
buildLineInnerHTML: (id) ->
lineState = @newState.lines[id]
{firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0
innerHTML = ""
@tokenIterator.reset(lineState)
while @tokenIterator.next()
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
for scope in @tokenIterator.getScopeStarts()
innerHTML += "<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
tokenStart = @tokenIterator.getScreenStart()
tokenEnd = @tokenIterator.getScreenEnd()
tokenText = @tokenIterator.getText()
isHardTab = @tokenIterator.isHardTab()
if hasLeadingWhitespace = tokenStart < firstNonWhitespaceIndex
tokenFirstNonWhitespaceIndex = firstNonWhitespaceIndex - tokenStart
else
tokenFirstNonWhitespaceIndex = null
if hasTrailingWhitespace = tokenEnd > firstTrailingWhitespaceIndex
tokenFirstTrailingWhitespaceIndex = Math.max(0, firstTrailingWhitespaceIndex - tokenStart)
else
tokenFirstTrailingWhitespaceIndex = null
hasIndentGuide =
@newState.indentGuidesVisible and
(hasLeadingWhitespace or lineIsWhitespaceOnly)
hasInvisibleCharacters =
(invisibles?.tab and isHardTab) or
(invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))
innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
for scope in @tokenIterator.getScopes()
innerHTML += "</span>"
innerHTML += @buildEndOfLineHTML(id)
innerHTML
buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
if isHardTab
classes = 'hard-tab'
classes += ' leading-whitespace' if firstNonWhitespaceIndex?
classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
return "<span class='#{classes}'>#{@escapeTokenText(tokenText)}</span>"
else
startIndex = 0
endIndex = tokenText.length
leadingHtml = ''
trailingHtml = ''
if firstNonWhitespaceIndex?
leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)
classes = 'leading-whitespace'
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
leadingHtml = "<span class='#{classes}'>#{leadingWhitespace}</span>"
startIndex = firstNonWhitespaceIndex
if firstTrailingWhitespaceIndex?
tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)
classes = 'trailing-whitespace'
classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
classes += ' invisible-character' if hasInvisibleCharacters
trailingHtml = "<span class='#{classes}'>#{trailingWhitespace}</span>"
endIndex = firstTrailingWhitespaceIndex
html = leadingHtml
if tokenText.length > MaxTokenLength
while startIndex < endIndex
html += "<span>" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + "</span>"
startIndex += MaxTokenLength
else
html += @escapeTokenText(tokenText, startIndex, endIndex)
html += trailingHtml
html
escapeTokenText: (tokenText, startIndex, endIndex) ->
if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
tokenText = tokenText.slice(startIndex, endIndex)
tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)
escapeTokenTextReplace: (match) ->
switch match
when '&' then '&amp;'
when '"' then '&quot;'
when "'" then '&#39;'
when '<' then '&lt;'
when '>' then '&gt;'
else match
buildEndOfLineHTML: (id) ->
{endOfLineInvisibles} = @newState.lines[id]
html = ''
if endOfLineInvisibles?
for invisible in endOfLineInvisibles
html += "<span class='invisible-character'>#{invisible}</span>"
html
updateLineNode: (id) ->
oldLineState = @oldState.lines[id]
newLineState = @newState.lines[id]
lineNode = @lineNodesByLineId[id]
if @newState.scrollWidth isnt @oldState.scrollWidth
lineNode.style.width = @newState.scrollWidth + 'px'
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses
if oldDecorationClasses?
for decorationClass in oldDecorationClasses
unless newDecorationClasses? and decorationClass in newDecorationClasses
lineNode.classList.remove(decorationClass)
if newDecorationClasses?
for decorationClass in newDecorationClasses
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
lineNode.classList.add(decorationClass)
oldLineState.decorationClasses = newLineState.decorationClasses
if newLineState.top isnt oldLineState.top
lineNode.style.top = newLineState.top + 'px'
oldLineState.top = newLineState.cop
if newLineState.screenRow isnt oldLineState.screenRow
lineNode.dataset.screenRow = newLineState.screenRow
oldLineState.screenRow = newLineState.screenRow
@lineIdsByScreenRow[newLineState.screenRow] = id
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
measureLineHeightAndDefaultCharWidth: ->
@domNode.appendChild(DummyLineNode)
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
@ -343,59 +114,18 @@ class LinesComponent
measureCharactersInNewLines: ->
@presenter.batchCharacterMeasurement =>
for id, lineState of @oldState.lines
unless @measuredLines.has(id)
lineNode = @lineNodesByLineId[id]
@measureCharactersInLine(id, lineState, lineNode)
for id, component of @tileComponentsByTileId
component.measureCharactersInNewLines()
return
measureCharactersInLine: (lineId, tokenizedLine, lineNode) ->
rangeForMeasurement = null
iterator = null
charIndex = 0
@tokenIterator.reset(tokenizedLine)
while @tokenIterator.next()
scopes = @tokenIterator.getScopes()
text = @tokenIterator.getText()
charWidths = @presenter.getScopedCharacterWidths(scopes)
textIndex = 0
while textIndex < text.length
if @tokenIterator.isPairedCharacter()
char = text
charLength = 2
textIndex += 2
else
char = text[textIndex]
charLength = 1
textIndex++
continue if char is '\0'
unless charWidths[char]?
unless textNode?
rangeForMeasurement ?= document.createRange()
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
textNode = iterator.nextNode()
textNodeIndex = 0
nextTextNodeIndex = textNode.textContent.length
while nextTextNodeIndex <= charIndex
textNode = iterator.nextNode()
textNodeIndex = nextTextNodeIndex
nextTextNodeIndex = textNodeIndex + textNode.textContent.length
i = charIndex - textNodeIndex
rangeForMeasurement.setStart(textNode, i)
rangeForMeasurement.setEnd(textNode, i + charLength)
charWidth = rangeForMeasurement.getBoundingClientRect().width
@presenter.setScopedCharacterWidth(scopes, char, charWidth)
charIndex += charLength
@measuredLines.add(lineId)
clearScopedCharWidths: ->
@measuredLines.clear()
for id, component of @tileComponentsByTileId
component.clearMeasurements()
@presenter.clearScopedCharacterWidths()
lineNodeForScreenRow: (screenRow) ->
tile = @presenter.tileForRow(screenRow)
@tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow)

View File

@ -170,7 +170,7 @@ class Package
if @mainModule?
if @mainModule.config? and typeof @mainModule.config is 'object'
atom.config.setSchema @name, {type: 'object', properties: @mainModule.config}
else if includeDeprecatedAPIs and @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object'
else if @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object'
deprecate("""Use a config schema instead. See the configuration section
of https://atom.io/docs/latest/hacking-atom-package-word-count and
https://atom.io/docs/api/latest/Config for more details""", {packageName: @name})
@ -203,16 +203,15 @@ class Package
try
itemsBySelector = map['context-menu']
if includeDeprecatedAPIs
# Detect deprecated format for items object
for key, value of itemsBySelector
unless _.isArray(value)
deprecate("""
The context menu CSON format has changed. Please see
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format
for more info.
""", {packageName: @name})
itemsBySelector = atom.contextMenu.convertLegacyItemsBySelector(itemsBySelector)
# Detect deprecated format for items object
for key, value of itemsBySelector
unless _.isArray(value)
deprecate("""
The context menu CSON format has changed. Please see
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format
for more info.
""", {packageName: @name})
itemsBySelector = atom.contextMenu.convertLegacyItemsBySelector(itemsBySelector)
@activationDisposables.add(atom.contextMenu.add(itemsBySelector))
catch error
@ -277,7 +276,7 @@ class Package
[stylesheetPath, atom.themes.loadStylesheet(stylesheetPath, true)]
getStylesheetsPath: ->
if includeDeprecatedAPIs and fs.isDirectorySync(path.join(@path, 'stylesheets'))
if fs.isDirectorySync(path.join(@path, 'stylesheets'))
deprecate("Store package style sheets in the `styles/` directory instead of `stylesheets/` in the `#{@name}` package", packageName: @name)
path.join(@path, 'stylesheets')
else
@ -353,7 +352,7 @@ class Package
deferred = Q.defer()
if includeDeprecatedAPIs and fs.isDirectorySync(path.join(@path, 'scoped-properties'))
if fs.isDirectorySync(path.join(@path, 'scoped-properties'))
settingsDirPath = path.join(@path, 'scoped-properties')
deprecate("Store package settings files in the `settings/` directory instead of `scoped-properties/`", packageName: @name)
else
@ -489,7 +488,7 @@ class Package
else if _.isArray(commands)
@activationCommands[selector].push(commands...)
if includeDeprecatedAPIs and @metadata.activationEvents?
if @metadata.activationEvents?
deprecate("""
Use `activationCommands` instead of `activationEvents` in your package.json
Commands should be grouped by selector as follows:

View File

@ -100,11 +100,12 @@ class Task
@childProcess.removeAllListeners()
@childProcess.on 'message', ({event, args}) =>
@emit(event, args...) if @childProcess?
# Catch the errors that happened before task-bootstrap.
@childProcess.stdout.on 'data', (data) ->
console.log data.toString()
@childProcess.stderr.on 'data', (data) ->
console.error data.toString()
@childProcess.stdout.removeAllListeners()
@childProcess.stdout.on 'data', (data) -> console.log data.toString()
@childProcess.stderr.removeAllListeners()
@childProcess.stderr.on 'data', (data) -> console.error data.toString()
# Public: Starts the task.
#
@ -152,6 +153,8 @@ class Task
return unless @childProcess?
@childProcess.removeAllListeners()
@childProcess.stdout.removeAllListeners()
@childProcess.stderr.removeAllListeners()
@childProcess.kill()
@childProcess = null

View File

@ -18,7 +18,7 @@ class TextEditorComponent
scrollSensitivity: 0.4
cursorBlinkPeriod: 800
cursorBlinkResumeDelay: 100
lineOverdrawMargin: 15
tileSize: 12
pendingScrollTop: null
pendingScrollLeft: null
@ -36,8 +36,8 @@ class TextEditorComponent
gutterComponent: null
mounted: true
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, lineOverdrawMargin}) ->
@lineOverdrawMargin = lineOverdrawMargin if lineOverdrawMargin?
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize}) ->
@tileSize = tileSize if tileSize?
@disposables = new CompositeDisposable
@observeConfig()
@ -47,7 +47,7 @@ class TextEditorComponent
model: @editor
scrollTop: @editor.getScrollTop()
scrollLeft: @editor.getScrollLeft()
lineOverdrawMargin: @lineOverdrawMargin
tileSize: tileSize
cursorBlinkPeriod: @cursorBlinkPeriod
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
stoppedScrollingDelay: 200
@ -768,8 +768,8 @@ class TextEditorComponent
{clientX, clientY} = event
linesClientRect = @linesComponent.getDomNode().getBoundingClientRect()
top = clientY - linesClientRect.top
left = clientX - linesClientRect.left
top = clientY - linesClientRect.top + @presenter.scrollTop
left = clientX - linesClientRect.left + @presenter.scrollLeft
{top, left}
getModel: ->

View File

@ -15,8 +15,9 @@ class TextEditorElement extends HTMLElement
componentDescriptor: null
component: null
attached: false
lineOverdrawMargin: null
tileSize: null
focusOnAttach: false
hasTiledRendering: true
createdCallback: ->
@emitter = new Emitter
@ -110,7 +111,7 @@ class TextEditorElement extends HTMLElement
rootElement: @rootElement
stylesElement: @stylesElement
editor: @model
lineOverdrawMargin: @lineOverdrawMargin
tileSize: @tileSize
useShadowDOM: @useShadowDOM
)
@rootElement.appendChild(@component.getDomNode())

View File

@ -15,11 +15,12 @@ class TextEditorPresenter
constructor: (params) ->
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@gutterWidth ?= 0
@tileSize ?= 12
@disposables = new CompositeDisposable
@emitter = new Emitter
@ -79,7 +80,7 @@ class TextEditorPresenter
@updateHiddenInputState() if @shouldUpdateHiddenInputState
@updateContentState() if @shouldUpdateContentState
@updateDecorations() if @shouldUpdateDecorations
@updateLinesState() if @shouldUpdateLinesState
@updateTilesState() if @shouldUpdateTilesState
@updateCursorsState() if @shouldUpdateCursorsState
@updateOverlaysState() if @shouldUpdateOverlaysState
@updateLineNumberGutterState() if @shouldUpdateLineNumberGutterState
@ -101,7 +102,7 @@ class TextEditorPresenter
@shouldUpdateHiddenInputState = false
@shouldUpdateContentState = false
@shouldUpdateDecorations = false
@shouldUpdateLinesState = false
@shouldUpdateTilesState = false
@shouldUpdateCursorsState = false
@shouldUpdateOverlaysState = false
@shouldUpdateLineNumberGutterState = false
@ -119,7 +120,8 @@ class TextEditorPresenter
@shouldUpdateScrollbarsState = true
@shouldUpdateContentState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateCursorsState = true
@shouldUpdateTilesState = true
@shouldUpdateLineNumberGutterState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateGutterOrderState = true
@ -139,7 +141,7 @@ class TextEditorPresenter
@shouldUpdateScrollbarsState = true
@shouldUpdateContentState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
@shouldUpdateLineNumberGutterState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateGutterOrderState = true
@ -214,7 +216,7 @@ class TextEditorPresenter
content:
scrollingVertically: false
cursorsVisible: false
lines: {}
tiles: {}
highlights: {}
overlays: {}
gutters: []
@ -240,7 +242,7 @@ class TextEditorPresenter
@updateHiddenInputState()
@updateContentState()
@updateDecorations()
@updateLinesState()
@updateTilesState()
@updateCursorsState()
@updateOverlaysState()
@updateLineNumberGutterState()
@ -291,8 +293,6 @@ class TextEditorPresenter
{top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange())
if @focused
top -= @scrollTop
left -= @scrollLeft
@state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0)
@state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0)
else
@ -309,56 +309,84 @@ class TextEditorPresenter
@state.content.backgroundColor = if @model.isMini() then null else @backgroundColor
@state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null
updateLinesState: ->
tileForRow: (row) ->
row - (row % @tileSize)
getStartTileRow: ->
Math.max(0, @tileForRow(@startRow))
getEndTileRow: ->
Math.min(
@tileForRow(@model.getScreenLineCount()), @tileForRow(@endRow)
)
updateTilesState: ->
return unless @startRow? and @endRow? and @lineHeight?
visibleTiles = {}
for startRow in [@getStartTileRow()..@getEndTileRow()] by @tileSize
endRow = Math.min(@model.getScreenLineCount(), startRow + @tileSize)
tile = @state.content.tiles[startRow] ?= {}
tile.top = startRow * @lineHeight - @scrollTop
tile.left = -@scrollLeft
tile.height = @tileSize * @lineHeight
tile.display = "block"
@updateLinesState(tile, startRow, endRow)
visibleTiles[startRow] = true
if @mouseWheelScreenRow? and @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)?
mouseWheelTile = @tileForRow(@mouseWheelScreenRow)
unless visibleTiles[mouseWheelTile]?
@state.content.tiles[mouseWheelTile].display = "none"
visibleTiles[mouseWheelTile] = true
for id, tile of @state.content.tiles
continue if visibleTiles.hasOwnProperty(id)
delete @state.content.tiles[id]
updateLinesState: (tileState, startRow, endRow) ->
tileState.lines ?= {}
visibleLineIds = {}
row = @startRow
while row < @endRow
row = startRow
while row < endRow
line = @model.tokenizedLineForScreenRow(row)
unless line?
throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}")
visibleLineIds[line.id] = true
if @state.content.lines.hasOwnProperty(line.id)
@updateLineState(row, line)
if tileState.lines.hasOwnProperty(line.id)
lineState = tileState.lines[line.id]
lineState.screenRow = row
lineState.top = (row - startRow) * @lineHeight
lineState.decorationClasses = @lineDecorationClassesForRow(row)
else
@buildLineState(row, line)
tileState.lines[line.id] =
screenRow: row
text: line.text
openScopes: line.openScopes
tags: line.tags
specialTokens: line.specialTokens
firstNonWhitespaceIndex: line.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line.firstTrailingWhitespaceIndex
invisibles: line.invisibles
endOfLineInvisibles: line.endOfLineInvisibles
isOnlyWhitespace: line.isOnlyWhitespace()
indentLevel: line.indentLevel
tabLength: line.tabLength
fold: line.fold
top: (row - startRow) * @lineHeight
decorationClasses: @lineDecorationClassesForRow(row)
row++
if @mouseWheelScreenRow?
if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)
visibleLineIds[preservedLine.id] = true
for id, line of @state.content.lines
unless visibleLineIds.hasOwnProperty(id)
delete @state.content.lines[id]
for id, line of tileState.lines
delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
return
updateLineState: (row, line) ->
lineState = @state.content.lines[line.id]
lineState.screenRow = row
lineState.top = row * @lineHeight
lineState.decorationClasses = @lineDecorationClassesForRow(row)
buildLineState: (row, line) ->
@state.content.lines[line.id] =
screenRow: row
text: line.text
openScopes: line.openScopes
tags: line.tags
specialTokens: line.specialTokens
firstNonWhitespaceIndex: line.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line.firstTrailingWhitespaceIndex
invisibles: line.invisibles
endOfLineInvisibles: line.endOfLineInvisibles
isOnlyWhitespace: line.isOnlyWhitespace()
indentLevel: line.indentLevel
tabLength: line.tabLength
fold: line.fold
top: row * @lineHeight
decorationClasses: @lineDecorationClassesForRow(row)
updateCursorsState: ->
@state.content.cursors = {}
@updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation
@ -391,12 +419,10 @@ class TextEditorPresenter
else
screenPosition = decoration.getMarker().getHeadScreenPosition()
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
pixelPosition = @pixelPositionForScreenPosition(screenPosition, true)
{scrollTop, scrollLeft} = @state.content
top = pixelPosition.top + @lineHeight - scrollTop
left = pixelPosition.left + @gutterWidth - scrollLeft
top = pixelPosition.top + @lineHeight
left = pixelPosition.left + @gutterWidth
if overlayDimensions = @overlayDimensions[decoration.id]
{itemWidth, itemHeight, contentMargin} = overlayDimensions
@ -578,7 +604,7 @@ class TextEditorPresenter
updateStartRow: ->
return unless @scrollTop? and @lineHeight?
startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin
startRow = Math.floor(@scrollTop / @lineHeight)
@startRow = Math.max(0, startRow)
updateEndRow: ->
@ -586,7 +612,7 @@ class TextEditorPresenter
startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight))
visibleLinesCount = Math.ceil(@height / @lineHeight) + 1
endRow = startRow + visibleLinesCount + @lineOverdrawMargin
endRow = startRow + visibleLinesCount
@endRow = Math.min(@model.getScreenLineCount(), endRow)
@markerObservationWindow.setScreenRange(Range(Point(@startRow, 0), Point(@endRow, 0)))
@ -620,6 +646,7 @@ class TextEditorPresenter
oldContentWidth = @contentWidth
clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped()
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left
@contentWidth += @scrollLeft
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
if @contentHeight isnt oldContentHeight
@ -765,7 +792,7 @@ class TextEditorPresenter
@shouldUpdateVerticalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
@shouldUpdateCursorsState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@ -788,7 +815,7 @@ class TextEditorPresenter
@state.content.scrollingVertically = false
if @mouseWheelScreenRow?
@mouseWheelScreenRow = null
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@ -802,8 +829,10 @@ class TextEditorPresenter
@model.setScrollLeft(scrollLeft)
@shouldUpdateHorizontalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateCursorsState = true unless oldScrollLeft?
@shouldUpdateCursorsState = true
@shouldUpdateOverlaysState = true
@shouldUpdateDecorations = true
@shouldUpdateTilesState = true
@emitDidUpdateState()
@ -851,7 +880,7 @@ class TextEditorPresenter
@shouldUpdateVerticalScrollState = true
@shouldUpdateScrollbarsState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
@shouldUpdateCursorsState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@ -879,7 +908,7 @@ class TextEditorPresenter
@shouldUpdateScrollbarsState = true
@shouldUpdateContentState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
@shouldUpdateCursorsState = true unless oldContentFrameWidth?
@emitDidUpdateState()
@ -945,7 +974,7 @@ class TextEditorPresenter
@shouldUpdateScrollbarsState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
@shouldUpdateCursorsState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@ -953,9 +982,9 @@ class TextEditorPresenter
@emitDidUpdateState()
setMouseWheelScreenRow: (mouseWheelScreenRow) ->
unless @mouseWheelScreenRow is mouseWheelScreenRow
@mouseWheelScreenRow = mouseWheelScreenRow
setMouseWheelScreenRow: (screenRow) ->
if @mouseWheelScreenRow isnt screenRow
@mouseWheelScreenRow = screenRow
@didStartScrolling()
setBaseCharacterWidth: (baseCharacterWidth) ->
@ -997,7 +1026,7 @@ class TextEditorPresenter
@shouldUpdateHiddenInputState = true
@shouldUpdateContentState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
@shouldUpdateCursorsState = true
@shouldUpdateOverlaysState = true
@ -1038,10 +1067,13 @@ class TextEditorPresenter
charLength = 1
valueIndex++
return {top, left} if column is targetColumn
break if column is targetColumn
left += characterWidths[char] ? baseCharacterWidth unless char is '\0'
column += charLength
top -= @scrollTop
left -= @scrollLeft
{top, left}
hasPixelRectRequirements: ->
@ -1104,7 +1136,7 @@ class TextEditorPresenter
if decoration.isType('overlay')
@shouldUpdateOverlaysState = true
if decoration.isType('line')
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
if decoration.isType('line-number')
@shouldUpdateLineNumbersState = true
else if decoration.isType('gutter')
@ -1119,7 +1151,7 @@ class TextEditorPresenter
decoration.getMarker().getScreenRange())
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
if decoration.isType('line') or Decoration.isType(oldProperties, 'line')
@shouldUpdateLinesState = true
@shouldUpdateTilesState = true
if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number')
@shouldUpdateLineNumbersState = true
if (decoration.isType('gutter') and not decoration.isType('line-number')) or
@ -1135,7 +1167,7 @@ class TextEditorPresenter
didDestroyDecoration: (decoration) ->
if decoration.isType('line') or decoration.isType('gutter')
@removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
@shouldUpdateLinesState = true if decoration.isType('line')
@shouldUpdateTilesState = true if decoration.isType('line')
if decoration.isType('line-number')
@shouldUpdateLineNumbersState = true
else if decoration.isType('gutter')
@ -1160,7 +1192,7 @@ class TextEditorPresenter
if decoration.isType('line') or decoration.isType('gutter')
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
@shouldUpdateLinesState = true if decoration.isType('line')
@shouldUpdateTilesState = true if decoration.isType('line')
if decoration.isType('line-number')
@shouldUpdateLineNumbersState = true
else if decoration.isType('gutter')

View File

@ -60,7 +60,7 @@ class TextEditorView extends View
placeholderText: placeholderText
element = new TextEditorElement
element.lineOverdrawMargin = props?.lineOverdrawMargin
element.tileSize = props?.tileSize
element.setAttribute(name, value) for name, value of attributes if attributes?
element.setModel(model)
return element.__spacePenView

View File

@ -128,7 +128,15 @@ class TextEditor extends Model
displayBuffer: @displayBuffer.serialize()
deserializeParams: (params) ->
params.displayBuffer = DisplayBuffer.deserialize(params.displayBuffer)
try
displayBuffer = DisplayBuffer.deserialize(params.displayBuffer)
catch error
if error.syscall is 'read'
return # Error reading the file, don't deserialize an editor for it
else
throw error
params.displayBuffer = displayBuffer
params.registerEditor = true
params

361
src/tile-component.coffee Normal file
View File

@ -0,0 +1,361 @@
_ = require 'underscore-plus'
TokenIterator = require './token-iterator'
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
WrapperDiv = document.createElement('div')
TokenTextEscapeRegex = /[&"'<>]/g
MaxTokenLength = 20000
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
clone
module.exports =
class TileComponent
placeholderTextDiv: null
constructor: ({@presenter, @id}) ->
@tokenIterator = new TokenIterator
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@domNode = document.createElement("div")
@domNode.classList.add("tile")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
getDomNode: ->
@domNode
updateSync: (state) ->
@newState = state
unless @oldState
@oldState = {tiles: {}}
@oldState.tiles[@id] = {lines: {}}
@newTileState = @newState.tiles[@id]
@oldTileState = @oldState.tiles[@id]
if @newTileState.display isnt @oldTileState.display
@domNode.style.display = @newTileState.display
@oldTileState.display = @newTileState.display
if @newTileState.height isnt @oldTileState.height
@domNode.style.height = @newTileState.height + 'px'
@oldTileState.height = @newTileState.height
if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left
@domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)"
@oldTileState.top = @newTileState.top
@oldTileState.left = @newTileState.left
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
@updateLineNodes()
if @newState.scrollWidth isnt @oldState.scrollWidth
@domNode.style.width = @newState.scrollWidth + 'px'
@oldState.scrollWidth = @newState.scrollWidth
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
removeLineNodes: ->
@removeLineNode(id) for id of @oldTileState.lines
return
removeLineNode: (id) ->
@lineNodesByLineId[id].remove()
delete @lineNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
delete @oldTileState.lines[id]
updateLineNodes: ->
for id of @oldTileState.lines
unless @newTileState.lines.hasOwnProperty(id)
@removeLineNode(id)
newLineIds = null
newLinesHTML = null
for id, lineState of @newTileState.lines
if @oldTileState.lines.hasOwnProperty(id)
@updateLineNode(id)
else
newLineIds ?= []
newLinesHTML ?= ""
newLineIds.push(id)
newLinesHTML += @buildLineHTML(id)
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldTileState.lines[id] = cloneObject(lineState)
return unless newLineIds?
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = _.toArray(WrapperDiv.children)
for id, i in newLineIds
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
@domNode.appendChild(lineNode)
return
buildLineHTML: (id) ->
{scrollWidth} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
classes = ''
if decorationClasses?
for decorationClass in decorationClasses
classes += decorationClass + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
else
lineHTML += @buildLineInnerHTML(id)
lineHTML += '<span class="fold-marker"></span>' if fold
lineHTML += "</div>"
lineHTML
buildEmptyLineInnerHTML: (id) ->
{indentGuidesVisible} = @newState
{indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
if indentGuidesVisible and indentLevel > 0
invisibleIndex = 0
lineHTML = ''
for i in [0...indentLevel]
lineHTML += "<span class='indent-guide'>"
for j in [0...tabLength]
if invisible = endOfLineInvisibles?[invisibleIndex++]
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
else
lineHTML += ' '
lineHTML += "</span>"
while invisibleIndex < endOfLineInvisibles?.length
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
lineHTML
else
@buildEndOfLineHTML(id) or '&nbsp;'
buildLineInnerHTML: (id) ->
lineState = @newTileState.lines[id]
{firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0
innerHTML = ""
@tokenIterator.reset(lineState)
while @tokenIterator.next()
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
for scope in @tokenIterator.getScopeStarts()
innerHTML += "<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
tokenStart = @tokenIterator.getScreenStart()
tokenEnd = @tokenIterator.getScreenEnd()
tokenText = @tokenIterator.getText()
isHardTab = @tokenIterator.isHardTab()
if hasLeadingWhitespace = tokenStart < firstNonWhitespaceIndex
tokenFirstNonWhitespaceIndex = firstNonWhitespaceIndex - tokenStart
else
tokenFirstNonWhitespaceIndex = null
if hasTrailingWhitespace = tokenEnd > firstTrailingWhitespaceIndex
tokenFirstTrailingWhitespaceIndex = Math.max(0, firstTrailingWhitespaceIndex - tokenStart)
else
tokenFirstTrailingWhitespaceIndex = null
hasIndentGuide =
@newState.indentGuidesVisible and
(hasLeadingWhitespace or lineIsWhitespaceOnly)
hasInvisibleCharacters =
(invisibles?.tab and isHardTab) or
(invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))
innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
for scope in @tokenIterator.getScopes()
innerHTML += "</span>"
innerHTML += @buildEndOfLineHTML(id)
innerHTML
buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
if isHardTab
classes = 'hard-tab'
classes += ' leading-whitespace' if firstNonWhitespaceIndex?
classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
return "<span class='#{classes}'>#{@escapeTokenText(tokenText)}</span>"
else
startIndex = 0
endIndex = tokenText.length
leadingHtml = ''
trailingHtml = ''
if firstNonWhitespaceIndex?
leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)
classes = 'leading-whitespace'
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
leadingHtml = "<span class='#{classes}'>#{leadingWhitespace}</span>"
startIndex = firstNonWhitespaceIndex
if firstTrailingWhitespaceIndex?
tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)
classes = 'trailing-whitespace'
classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
classes += ' invisible-character' if hasInvisibleCharacters
trailingHtml = "<span class='#{classes}'>#{trailingWhitespace}</span>"
endIndex = firstTrailingWhitespaceIndex
html = leadingHtml
if tokenText.length > MaxTokenLength
while startIndex < endIndex
html += "<span>" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + "</span>"
startIndex += MaxTokenLength
else
html += @escapeTokenText(tokenText, startIndex, endIndex)
html += trailingHtml
html
escapeTokenText: (tokenText, startIndex, endIndex) ->
if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
tokenText = tokenText.slice(startIndex, endIndex)
tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)
escapeTokenTextReplace: (match) ->
switch match
when '&' then '&amp;'
when '"' then '&quot;'
when "'" then '&#39;'
when '<' then '&lt;'
when '>' then '&gt;'
else match
buildEndOfLineHTML: (id) ->
{endOfLineInvisibles} = @newTileState.lines[id]
html = ''
if endOfLineInvisibles?
for invisible in endOfLineInvisibles
html += "<span class='invisible-character'>#{invisible}</span>"
html
updateLineNode: (id) ->
oldLineState = @oldTileState.lines[id]
newLineState = @newTileState.lines[id]
lineNode = @lineNodesByLineId[id]
if @newState.scrollWidth isnt @oldState.scrollWidth
lineNode.style.width = @newState.scrollWidth + 'px'
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses
if oldDecorationClasses?
for decorationClass in oldDecorationClasses
unless newDecorationClasses? and decorationClass in newDecorationClasses
lineNode.classList.remove(decorationClass)
if newDecorationClasses?
for decorationClass in newDecorationClasses
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
lineNode.classList.add(decorationClass)
oldLineState.decorationClasses = newLineState.decorationClasses
if newLineState.top isnt oldLineState.top
lineNode.style.top = newLineState.top + 'px'
oldLineState.top = newLineState.top
if newLineState.screenRow isnt oldLineState.screenRow
lineNode.dataset.screenRow = newLineState.screenRow
oldLineState.screenRow = newLineState.screenRow
@lineIdsByScreenRow[newLineState.screenRow] = id
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
measureCharactersInNewLines: ->
for id, lineState of @oldTileState.lines
unless @measuredLines.has(id)
lineNode = @lineNodesByLineId[id]
@measureCharactersInLine(id, lineState, lineNode)
return
measureCharactersInLine: (lineId, tokenizedLine, lineNode) ->
rangeForMeasurement = null
iterator = null
charIndex = 0
@tokenIterator.reset(tokenizedLine)
while @tokenIterator.next()
scopes = @tokenIterator.getScopes()
text = @tokenIterator.getText()
charWidths = @presenter.getScopedCharacterWidths(scopes)
textIndex = 0
while textIndex < text.length
if @tokenIterator.isPairedCharacter()
char = text
charLength = 2
textIndex += 2
else
char = text[textIndex]
charLength = 1
textIndex++
continue if char is '\0'
unless charWidths[char]?
unless textNode?
rangeForMeasurement ?= document.createRange()
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
textNode = iterator.nextNode()
textNodeIndex = 0
nextTextNodeIndex = textNode.textContent.length
while nextTextNodeIndex <= charIndex
textNode = iterator.nextNode()
textNodeIndex = nextTextNodeIndex
nextTextNodeIndex = textNodeIndex + textNode.textContent.length
i = charIndex - textNodeIndex
rangeForMeasurement.setStart(textNode, i)
rangeForMeasurement.setEnd(textNode, i + charLength)
charWidth = rangeForMeasurement.getBoundingClientRect().width
@presenter.setScopedCharacterWidth(scopes, char, charWidth)
charIndex += charLength
@measuredLines.add(lineId)
clearMeasurements: ->
@measuredLines.clear()

View File

@ -220,17 +220,20 @@ class TokenizedLine
copy: ->
copy = new TokenizedLine
copy.tokenIterator = @tokenIterator
copy.indentLevel = @indentLevel
copy.openScopes = @openScopes
copy.text = @text
copy.tags = @tags
copy.specialTokens = @specialTokens
copy.startBufferColumn = @startBufferColumn
copy.bufferDelta = @bufferDelta
copy.ruleStack = @ruleStack
copy.lineEnding = @lineEnding
copy.invisibles = @invisibles
copy.endOfLineInvisibles = @endOfLineInvisibles
copy.indentLevel = @indentLevel
copy.tabLength = @tabLength
copy.firstNonWhitespaceIndex = @firstNonWhitespaceIndex
copy.firstTrailingWhitespaceIndex = @firstTrailingWhitespaceIndex
copy.lineEnding = @lineEnding
copy.endOfLineInvisibles = @endOfLineInvisibles
copy.ruleStack = @ruleStack
copy.startBufferColumn = @startBufferColumn
copy.fold = @fold
copy

View File

@ -23,10 +23,10 @@ class WindowEventHandler
if pathToOpen? and needsProjectPaths
if fs.existsSync(pathToOpen)
atom.project.addPath(pathToOpen)
else if fs.existsSync(path.dirname(pathToOpen))
atom.project.addPath(path.dirname(pathToOpen))
else
dirToOpen = path.dirname(pathToOpen)
if fs.existsSync(dirToOpen)
atom.project.addPath(dirToOpen)
atom.project.addPath(pathToOpen)
unless fs.isDirectorySync(pathToOpen)
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})

View File

@ -25,5 +25,6 @@
@import "text-editor-light";
@import "select-list";
@import "syntax";
@import "text";
@import "utilities";
@import "octicons";

View File

@ -2,7 +2,6 @@
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src *; script-src 'self'; style-src 'self' 'unsafe-inline';">
<script src="index.js"></script>
</head>
<body tabindex="-1">

39
static/text.less Normal file
View File

@ -0,0 +1,39 @@
@import "ui-variables";
.text-bits (@type) {
@text-color-name: "text-color-@{type}";
@bg-color-name: "background-color-@{type}";
@text-color: @@text-color-name;
@bg-color: @@bg-color-name;
code {
color: @text-color;
background: fadeout(@bg-color, 80%);
}
a, a code {
text-decoration: underline;
color: darken(@text-color, 10%);
&:hover {
color: darken(@text-color, 15%);
}
}
}
.text-info {
.text-bits(info);
}
.text-success {
.text-bits(success);
}
.text-warning {
.text-bits(warning);
}
.text-error {
.text-bits(error);
}