mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 15:37:46 +03:00
Merge branch 'master' into mb-optimize-marker-observation
Conflicts: src/text-editor-component.coffee src/text-editor-presenter.coffee
This commit is contained in:
commit
76c696f1a2
22
package.json
22
package.json
@ -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",
|
||||
|
@ -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 =
|
||||
|
@ -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()
|
||||
|
@ -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])
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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 = []
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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]+$/, '')
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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 = {}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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 ' '
|
||||
|
||||
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 '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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: ->
|
||||
|
@ -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())
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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
361
src/tile-component.coffee
Normal 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 ' '
|
||||
|
||||
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 '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
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()
|
@ -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
|
||||
|
||||
|
@ -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})
|
||||
|
@ -25,5 +25,6 @@
|
||||
@import "text-editor-light";
|
||||
@import "select-list";
|
||||
@import "syntax";
|
||||
@import "text";
|
||||
@import "utilities";
|
||||
@import "octicons";
|
||||
|
@ -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
39
static/text.less
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user