diff --git a/package.json b/package.json index 5165c61eb..74a07659d 100644 --- a/package.json +++ b/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", diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 2c5ba2fc2..b75a121a1 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -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 = diff --git a/spec/default-directory-provider-spec.coffee b/spec/default-directory-provider-spec.coffee index 69370a77b..357348c4a 100644 --- a/spec/default-directory-provider-spec.coffee +++ b/spec/default-directory-provider-spec.coffee @@ -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() diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 54817fedc..d2583ed27 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -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]) diff --git a/spec/task-spec.coffee b/spec/task-spec.coffee index 81a8713ad..bddb59d86 100644 --- a/spec/task-spec.coffee +++ b/spec/task-spec.coffee @@ -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 diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 12d189035..b2bb09acd 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -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' diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 1ad18da5a..142026913 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -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 = [] diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 28dbaaff8..8ad1ce390 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -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) diff --git a/spec/window-spec.coffee b/spec/window-spec.coffee index 376a71ab3..94e605a36 100644 --- a/spec/window-spec.coffee +++ b/spec/window-spec.coffee @@ -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 diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index ae56fb1d4..fb038c07c 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -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]+$/, '') diff --git a/src/browser/main.coffee b/src/browser/main.coffee index 1af0526dd..371dd71ea 100644 --- a/src/browser/main.coffee +++ b/src/browser/main.coffee @@ -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) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 3969dc283..72098eda0 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -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 diff --git a/src/config-schema.coffee b/src/config-schema.coffee index b814e21de..fd35d2b07 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -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' diff --git a/src/config.coffee b/src/config.coffee index b97ce99ec..f781e02e9 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -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 = {} diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 0fdcf4ab6..73bcbf440 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -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 diff --git a/src/default-directory-provider.coffee b/src/default-directory-provider.coffee index 9e25e097b..da2d17593 100644 --- a/src/default-directory-provider.coffee +++ b/src/default-directory-provider.coffee @@ -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. # diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 17c904e99..23f1c0015 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -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 = "