diff --git a/apm/package.json b/apm/package.json index 1dde8b9a6..bb9fd4647 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.170.0" + "atom-package-manager": "0.171.0" } } diff --git a/build/deprecated-packages.json b/build/deprecated-packages.json index 8d41a53ea..ac980e165 100644 --- a/build/deprecated-packages.json +++ b/build/deprecated-packages.json @@ -1545,11 +1545,6 @@ "hasDeprecations": true, "latestHasDeprecations": false }, - "tree-view": { - "version": "<=0.172.0", - "hasDeprecations": true, - "latestHasDeprecations": true - }, "true-color": { "version": "<=0.4.1", "hasDeprecations": true, @@ -1689,4 +1684,4 @@ "hasDeprecations": true, "latestHasDeprecations": false } -} \ No newline at end of file +} diff --git a/build/tasks/generate-asar-task.coffee b/build/tasks/generate-asar-task.coffee index 3de5f73b6..800721fee 100644 --- a/build/tasks/generate-asar-task.coffee +++ b/build/tasks/generate-asar-task.coffee @@ -15,6 +15,7 @@ module.exports = (grunt) -> 'ctags-linux' 'ctags-win32.exe' '**/node_modules/spellchecker/**' + '**/resources/atom.png' ] unpack = "{#{unpack.join(',')}}" diff --git a/build/tasks/install-task.coffee b/build/tasks/install-task.coffee index 71d3c4ae8..86a827a1b 100644 --- a/build/tasks/install-task.coffee +++ b/build/tasks/install-task.coffee @@ -31,8 +31,6 @@ module.exports = (grunt) -> binDir = path.join(installDir, 'bin') shareDir = path.join(installDir, 'share', 'atom') - iconName = path.join(shareDir, 'resources', 'app', 'resources', 'atom.png') - mkdir binDir cp 'atom.sh', path.join(binDir, 'atom') rm shareDir @@ -46,7 +44,7 @@ module.exports = (grunt) -> desktopInstallFile = path.join(installDir, 'share', 'applications', 'atom.desktop') {description} = grunt.file.readJSON('package.json') - iconName = path.join(shareDir, 'resources', 'app', 'resources', 'atom.png') + iconName = path.join(shareDir, 'resources', 'app.asar.unpacked', 'resources', 'atom.png') executable = path.join(shareDir, 'atom') template = _.template(String(fs.readFileSync(desktopFile))) filled = template({description, iconName, executable}) diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index 4874489ab..7b81786b7 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -30,7 +30,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. ### Arch * `sudo pacman -S gconf base-devel git nodejs libgnome-keyring python2` -* `export PYTHON=/usr/bin/python2` before building Atom. +* `export python=/usr/bin/python2` before building Atom. ### Slackware diff --git a/package.json b/package.json index dd5ffbbec..c331fb413 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "0.206.0", + "version": "0.208.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { @@ -64,7 +64,7 @@ "space-pen": "3.8.2", "stacktrace-parser": "0.1.1", "temp": "0.8.1", - "text-buffer": "6.2.0", + "text-buffer": "6.3.2", "theorist": "^1.0.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", @@ -78,28 +78,28 @@ "base16-tomorrow-dark-theme": "0.26.0", "base16-tomorrow-light-theme": "0.9.0", "one-dark-ui": "0.9.0", - "one-dark-syntax": "0.7.0", + "one-dark-syntax": "0.7.1", "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", + "archive-view": "0.58.0", "autocomplete-atom-api": "0.9.0", "autocomplete-css": "0.7.2", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.17.3", "autocomplete-snippets": "1.7.0", - "autoflow": "0.24.0", + "autoflow": "0.25.0", "autosave": "0.20.0", "background-tips": "0.25.0", "bookmarks": "0.35.0", - "bracket-matcher": "0.75.0", + "bracket-matcher": "0.76.0", "command-palette": "0.36.0", "deprecation-cop": "0.52.0", "dev-live-reload": "0.46.0", "encoding-selector": "0.20.0", "exception-reporting": "0.24.0", - "find-and-replace": "0.171.0", + "find-and-replace": "0.172.0", "fuzzy-finder": "0.87.0", "git-diff": "0.55.0", "go-to-line": "0.30.0", @@ -110,46 +110,46 @@ "link": "0.30.0", "markdown-preview": "0.150.0", "metrics": "0.51.0", - "notifications": "0.50.0", + "notifications": "0.52.0", "open-on-github": "0.37.0", "package-generator": "0.39.0", - "release-notes": "0.52.0", - "settings-view": "0.207.0", - "snippets": "0.93.0", - "spell-check": "0.58.0", + "release-notes": "0.53.0", + "settings-view": "0.208.0", + "snippets": "0.94.0", + "spell-check": "0.59.0", "status-bar": "0.74.0", "styleguide": "0.44.0", - "symbols-view": "0.97.0", - "tabs": "0.73.0", + "symbols-view": "0.98.0", + "tabs": "0.76.0", "timecop": "0.31.0", - "tree-view": "0.171.0", + "tree-view": "0.172.0", "update-package-dependencies": "0.10.0", - "welcome": "0.27.0", - "whitespace": "0.29.0", + "welcome": "0.28.0", + "whitespace": "0.30.0", "wrap-guide": "0.35.0", "language-c": "0.45.0", "language-clojure": "0.15.0", - "language-coffee-script": "0.40.0", + "language-coffee-script": "0.41.0", "language-csharp": "0.5.0", "language-css": "0.30.0", "language-gfm": "0.77.0", "language-git": "0.10.0", "language-go": "0.26.0", - "language-html": "0.39.0", + "language-html": "0.40.0", "language-hyperlink": "0.13.0", "language-java": "0.15.0", - "language-javascript": "0.78.0", + "language-javascript": "0.80.0", "language-json": "0.15.0", "language-less": "0.27.0", "language-make": "0.14.0", "language-mustache": "0.11.0", "language-objective-c": "0.15.0", - "language-perl": "0.24.0", + "language-perl": "0.25.0", "language-php": "0.24.0", "language-property-list": "0.8.0", - "language-python": "0.35.0", + "language-python": "0.36.0", "language-ruby": "0.56.0", - "language-ruby-on-rails": "0.21.0", + "language-ruby-on-rails": "0.22.0", "language-sass": "0.38.0", "language-shellscript": "0.15.0", "language-source": "0.9.0", diff --git a/resources/linux/redhat/atom.spec.in b/resources/linux/redhat/atom.spec.in index 2bd89cb9a..8ef886e31 100644 --- a/resources/linux/redhat/atom.spec.in +++ b/resources/linux/redhat/atom.spec.in @@ -7,7 +7,7 @@ URL: https://atom.io/ AutoReqProv: no # Avoid libchromiumcontent.so missing dependency Prefix: <%= installDir %> -Requires: lsb +Requires: lsb-core-noarch %description <%= description %> diff --git a/spec/fixtures/packages/sublime-tabs/package.json b/spec/fixtures/packages/sublime-tabs/package.json new file mode 100644 index 000000000..2fd01501d --- /dev/null +++ b/spec/fixtures/packages/sublime-tabs/package.json @@ -0,0 +1,4 @@ +{ + "name": "sublime-tabs", + "version": "1.0.0" +} diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index f09f43824..26b675975 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -877,3 +877,32 @@ describe "PackageManager", -> runs -> expect(fs.isDirectorySync(autocompleteCSSPath)).toBe false expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true + + describe "when the deprecated sublime-tabs package is installed", -> + grim = require 'grim' + includeDeprecatedAPIs = null + + beforeEach -> + {includeDeprecatedAPIs} = grim + grim.includeDeprecatedAPIs = false + + afterEach -> + grim.includeDeprecatedAPIs = includeDeprecatedAPIs + + it "enables the tree-view and tabs package", -> + atom.config.pushAtKeyPath('core.disabledPackages', 'tree-view') + atom.config.pushAtKeyPath('core.disabledPackages', 'tabs') + + spyOn(atom.packages, 'getAvailablePackagePaths').andReturn [ + path.join(__dirname, 'fixtures', 'packages', 'sublime-tabs') + path.resolve(__dirname, '..', 'node_modules', 'tree-view') + path.resolve(__dirname, '..', 'node_modules', 'tabs') + ] + atom.packages.loadPackages() + + waitsFor -> + not atom.packages.isPackageDisabled('tree-view') and not atom.packages.isPackageDisabled('tabs') + + runs -> + expect(atom.packages.isPackageLoaded('tree-view')).toBe true + expect(atom.packages.isPackageLoaded('tabs')).toBe true diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index b2bb09acd..6749e7803 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -89,19 +89,19 @@ describe "TextEditorComponent", -> expect(tilesNodes.length).toBe(3) expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" - expect(tilesNodes[0].children.length).toBe(tileSize) + expect(tilesNodes[0].querySelectorAll(".line").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) + expect(tilesNodes[1].querySelectorAll(".line").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) + expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize) expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels) @@ -118,19 +118,19 @@ describe "TextEditorComponent", -> expect(tilesNodes.length).toBe(3) expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)" - expect(tilesNodes[0].children.length).toBe(tileSize) + expect(tilesNodes[0].querySelectorAll(".line").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) + expect(tilesNodes[1].querySelectorAll(".line").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) + expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize) expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels) @@ -246,8 +246,10 @@ describe "TextEditorComponent", -> # lines caused full-screen repaints after switching away from an editor # and back again Please ensure you don't cause a performance regression if # you change this behavior. + editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth() + for lineNode in lineNodes - expect(lineNode.style.width).toBe editor.getScrollWidth() + 'px' + expect(lineNode.style.width).toBe editorFullWidth + 'px' componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px' component.measureDimensions() @@ -957,14 +959,15 @@ describe "TextEditorComponent", -> it "renders 2 regions for 2-line selections", -> editor.setSelectedScreenRange([[1, 6], [2, 10]]) nextAnimationFrame() - regions = componentNode.querySelectorAll('.selection .region') + tileNode = componentNode.querySelectorAll(".tile")[0] + regions = tileNode.querySelectorAll('.selection .region') expect(regions.length).toBe 2 region1Rect = regions[0].getBoundingClientRect() expect(region1Rect.top).toBe 1 * lineHeightInPixels expect(region1Rect.height).toBe 1 * lineHeightInPixels expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth - expect(region1Rect.right).toBe scrollViewNode.getBoundingClientRect().right + expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right region2Rect = regions[1].getBoundingClientRect() expect(region2Rect.top).toBe 2 * lineHeightInPixels @@ -972,23 +975,49 @@ describe "TextEditorComponent", -> expect(region2Rect.left).toBe scrollViewClientLeft + 0 expect(region2Rect.width).toBe 10 * charWidth - it "renders 3 regions for selections with more than 2 lines", -> - editor.setSelectedScreenRange([[1, 6], [5, 10]]) + it "renders 3 regions per tile for selections with more than 2 lines", -> + editor.setSelectedScreenRange([[0, 6], [5, 10]]) nextAnimationFrame() - regions = componentNode.querySelectorAll('.selection .region') - expect(regions.length).toBe 3 + + # Tile 0 + tileNode = componentNode.querySelectorAll(".tile")[0] + regions = tileNode.querySelectorAll('.selection .region') + expect(regions.length).toBe(3) region1Rect = regions[0].getBoundingClientRect() - expect(region1Rect.top).toBe 1 * lineHeightInPixels + expect(region1Rect.top).toBe 0 expect(region1Rect.height).toBe 1 * lineHeightInPixels expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth - expect(region1Rect.right).toBe scrollViewNode.getBoundingClientRect().right + expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right region2Rect = regions[1].getBoundingClientRect() - expect(region2Rect.top).toBe 2 * lineHeightInPixels - expect(region2Rect.height).toBe 3 * lineHeightInPixels + expect(region2Rect.top).toBe 1 * lineHeightInPixels + expect(region2Rect.height).toBe 1 * lineHeightInPixels expect(region2Rect.left).toBe scrollViewClientLeft + 0 - expect(region2Rect.right).toBe scrollViewNode.getBoundingClientRect().right + expect(region2Rect.right).toBe tileNode.getBoundingClientRect().right + + region3Rect = regions[2].getBoundingClientRect() + expect(region3Rect.top).toBe 2 * lineHeightInPixels + expect(region3Rect.height).toBe 1 * lineHeightInPixels + expect(region3Rect.left).toBe scrollViewClientLeft + 0 + expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right + + # Tile 3 + tileNode = componentNode.querySelectorAll(".tile")[1] + regions = tileNode.querySelectorAll('.selection .region') + expect(regions.length).toBe(3) + + region1Rect = regions[0].getBoundingClientRect() + expect(region1Rect.top).toBe 3 * lineHeightInPixels + expect(region1Rect.height).toBe 1 * lineHeightInPixels + expect(region1Rect.left).toBe scrollViewClientLeft + 0 + expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right + + region2Rect = regions[1].getBoundingClientRect() + expect(region2Rect.top).toBe 4 * lineHeightInPixels + expect(region2Rect.height).toBe 1 * lineHeightInPixels + expect(region2Rect.left).toBe scrollViewClientLeft + 0 + expect(region2Rect.right).toBe tileNode.getBoundingClientRect().right region3Rect = regions[2].getBoundingClientRect() expect(region3Rect.top).toBe 5 * lineHeightInPixels @@ -1219,7 +1248,7 @@ describe "TextEditorComponent", -> expect(regions.length).toBe 1 regionRect = regions[0].style - expect(regionRect.top).toBe (9 * lineHeightInPixels - verticalScrollbarNode.scrollTop) + 'px' + expect(regionRect.top).toBe (0 + 'px') expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px' expect(regionRect.left).toBe 2 * charWidth + 'px' expect(regionRect.width).toBe 2 * charWidth + 'px' @@ -1263,10 +1292,10 @@ describe "TextEditorComponent", -> it "allows multiple space-delimited decoration classes", -> decoration.setProperties(type: 'highlight', class: 'foo bar') nextAnimationFrame() - expect(componentNode.querySelectorAll('.foo.bar').length).toBe 1 + expect(componentNode.querySelectorAll('.foo.bar').length).toBe 2 decoration.setProperties(type: 'highlight', class: 'bar baz') nextAnimationFrame() - expect(componentNode.querySelectorAll('.bar.baz').length).toBe 1 + expect(componentNode.querySelectorAll('.bar.baz').length).toBe 2 it "renders classes on the regions directly if 'deprecatedRegionClass' option is defined", -> decoration = editor.decorateMarker(marker, type: 'highlight', class: 'test-highlight', deprecatedRegionClass: 'test-highlight-region') @@ -1278,7 +1307,7 @@ describe "TextEditorComponent", -> describe "when flashing a decoration via Decoration::flash()", -> highlightNode = null beforeEach -> - highlightNode = componentNode.querySelector('.test-highlight') + highlightNode = componentNode.querySelectorAll('.test-highlight')[1] it "adds and removes the flash class specified in ::flash", -> expect(highlightNode.classList.contains('flash-class')).toBe false @@ -1314,13 +1343,15 @@ describe "TextEditorComponent", -> regionStyle = componentNode.querySelector('.test-highlight .region').style originalTop = parseInt(regionStyle.top) + expect(originalTop).toBe(2 * lineHeightInPixels) + editor.getBuffer().insert([0, 0], '\n') nextAnimationFrame() regionStyle = componentNode.querySelector('.test-highlight .region').style newTop = parseInt(regionStyle.top) - expect(newTop).toBe originalTop + lineHeightInPixels + expect(newTop).toBe(0) it "moves rendered highlights when the marker is manually moved", -> regionStyle = componentNode.querySelector('.test-highlight .region').style @@ -1330,7 +1361,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() regionStyle = componentNode.querySelector('.test-highlight .region').style - expect(parseInt(regionStyle.top)).toBe 5 * lineHeightInPixels + expect(parseInt(regionStyle.top)).toBe 2 * lineHeightInPixels describe "when a decoration is updated via Decoration::update", -> it "renders the decoration's new params", -> @@ -2340,10 +2371,10 @@ describe "TextEditorComponent", -> editor.setText("") componentNode.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode)) - currentTime += 100 + currentTime += 99 componentNode.dispatchEvent(buildTextInputEvent(data: 'y', target: inputNode)) - currentTime += 100 + currentTime += 99 componentNode.dispatchEvent(new CustomEvent('editor:duplicate-lines', bubbles: true, cancelable: true)) currentTime += 101 diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index fc52c4828..25fde69c1 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1277,12 +1277,22 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.cursorsVisible).toBe false describe ".highlights", -> - stateForHighlight = (presenter, decoration) -> - presenter.getState().content.highlights[decoration.id] + expectUndefinedStateForHighlight = (presenter, decoration) -> + for tileId, tileState of presenter.getState().content.tiles + state = stateForHighlightInTile(presenter, decoration, tileId) + expect(state).toBeUndefined() - stateForSelection = (presenter, selectionIndex) -> + stateForHighlightInTile = (presenter, decoration, tile) -> + presenter.getState().content.tiles[tile]?.highlights[decoration.id] + + stateForSelectionInTile = (presenter, selectionIndex, tile) -> selection = presenter.model.getSelections()[selectionIndex] - stateForHighlight(presenter, selection.decoration) + stateForHighlightInTile(presenter, selection.decoration, tile) + + expectUndefinedStateForSelection = (presenter, selectionIndex) -> + for tileId, tileState of presenter.getState().content.tiles + state = stateForSelectionInTile(presenter, selectionIndex, tileId) + expect(state).toBeUndefined() it "contains states for highlights that are visible on screen", -> # off-screen above @@ -1297,11 +1307,11 @@ describe "TextEditorPresenter", -> marker3 = editor.markBufferRange([[0, 6], [3, 6]]) highlight3 = editor.decorateMarker(marker3, type: 'highlight', class: 'c') - # on-screen + # on-screen, spans over 2 tiles marker4 = editor.markBufferRange([[2, 6], [4, 6]]) highlight4 = editor.decorateMarker(marker4, type: 'highlight', class: 'd') - # partially off-screen below, 2 of 3 regions on screen + # partially off-screen below, spans over 3 tiles, 2 of 3 regions on screen marker5 = editor.markBufferRange([[3, 6], [6, 6]]) highlight5 = editor.decorateMarker(marker5, type: 'highlight', class: 'e') @@ -1317,107 +1327,133 @@ describe "TextEditorPresenter", -> marker8 = editor.markBufferRange([[2, 2], [2, 2]]) highlight8 = editor.decorateMarker(marker8, type: 'highlight', class: 'h') - presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) + presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2) - expect(stateForHighlight(presenter, highlight1)).toBeUndefined() + expectUndefinedStateForHighlight(presenter, highlight1) - expectValues stateForHighlight(presenter, highlight2), { + expectValues stateForHighlightInTile(presenter, highlight2, 2), { class: 'b' regions: [ - {top: 2 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10} + {top: 0, left: 0 * 10, width: 6 * 10, height: 1 * 10} ] } - expectValues stateForHighlight(presenter, highlight3), { + expectValues stateForHighlightInTile(presenter, highlight3, 2), { class: 'c' regions: [ - {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} + {top: 0, left: 0 * 10, right: 0, height: 1 * 10} + {top: 10, left: 0 * 10, width: 6 * 10, height: 1 * 10} ] } - expectValues stateForHighlight(presenter, highlight4), { + expectValues stateForHighlightInTile(presenter, highlight4, 2), { class: 'd' regions: [ - {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} + {top: 0, left: 6 * 10, right: 0, height: 1 * 10} + {top: 10, left: 0, right: 0, height: 1 * 10} + ] + } + expectValues stateForHighlightInTile(presenter, highlight4, 4), { + class: 'd' + regions: [ + {top: 0, left: 0, width: 60, height: 1 * 10} ] } - expectValues stateForHighlight(presenter, highlight5), { + expectValues stateForHighlightInTile(presenter, highlight5, 2), { class: 'e' regions: [ - {top: 3 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10} - {top: 4 * 10 - 20, left: 0 * 10, right: 0, height: 2 * 10} + {top: 10, left: 6 * 10, right: 0, height: 1 * 10} ] } - expectValues stateForHighlight(presenter, highlight6), { + expectValues stateForHighlightInTile(presenter, highlight5, 4), { + class: 'e' + regions: [ + {top: 0, left: 0, right: 0, height: 1 * 10} + {top: 10, left: 0, right: 0, height: 1 * 10} + ] + } + + expect(stateForHighlightInTile(presenter, highlight5, 6)).toBeUndefined() + + expectValues stateForHighlightInTile(presenter, highlight6, 4), { class: 'f' regions: [ - {top: 5 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10} + {top: 10, left: 6 * 10, right: 0, height: 1 * 10} ] } - expect(stateForHighlight(presenter, highlight7)).toBeUndefined() - expect(stateForHighlight(presenter, highlight8)).toBeUndefined() + expect(stateForHighlightInTile(presenter, highlight6, 6)).toBeUndefined() + + expectUndefinedStateForHighlight(presenter, highlight7) + expectUndefinedStateForHighlight(presenter, highlight8) it "is empty until all of the required measurements are assigned", -> editor.setSelectedBufferRanges([ [[0, 2], [2, 4]], ]) - presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null) - expect(presenter.getState().content.highlights).toEqual({}) + presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null, tileSize: 2) + for tileId, tileState of presenter.getState().content.tiles + expect(tileState.highlights).toEqual({}) presenter.setExplicitHeight(25) - expect(presenter.getState().content.highlights).toEqual({}) + for tileId, tileState of presenter.getState().content.tiles + expect(tileState.highlights).toEqual({}) presenter.setLineHeight(10) - expect(presenter.getState().content.highlights).toEqual({}) + for tileId, tileState of presenter.getState().content.tiles + expect(tileState.highlights).toEqual({}) presenter.setScrollTop(0) - expect(presenter.getState().content.highlights).toEqual({}) + for tileId, tileState of presenter.getState().content.tiles + expect(tileState.highlights).toEqual({}) presenter.setBaseCharacterWidth(8) - expect(presenter.getState().content.highlights).not.toEqual({}) + assignedAnyHighlight = false + for tileId, tileState of presenter.getState().content.tiles + assignedAnyHighlight ||= _.isEqual(tileState.highlights, {}) + + expect(assignedAnyHighlight).toBe(true) it "does not include highlights for invalid markers", -> marker = editor.markBufferRange([[2, 2], [2, 4]], invalidate: 'touch') highlight = editor.decorateMarker(marker, type: 'highlight', class: 'h') - presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) + presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2) + + expect(stateForHighlightInTile(presenter, highlight, 2)).toBeDefined() - expect(stateForHighlight(presenter, highlight)).toBeDefined() expectStateUpdate presenter, -> editor.getBuffer().insert([2, 2], "stuff") - expect(stateForHighlight(presenter, highlight)).toBeUndefined() + + expectUndefinedStateForHighlight(presenter, highlight) it "updates when ::scrollTop changes", -> editor.setSelectedBufferRanges([ [[6, 2], [6, 4]], ]) - presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) + presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2) - expect(stateForSelection(presenter, 0)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 0) expectStateUpdate presenter, -> presenter.setScrollTop(5 * 10) - expect(stateForSelection(presenter, 0)).toBeDefined() + expect(stateForSelectionInTile(presenter, 0, 6)).toBeDefined() expectStateUpdate presenter, -> presenter.setScrollTop(2 * 10) - expect(stateForSelection(presenter, 0)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 0) it "updates when ::explicitHeight changes", -> editor.setSelectedBufferRanges([ [[6, 2], [6, 4]], ]) - presenter = buildPresenter(explicitHeight: 20, scrollTop: 20) + presenter = buildPresenter(explicitHeight: 20, scrollTop: 20, tileSize: 2) - expect(stateForSelection(presenter, 0)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 0) expectStateUpdate presenter, -> presenter.setExplicitHeight(60) - expect(stateForSelection(presenter, 0)).toBeDefined() + expect(stateForSelectionInTile(presenter, 0, 6)).toBeDefined() expectStateUpdate presenter, -> presenter.setExplicitHeight(20) - expect(stateForSelection(presenter, 0)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 0) it "updates when ::lineHeight changes", -> editor.setSelectedBufferRanges([ @@ -1425,26 +1461,26 @@ describe "TextEditorPresenter", -> [[3, 4], [3, 6]], ]) - presenter = buildPresenter(explicitHeight: 20, scrollTop: 0) + presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2) - expectValues stateForSelection(presenter, 0), { + expectValues stateForSelectionInTile(presenter, 0, 2), { regions: [ - {top: 2 * 10, left: 2 * 10, width: 2 * 10, height: 10} + {top: 0, left: 2 * 10, width: 2 * 10, height: 10} ] } - expect(stateForSelection(presenter, 1)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 1) expectStateUpdate presenter, -> presenter.setLineHeight(5) - expectValues stateForSelection(presenter, 0), { + expectValues stateForSelectionInTile(presenter, 0, 2), { regions: [ - {top: 2 * 5, left: 2 * 10, width: 2 * 10, height: 5} + {top: 0, left: 2 * 10, width: 2 * 10, height: 5} ] } - expectValues stateForSelection(presenter, 1), { + expectValues stateForSelectionInTile(presenter, 1, 2), { regions: [ - {top: 3 * 5, left: 4 * 10, width: 2 * 10, height: 5} + {top: 5, left: 4 * 10, width: 2 * 10, height: 5} ] } @@ -1453,14 +1489,14 @@ describe "TextEditorPresenter", -> [[2, 2], [2, 4]], ]) - presenter = buildPresenter(explicitHeight: 20, scrollTop: 0) + presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2) - expectValues stateForSelection(presenter, 0), { - regions: [{top: 2 * 10, left: 2 * 10, width: 2 * 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 0, 2), { + regions: [{top: 0, left: 2 * 10, width: 2 * 10, height: 10}] } expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20) - expectValues stateForSelection(presenter, 0), { - regions: [{top: 2 * 10, left: 2 * 20, width: 2 * 20, height: 10}] + expectValues stateForSelectionInTile(presenter, 0, 2), { + regions: [{top: 0, left: 2 * 20, width: 2 * 20, height: 10}] } it "updates when scoped character widths change", -> @@ -1472,14 +1508,14 @@ describe "TextEditorPresenter", -> [[2, 4], [2, 6]], ]) - presenter = buildPresenter(explicitHeight: 20, scrollTop: 0) + presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2) - expectValues stateForSelection(presenter, 0), { - regions: [{top: 2 * 10, left: 4 * 10, width: 2 * 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 0, 2), { + regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}] } expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20) - expectValues stateForSelection(presenter, 0), { - regions: [{top: 2 * 10, left: 4 * 10, width: 20 + 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 0, 2), { + regions: [{top: 0, left: 4 * 10, width: 20 + 10, height: 10}] } it "updates when highlight decorations are added, moved, hidden, shown, or destroyed", -> @@ -1487,74 +1523,79 @@ describe "TextEditorPresenter", -> [[1, 2], [1, 4]], [[3, 4], [3, 6]] ]) - presenter = buildPresenter(explicitHeight: 20, scrollTop: 0) + presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2) - expectValues stateForSelection(presenter, 0), { - regions: [{top: 1 * 10, left: 2 * 10, width: 2 * 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 0, 0), { + regions: [{top: 10, left: 2 * 10, width: 2 * 10, height: 10}] } - expect(stateForSelection(presenter, 1)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 1) # moving into view expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false) - expectValues stateForSelection(presenter, 1), { - regions: [{top: 2 * 10, left: 4 * 10, width: 2 * 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 1, 2), { + regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}] } # becoming empty expectStateUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false) - expect(stateForSelection(presenter, 1)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 1) # becoming non-empty expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false) - expectValues stateForSelection(presenter, 1), { - regions: [{top: 2 * 10, left: 4 * 10, width: 2 * 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 1, 2), { + regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}] } # moving out of view expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false) - expect(stateForSelection(presenter, 1)).toBeUndefined() + expectUndefinedStateForSelection(presenter, 1) # adding expectStateUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false) - expectValues stateForSelection(presenter, 2), { - regions: [{top: 1 * 10, left: 4 * 10, width: 2 * 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 2, 0), { + regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}] } # moving added selection expectStateUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false) - expectValues stateForSelection(presenter, 2), { - regions: [{top: 1 * 10, left: 4 * 10, width: 4 * 10, height: 10}] + expectValues stateForSelectionInTile(presenter, 2, 0), { + regions: [{top: 10, left: 4 * 10, width: 4 * 10, height: 10}] } # destroying destroyedSelection = editor.getSelections()[2] expectStateUpdate presenter, -> destroyedSelection.destroy() - expect(stateForHighlight(presenter, destroyedSelection.decoration)).toBeUndefined() + expectUndefinedStateForHighlight(presenter, destroyedSelection.decoration) it "updates when highlight decorations' properties are updated", -> marker = editor.markBufferPosition([2, 2]) highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a') - presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) + presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2) - expect(stateForHighlight(presenter, highlight)).toBeUndefined() + expectUndefinedStateForHighlight(presenter, highlight) expectStateUpdate presenter, -> marker.setBufferRange([[2, 2], [2, 4]]) highlight.setProperties(class: 'b', type: 'highlight') - expectValues stateForHighlight(presenter, highlight), {class: 'b'} + expectValues stateForHighlightInTile(presenter, highlight, 2), {class: 'b'} it "increments the .flashCount and sets the .flashClass and .flashDuration when the highlight model flashes", -> - presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) + presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2) marker = editor.markBufferPosition([2, 2]) highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a') expectStateUpdate presenter, -> - marker.setBufferRange([[2, 2], [2, 4]]) + marker.setBufferRange([[2, 2], [5, 2]]) highlight.flash('b', 500) - expectValues stateForHighlight(presenter, highlight), { + expectValues stateForHighlightInTile(presenter, highlight, 2), { + flashClass: 'b' + flashDuration: 500 + flashCount: 1 + } + expectValues stateForHighlightInTile(presenter, highlight, 4), { flashClass: 'b' flashDuration: 500 flashCount: 1 @@ -1562,7 +1603,12 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> highlight.flash('c', 600) - expectValues stateForHighlight(presenter, highlight), { + expectValues stateForHighlightInTile(presenter, highlight, 2), { + flashClass: 'c' + flashDuration: 600 + flashCount: 2 + } + expectValues stateForHighlightInTile(presenter, highlight, 4), { flashClass: 'c' flashDuration: 600 flashCount: 2 diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 8ad1ce390..63b01a178 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -66,6 +66,25 @@ describe "TextEditor", -> expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?' + describe "when the editor is constructed with the largeFileMode option set to true", -> + it "loads the editor but doesn't tokenize", -> + editor = null + + waitsForPromise -> + atom.workspace.open('sample.js', largeFileMode: true).then (o) -> editor = o + + runs -> + buffer = editor.getBuffer() + expect(editor.tokenizedLineForScreenRow(0).text).toBe buffer.lineForRow(0) + expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1 + expect(editor.tokenizedLineForScreenRow(1).tokens.length).toBe 2 # soft tab + expect(editor.tokenizedLineForScreenRow(12).text).toBe buffer.lineForRow(12) + expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1 + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + editor.insertText('hey"') + expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1 + expect(editor.tokenizedLineForScreenRow(1).tokens.length).toBe 2 # sof tab + describe "when the editor is constructed with an initialLine option", -> it "positions the cursor on the specified line", -> editor = null diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index dc57d3fee..23520518a 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -322,6 +322,22 @@ describe "TokenizedBuffer", -> expect(tokens[2].value).toBe " \u030b" expect(tokens[2].hasLeadingWhitespace()).toBe false + it "does not break out soft tabs across a scope boundary", -> + waitsForPromise -> + atom.packages.activatePackage('language-gfm') + + runs -> + tokenizedBuffer.setTabLength(4) + tokenizedBuffer.setGrammar(atom.grammars.selectGrammar('.md')) + buffer.setText(' 0 + + expect(length).toBe 4 + describe "when the buffer contains hard-tabs", -> beforeEach -> waitsForPromise -> diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index 009f95a1c..4918fc65f 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -224,6 +224,17 @@ describe "Workspace", -> expect(workspace.paneContainer.root.children[0]).toBe pane1 expect(workspace.paneContainer.root.children[1]).toBe pane4 + describe "when the file is large (over 2mb)", -> + it "opens the editor with largeFileMode: true", -> + spyOn(fs, 'getSizeSync').andReturn 2 * 1048577 # 2MB + + editor = null + waitsForPromise -> + workspace.open('sample.js').then (e) -> editor = e + + runs -> + expect(editor.displayBuffer.largeFileMode).toBe true + describe "when passed a path that matches a custom opener", -> it "returns the resource returned by the custom opener", -> fooOpener = (pathToOpen, options) -> {foo: pathToOpen, options} if pathToOpen?.match(/\.foo/) @@ -272,20 +283,6 @@ describe "Workspace", -> beforeEach -> atom.notifications.onDidAddNotification notificationSpy = jasmine.createSpy() - describe "when a large file is opened", -> - beforeEach -> - spyOn(fs, 'getSizeSync').andReturn 2 * 1048577 # 2MB - - it "creates a notification", -> - waitsForPromise -> - workspace.open('file1') - - runs -> - expect(notificationSpy).toHaveBeenCalled() - notification = notificationSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe 'warning' - expect(notification.getMessage()).toContain '< 2MB' - describe "when a file does not exist", -> it "creates an empty buffer for the specified path", -> waitsForPromise -> diff --git a/src/browser/auto-updater-win32.coffee b/src/browser/auto-updater-win32.coffee index 89018a396..4d043ac4e 100644 --- a/src/browser/auto-updater-win32.coffee +++ b/src/browser/auto-updater-win32.coffee @@ -51,12 +51,13 @@ class AutoUpdater @emit 'update-not-available' return + @emit 'update-available' + @installUpdate (error) => if error? @emit 'update-not-available' return - @emit 'update-available' @emit 'update-downloaded', {}, update.releaseNotes, update.version, new Date(), 'https://atom.io', => @quitAndInstall() module.exports = new AutoUpdater() diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index d98f93fe3..2519fa6d6 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -24,13 +24,13 @@ class DisplayBuffer extends Model horizontalScrollMargin: 6 scopedCharacterWidthsChangeCount: 0 - constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles}={}) -> + constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles, @largeFileMode}={}) -> super @emitter = new Emitter @disposables = new CompositeDisposable - @tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, ignoreInvisibles}) + @tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, ignoreInvisibles, @largeFileMode}) @buffer = @tokenizedBuffer.buffer @charWidthsByScope = {} @markers = {} @@ -89,13 +89,14 @@ class DisplayBuffer extends Model scrollTop: @scrollTop scrollLeft: @scrollLeft tokenizedBuffer: @tokenizedBuffer.serialize() + largeFileMode: @largeFileMode deserializeParams: (params) -> params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer) params copy: -> - newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()}) + newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @largeFileMode}) newDisplayBuffer.setScrollTop(@getScrollTop()) newDisplayBuffer.setScrollLeft(@getScrollLeft()) @@ -445,7 +446,10 @@ class DisplayBuffer extends Model @isSoftWrapped() isSoftWrapped: -> - @softWrapped ? @configSettings.softWrap ? false + if @largeFileMode + false + else + @softWrapped ? @configSettings.softWrap ? false # Set the number of characters that fit horizontally in the editor. # @@ -478,7 +482,14 @@ class DisplayBuffer extends Model # # Returns {TokenizedLine} tokenizedLineForScreenRow: (screenRow) -> - @screenLines[screenRow] + if @largeFileMode + if line = @tokenizedBuffer.tokenizedLineForRow(screenRow) + if line.text.length > @maxLineLength + @maxLineLength = line.text.length + @longestScreenRow = screenRow + line + else + @screenLines[screenRow] # Gets the screen lines for the given screen row range. # @@ -487,13 +498,19 @@ class DisplayBuffer extends Model # # Returns an {Array} of {TokenizedLine}s. tokenizedLinesForScreenRows: (startRow, endRow) -> - @screenLines[startRow..endRow] + if @largeFileMode + @tokenizedBuffer.tokenizedLinesForRows(startRow, endRow) + else + @screenLines[startRow..endRow] # Gets all the screen lines. # # Returns an {Array} of {TokenizedLine}s. getTokenizedLines: -> - new Array(@screenLines...) + if @largeFileMode + @tokenizedBuffer.tokenizedLinesForRows(0, @getLastRow()) + else + new Array(@screenLines...) indentLevelForLine: (line) -> @tokenizedBuffer.indentLevelForLine(line) @@ -506,8 +523,11 @@ class DisplayBuffer extends Model # # Returns an {Array} of buffer rows as {Numbers}s. bufferRowsForScreenRows: (startScreenRow, endScreenRow) -> - for screenRow in [startScreenRow..endScreenRow] - @rowMap.bufferRowRangeForScreenRow(screenRow)[0] + if @largeFileMode + [startScreenRow..endScreenRow] + else + for screenRow in [startScreenRow..endScreenRow] + @rowMap.bufferRowRangeForScreenRow(screenRow)[0] # Creates a new fold between two row numbers. # @@ -516,10 +536,11 @@ class DisplayBuffer extends Model # # Returns the new {Fold}. createFold: (startRow, endRow) -> - foldMarker = - @findFoldMarker({startRow, endRow}) ? - @buffer.markRange([[startRow, 0], [endRow, Infinity]], @getFoldMarkerAttributes()) - @foldForMarker(foldMarker) + unless @largeFileMode + foldMarker = + @findFoldMarker({startRow, endRow}) ? + @buffer.markRange([[startRow, 0], [endRow, Infinity]], @getFoldMarkerAttributes()) + @foldForMarker(foldMarker) isFoldedAtBufferRow: (bufferRow) -> @largestFoldContainingBufferRow(bufferRow)? @@ -611,10 +632,16 @@ class DisplayBuffer extends Model # # Returns a {Number}. screenRowForBufferRow: (bufferRow) -> - @rowMap.screenRowRangeForBufferRow(bufferRow)[0] + if @largeFileMode + bufferRow + else + @rowMap.screenRowRangeForBufferRow(bufferRow)[0] lastScreenRowForBufferRow: (bufferRow) -> - @rowMap.screenRowRangeForBufferRow(bufferRow)[1] - 1 + if @largeFileMode + bufferRow + else + @rowMap.screenRowRangeForBufferRow(bufferRow)[1] - 1 # Given a screen row, this converts it into a buffer row. # @@ -622,7 +649,10 @@ class DisplayBuffer extends Model # # Returns a {Number}. bufferRowForScreenRow: (screenRow) -> - @rowMap.bufferRowRangeForScreenRow(screenRow)[0] + if @largeFileMode + screenRow + else + @rowMap.bufferRowRangeForScreenRow(screenRow)[0] # Given a buffer range, this converts it into a screen position. # @@ -724,7 +754,10 @@ class DisplayBuffer extends Model # # Returns a {Number}. getLineCount: -> - @screenLines.length + if @largeFileMode + @tokenizedBuffer.getLineCount() + else + @screenLines.length # Gets the number of the last screen line. # @@ -759,7 +792,7 @@ class DisplayBuffer extends Model {row, column} = @buffer.clipPosition(bufferPosition) [startScreenRow, endScreenRow] = @rowMap.screenRowRangeForBufferRow(row) for screenRow in [startScreenRow...endScreenRow] - screenLine = @screenLines[screenRow] + screenLine = @tokenizedLineForScreenRow(screenRow) unless screenLine? throw new BufferToScreenConversionError "No screen line exists when converting buffer row to screen row", @@ -792,7 +825,7 @@ class DisplayBuffer extends Model bufferPositionForScreenPosition: (screenPosition, options) -> {row, column} = @clipScreenPosition(Point.fromObject(screenPosition), options) [bufferRow] = @rowMap.bufferRowRangeForScreenRow(row) - new Point(bufferRow, @screenLines[row].bufferColumnForScreenColumn(column)) + new Point(bufferRow, @tokenizedLineForScreenRow(row).bufferColumnForScreenColumn(column)) # Retrieves the grammar's token scopeDescriptor for a buffer position. # @@ -856,13 +889,13 @@ class DisplayBuffer extends Model else if column < 0 column = 0 - screenLine = @screenLines[row] + screenLine = @tokenizedLineForScreenRow(row) maxScreenColumn = screenLine.getMaxScreenColumn() if screenLine.isSoftWrapped() and column >= maxScreenColumn if wrapAtSoftNewlines row++ - column = @screenLines[row].clipScreenColumn(0) + column = @tokenizedLineForScreenRow(row).clipScreenColumn(0) else column = screenLine.clipScreenColumn(maxScreenColumn - 1) else if screenLine.isColumnInsideSoftWrapIndentation(column) @@ -870,7 +903,7 @@ class DisplayBuffer extends Model column = screenLine.clipScreenColumn(0) else row-- - column = @screenLines[row].getMaxScreenColumn() - 1 + column = @tokenizedLineForScreenRow(row).getMaxScreenColumn() - 1 else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() row++ column = 0 @@ -1137,6 +1170,8 @@ class DisplayBuffer extends Model @setScrollTop(Math.min(@getScrollTop(), @getMaxScrollTop())) if delta < 0 updateScreenLines: (startBufferRow, endBufferRow, bufferDelta=0, options={}) -> + return if @largeFileMode + startBufferRow = @rowMap.bufferRowRangeForBufferRow(startBufferRow)[0] endBufferRow = @rowMap.bufferRowRangeForBufferRow(endBufferRow - 1)[1] startScreenRow = @rowMap.screenRowRangeForBufferRow(startBufferRow)[0] diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index 5a5747d4c..5e364f90c 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -12,16 +12,11 @@ class HighlightsComponent @domNode = document.createElement('div') @domNode.classList.add('highlights') - if atom.config.get('editor.useShadowDOM') - insertionPoint = document.createElement('content') - insertionPoint.setAttribute('select', '.underlayer') - @domNode.appendChild(insertionPoint) - getDomNode: -> @domNode updateSync: (state) -> - newState = state.content.highlights + newState = state.highlights @oldState ?= {} # remove highlights diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 92823daf1..6902e8aa3 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -1,7 +1,6 @@ {$$} = require 'space-pen' CursorsComponent = require './cursors-component' -HighlightsComponent = require './highlights-component' TileComponent = require './tile-component' TiledComponent = require './tiled-component' @@ -18,9 +17,6 @@ class LinesComponent extends TiledComponent @cursorsComponent = new CursorsComponent(@presenter) @domNode.appendChild(@cursorsComponent.getDomNode()) - @highlightsComponent = new HighlightsComponent(@presenter) - @domNode.appendChild(@highlightsComponent.getDomNode()) - if @useShadowDOM insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', '.overlayer') @@ -39,11 +35,6 @@ class LinesComponent extends TiledComponent if @newState.backgroundColor isnt @oldState.backgroundColor @domNode.style.backgroundColor = @newState.backgroundColor - @oldState.backgroundColor = @newState.backgroundColor - - if @newState.scrollWidth isnt @oldState.scrollWidth - @domNode.style.width = @newState.scrollWidth + 'px' - @oldState.scrollWidth = @newState.scrollWidth afterUpdateSync: (state) -> if @newState.placeholderText isnt @oldState.placeholderText @@ -54,11 +45,18 @@ class LinesComponent extends TiledComponent @placeholderTextDiv.textContent = @newState.placeholderText @domNode.appendChild(@placeholderTextDiv) + @removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible + @updateTileNodes() + + if @newState.width isnt @oldState.width + @domNode.style.width = @newState.width + 'px' + @cursorsComponent.updateSync(state) - @highlightsComponent.updateSync(state) @oldState.indentGuidesVisible = @newState.indentGuidesVisible @oldState.scrollWidth = @newState.scrollWidth + @oldState.width = @newState.width + @oldState.backgroundColor = @newState.backgroundColor buildComponentForTile: (id) -> new TileComponent({id, @presenter}) diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 306e553a8..aeb67ea3b 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -314,6 +314,10 @@ class PackageManager @uninstallAutocompletePlus() packagePaths = @getAvailablePackagePaths() + + # TODO: remove after a few atom versions. + @migrateSublimeTabsSettings(packagePaths) + packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath)) packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath) @loadPackage(packagePath) for packagePath in packagePaths @@ -445,6 +449,14 @@ class PackageManager @uninstallDirectory(dirToRemove) return + # TODO: remove this after a few versions + migrateSublimeTabsSettings: (packagePaths) -> + return if Grim.includeDeprecatedAPIs + for packagePath in packagePaths when path.basename(packagePath) is 'sublime-tabs' + atom.config.removeAtKeyPath('core.disabledPackages', 'tree-view') + atom.config.removeAtKeyPath('core.disabledPackages', 'tabs') + return + uninstallDirectory: (directory) -> symlinkPromise = new Promise (resolve) -> fs.isSymbolicLink directory, (isSymLink) -> resolve(isSymLink) diff --git a/src/project.coffee b/src/project.coffee index 75cdb714f..c10ce4a3b 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -128,8 +128,8 @@ class Project extends Model # Prefer the following, which evaluates to a {Promise} that resolves to an # {Array} of {Repository} objects: # ``` - # Promise.all(project.getDirectories().map( - # project.repositoryForDirectory.bind(project))) + # Promise.all(atom.project.getDirectories().map( + # atom.project.repositoryForDirectory.bind(atom.project))) # ``` getRepositories: -> @repositories @@ -376,11 +376,6 @@ class Project extends Model # # Returns a promise that resolves to the {TextBuffer}. buildBuffer: (absoluteFilePath) -> - if fs.getSizeSync(absoluteFilePath) >= 2 * 1048576 # 2MB - error = new Error("Atom can only handle files < 2MB for now.") - error.code = 'EFILETOOLARGE' - throw error - buffer = new TextBuffer({filePath: absoluteFilePath}) @addBuffer(buffer) buffer.load() @@ -410,7 +405,8 @@ class Project extends Model buffer?.destroy() buildEditorForBuffer: (buffer, editorOptions) -> - editor = new TextEditor(_.extend({buffer, registerEditor: true}, editorOptions)) + largeFileMode = fs.getSizeSync(buffer.getPath()) >= 2 * 1048576 # 2MB + editor = new TextEditor(_.extend({buffer, largeFileMode, registerEditor: true}, editorOptions)) editor eachBuffer: (args...) -> diff --git a/src/styles-element.coffee b/src/styles-element.coffee index 74ebd23ba..fc3b888cf 100644 --- a/src/styles-element.coffee +++ b/src/styles-element.coffee @@ -1,5 +1,4 @@ {Emitter, CompositeDisposable} = require 'event-kit' -{includeDeprecatedAPIs} = require 'grim' class StylesElement extends HTMLElement subscriptions: null @@ -19,7 +18,7 @@ class StylesElement extends HTMLElement @styleElementClonesByOriginalElement = new WeakMap attachedCallback: -> - if includeDeprecatedAPIs and @context is 'atom-text-editor' + if @context is 'atom-text-editor' for styleElement in @children @upgradeDeprecatedSelectors(styleElement) @initialize() @@ -67,7 +66,7 @@ class StylesElement extends HTMLElement @insertBefore(styleElementClone, insertBefore) - if includeDeprecatedAPIs and @context is 'atom-text-editor' + if @context is 'atom-text-editor' @upgradeDeprecatedSelectors(styleElementClone) @emitter.emit 'did-add-style-element', styleElementClone diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 496b5cf82..b156d236a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -24,6 +24,7 @@ class TextEditorPresenter @disposables = new CompositeDisposable @emitter = new Emitter + @visibleHighlights = {} @characterWidthsByScope = {} @rangesByDecorationId = {} @lineDecorationsByScreenRow = {} @@ -307,6 +308,7 @@ class TextEditorPresenter @state.hiddenInput.width = Math.max(width, 2) updateContentState: -> + @state.content.width = Math.max(@contentWidth + @verticalScrollbarWidth, @contentFrameWidth) @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide @@ -336,6 +338,7 @@ class TextEditorPresenter tile.left = -@scrollLeft tile.height = @tileSize * @lineHeight tile.display = "block" + tile.highlights ?= {} gutterTile = @lineNumberGutter.tiles[startRow] ?= {} gutterTile.top = startRow * @lineHeight - @scrollTop @@ -1136,8 +1139,6 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = true else if decoration.isType('gutter') @shouldUpdateCustomGutterDecorationState = true - if decoration.isType('highlight') - @updateHighlightState(decoration) if decoration.isType('overlay') @shouldUpdateOverlaysState = true @@ -1147,15 +1148,14 @@ class TextEditorPresenter @observeDecoration(decoration) if decoration.isType('line') or decoration.isType('gutter') - @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) - if decoration.isType('line') - @shouldUpdateLinesState = true + @shouldUpdateDecorations = true + @shouldUpdateLinesState = true if decoration.isType('line') if decoration.isType('line-number') @shouldUpdateLineNumbersState = true else if decoration.isType('gutter') @shouldUpdateCustomGutterDecorationState = true else if decoration.isType('highlight') - @updateHighlightState(decoration) + @shouldUpdateDecorations = true else if decoration.isType('overlay') @shouldUpdateOverlaysState = true @@ -1166,8 +1166,8 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterNameAndScreenRow = {} + @visibleHighlights = {} - visibleHighlights = {} return unless 0 <= @startRow <= @endRow <= Infinity for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1) @@ -1176,11 +1176,11 @@ class TextEditorPresenter if decoration.isType('line') or decoration.isType('gutter') @addToLineDecorationCaches(decoration, range) else if decoration.isType('highlight') - visibleHighlights[decoration.id] = @updateHighlightState(decoration) + @updateHighlightState(decoration) - for id of @state.content.highlights - unless visibleHighlights[id] - delete @state.content.highlights[id] + for tileId, tileState of @state.content.tiles + for id, highlight of tileState.highlights + delete tileState.highlights[id] unless @visibleHighlights[tileId]?[id]? return @@ -1231,6 +1231,22 @@ class TextEditorPresenter return + intersectRangeWithTile: (range, tileStartRow) -> + intersectingStartRow = Math.max(tileStartRow, range.start.row) + intersectingEndRow = Math.min(tileStartRow + @tileSize - 1, range.end.row) + intersectingRange = new Range( + new Point(intersectingStartRow, 0), + new Point(intersectingEndRow, Infinity) + ) + + if intersectingStartRow is range.start.row + intersectingRange.start.column = range.start.column + + if intersectingEndRow is range.end.row + intersectingRange.end.column = range.end.column + + intersectingRange + updateHighlightState: (decoration) -> return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements() @@ -1239,7 +1255,12 @@ class TextEditorPresenter range = marker.getScreenRange() if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) - delete @state.content.highlights[decoration.id] + tileStartRow = @tileForRow(range.start.row) + tileEndRow = @tileForRow(range.end.row) + + for tile in [tileStartRow..tileEndRow] by @tileSize + delete @state.content.tiles[tile]?.highlights[decoration.id] + @emitDidUpdateState() return @@ -1251,44 +1272,72 @@ class TextEditorPresenter range.end.column = 0 if range.isEmpty() - delete @state.content.highlights[decoration.id] + tileState = @state.content.tiles[@tileForRow(range.start.row)] + delete tileState.highlights[decoration.id] @emitDidUpdateState() return - highlightState = @state.content.highlights[decoration.id] ?= { - flashCount: 0 - flashDuration: null - flashClass: null - } + flash = decoration.consumeNextFlash() - if flash = decoration.consumeNextFlash() - highlightState.flashCount++ - highlightState.flashClass = flash.class - highlightState.flashDuration = flash.duration + startTile = @tileForRow(range.start.row) + endTile = @tileForRow(range.end.row) + + for tileStartRow in [startTile..endTile] by @tileSize + rangeWithinTile = @intersectRangeWithTile(range, tileStartRow) + + continue if rangeWithinTile.isEmpty() + + tileState = @state.content.tiles[tileStartRow] ?= {highlights: {}} + highlightState = tileState.highlights[decoration.id] ?= { + flashCount: 0 + flashDuration: null + flashClass: null + } + + if flash? + highlightState.flashCount++ + highlightState.flashClass = flash.class + highlightState.flashDuration = flash.duration + + highlightState.class = properties.class + highlightState.deprecatedRegionClass = properties.deprecatedRegionClass + highlightState.regions = @buildHighlightRegions(rangeWithinTile) + + for region in highlightState.regions + @repositionRegionWithinTile(region, tileStartRow) + + @visibleHighlights[tileStartRow] ?= {} + @visibleHighlights[tileStartRow][decoration.id] = true - highlightState.class = properties.class - highlightState.deprecatedRegionClass = properties.deprecatedRegionClass - highlightState.regions = @buildHighlightRegions(range) @emitDidUpdateState() true + repositionRegionWithinTile: (region, tileStartRow) -> + region.top += @scrollTop - tileStartRow * @lineHeight + region.left += @scrollLeft + buildHighlightRegions: (screenRange) -> lineHeightInPixels = @lineHeight - startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, true) - endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, true) + startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, false) + endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, false) spannedRows = screenRange.end.row - screenRange.start.row + 1 + regions = [] + if spannedRows is 1 - [ + region = top: startPixelPosition.top height: lineHeightInPixels left: startPixelPosition.left - width: endPixelPosition.left - startPixelPosition.left - ] - else - regions = [] + if screenRange.end.column is Infinity + region.right = 0 + else + region.width = endPixelPosition.left - startPixelPosition.left + + regions.push(region) + else # First row, extending from selection start to the right side of screen regions.push( top: startPixelPosition.top @@ -1308,14 +1357,19 @@ class TextEditorPresenter # Last row, extending from left side of screen to selection end if screenRange.end.column > 0 - regions.push( + region = top: endPixelPosition.top height: lineHeightInPixels left: 0 - width: endPixelPosition.left - ) - regions + if screenRange.end.column is Infinity + region.right = 0 + else + region.width = endPixelPosition.left + + regions.push(region) + + regions setOverlayDimensions: (decorationId, itemWidth, itemHeight, contentMargin) -> @overlayDimensions[decorationId] ?= {} @@ -1360,7 +1414,6 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = true @pauseCursorBlinking() @updateCursorState(cursor) - @emitDidUpdateState() startBlinkingCursors: -> diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 4aacb3dfa..977b94ed1 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -75,7 +75,7 @@ class TextEditor extends Model 'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows', toProperty: 'languageMode' - constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible}={}) -> + constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) -> super @emitter = new Emitter @@ -84,7 +84,7 @@ class TextEditor extends Model @selections = [] buffer ?= new TextBuffer - @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini}) + @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode}) @buffer = @displayBuffer.buffer @softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true @@ -325,7 +325,7 @@ class TextEditor extends Model onWillInsertText: (callback) -> @emitter.on 'will-insert-text', callback - # Extended: Calls your `callback` adter text has been inserted. + # Extended: Calls your `callback` after text has been inserted. # # * `callback` {Function} # * `event` event {Object} diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 88e06afd2..99dfc6ced 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -1,5 +1,6 @@ _ = require 'underscore-plus' +HighlightsComponent = require './highlights-component' TokenIterator = require './token-iterator' AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} WrapperDiv = document.createElement('div') @@ -13,8 +14,6 @@ cloneObject = (object) -> module.exports = class TileComponent - placeholderTextDiv: null - constructor: ({@presenter, @id}) -> @tokenIterator = new TokenIterator @measuredLines = new Set @@ -26,6 +25,9 @@ class TileComponent @domNode.style.position = "absolute" @domNode.style.display = "block" + @highlightsComponent = new HighlightsComponent(@presenter) + @domNode.appendChild(@highlightsComponent.getDomNode()) + getDomNode: -> @domNode @@ -38,6 +40,10 @@ class TileComponent @newTileState = @newState.tiles[@id] @oldTileState = @oldState.tiles[@id] + if @newState.backgroundColor isnt @oldState.backgroundColor + @domNode.style.backgroundColor = @newState.backgroundColor + @oldState.backgroundColor = @newState.backgroundColor + if @newTileState.display isnt @oldTileState.display @domNode.style.display = @newTileState.display @oldTileState.display = @newTileState.display @@ -46,6 +52,9 @@ class TileComponent @domNode.style.height = @newTileState.height + 'px' @oldTileState.height = @newTileState.height + if @newState.width isnt @oldState.width + @domNode.style.width = @newState.width + 'px' + 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 @@ -54,12 +63,9 @@ class TileComponent @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible @updateLineNodes() - if @newState.scrollWidth isnt @oldState.scrollWidth - @domNode.style.width = @newState.scrollWidth + 'px' - @oldState.scrollWidth = @newState.scrollWidth + @highlightsComponent.updateSync(@newTileState) @oldState.indentGuidesVisible = @newState.indentGuidesVisible - @oldState.scrollWidth = @newState.scrollWidth removeLineNodes: -> @removeLineNode(id) for id of @oldTileState.lines @@ -104,7 +110,7 @@ class TileComponent return buildLineHTML: (id) -> - {scrollWidth} = @newState + {width} = @newState {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id] classes = '' @@ -113,7 +119,7 @@ class TileComponent classes += decorationClass + ' ' classes += 'line' - lineHTML = "
" + lineHTML = "
" if text is "" lineHTML += @buildEmptyLineInnerHTML(id) @@ -273,8 +279,8 @@ class TileComponent lineNode = @lineNodesByLineId[id] - if @newState.scrollWidth isnt @oldState.scrollWidth - lineNode.style.width = @newState.scrollWidth + 'px' + if @newState.width isnt @oldState.width + lineNode.style.width = @newState.width + 'px' newDecorationClasses = newLineState.decorationClasses oldDecorationClasses = oldLineState.decorationClasses diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 55d2e7e37..7fb21937d 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -24,7 +24,7 @@ class TokenizedBuffer extends Model visible: false configSettings: null - constructor: ({@buffer, @tabLength, @ignoreInvisibles}) -> + constructor: ({@buffer, @tabLength, @ignoreInvisibles, @largeFileMode}) -> @emitter = new Emitter @disposables = new CompositeDisposable @tokenIterator = new TokenIterator @@ -44,6 +44,7 @@ class TokenizedBuffer extends Model bufferPath: @buffer.getPath() tabLength: @tabLength ignoreInvisibles: @ignoreInvisibles + largeFileMode: @largeFileMode deserializeParams: (params) -> params.buffer = atom.project.bufferForPathSync(params.bufferPath) @@ -66,7 +67,7 @@ class TokenizedBuffer extends Model if grammar.injectionSelector? @retokenizeLines() if @hasTokenForSelector(grammar.injectionSelector) else - newScore = grammar.getScore(@buffer.getPath(), @buffer.getText()) + newScore = grammar.getScore(@buffer.getPath(), @getGrammarSelectionContent()) @setGrammar(grammar, newScore) if newScore > @currentGrammarScore setGrammar: (grammar, score) -> @@ -74,7 +75,7 @@ class TokenizedBuffer extends Model @grammar = grammar @rootScopeDescriptor = new ScopeDescriptor(scopes: [@grammar.scopeName]) - @currentGrammarScore = score ? grammar.getScore(@buffer.getPath(), @buffer.getText()) + @currentGrammarScore = score ? grammar.getScore(@buffer.getPath(), @getGrammarSelectionContent()) @grammarUpdateDisposable?.dispose() @grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines() @@ -105,21 +106,24 @@ class TokenizedBuffer extends Model @emit 'grammar-changed', grammar if Grim.includeDeprecatedAPIs @emitter.emit 'did-change-grammar', grammar + getGrammarSelectionContent: -> + @buffer.getTextInRange([[0, 0], [10, 0]]) + reloadGrammar: -> - if grammar = atom.grammars.selectGrammar(@buffer.getPath(), @buffer.getText()) + if grammar = atom.grammars.selectGrammar(@buffer.getPath(), @getGrammarSelectionContent()) @setGrammar(grammar) else throw new Error("No grammar found for path: #{path}") hasTokenForSelector: (selector) -> - for {tokens} in @tokenizedLines - for token in tokens + for tokenizedLine in @tokenizedLines when tokenizedLine? + for token in tokenizedLine.tokens return true if selector.matches(token.scopes) false retokenizeLines: -> lastRow = @buffer.getLastRow() - @tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, lastRow) + @tokenizedLines = new Array(lastRow + 1) @invalidRows = [] @invalidateRow(0) @fullyTokenized = false @@ -209,6 +213,8 @@ class TokenizedBuffer extends Model return invalidateRow: (row) -> + return if @largeFileMode + @invalidRows.push(row) @invalidRows.sort (a, b) -> a - b @tokenizeInBackground() @@ -230,7 +236,10 @@ class TokenizedBuffer extends Model @updateInvalidRows(start, end, delta) previousEndStack = @stackForRow(end) # used in spill detection below - newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1), @openScopesForRow(start)) + if @largeFileMode + newTokenizedLines = @buildPlaceholderTokenizedLinesForRows(start, end + delta) + else + newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1), @openScopesForRow(start)) _.spliceWithArray(@tokenizedLines, start, end - start + 1, newTokenizedLines) start = @retokenizeWhitespaceRowsIfIndentLevelChanged(start - 1, -1) @@ -248,16 +257,18 @@ class TokenizedBuffer extends Model @emitter.emit 'did-change', event retokenizeWhitespaceRowsIfIndentLevelChanged: (row, increment) -> - line = @tokenizedLines[row] + line = @tokenizedLineForRow(row) if line?.isOnlyWhitespace() and @indentLevelForRow(row) isnt line.indentLevel while line?.isOnlyWhitespace() @tokenizedLines[row] = @buildTokenizedLineForRow(row, @stackForRow(row - 1), @openScopesForRow(row)) row += increment - line = @tokenizedLines[row] + line = @tokenizedLineForRow(row) row - increment updateFoldableStatus: (startRow, endRow) -> + return [startRow, endRow] if @largeFileMode + scanStartRow = @buffer.previousNonBlankRow(startRow) ? startRow scanStartRow-- while scanStartRow > 0 and @tokenizedLineForRow(scanStartRow).isComment() scanEndRow = @buffer.nextNonBlankRow(endRow) ? endRow @@ -273,7 +284,10 @@ class TokenizedBuffer extends Model [startRow, endRow] isFoldableAtRow: (row) -> - @isFoldableCodeAtRow(row) or @isFoldableCommentAtRow(row) + if @largeFileMode + false + else + @isFoldableCodeAtRow(row) or @isFoldableCommentAtRow(row) # Returns a {Boolean} indicating whether the given buffer row starts # a a foldable row range due to the code's indentation patterns. @@ -313,7 +327,7 @@ class TokenizedBuffer extends Model tokenizedLines buildPlaceholderTokenizedLinesForRows: (startRow, endRow) -> - @buildPlaceholderTokenizedLineForRow(row) for row in [startRow..endRow] + @buildPlaceholderTokenizedLineForRow(row) for row in [startRow..endRow] by 1 buildPlaceholderTokenizedLineForRow: (row) -> openScopes = [@grammar.startIdForScope(@grammar.scopeName)] @@ -341,7 +355,12 @@ class TokenizedBuffer extends Model null tokenizedLineForRow: (bufferRow) -> - @tokenizedLines[bufferRow] + if 0 <= bufferRow < @tokenizedLines.length + @tokenizedLines[bufferRow] ?= @buildPlaceholderTokenizedLineForRow(bufferRow) + + tokenizedLinesForRows: (startRow, endRow) -> + for row in [startRow..endRow] by 1 + @tokenizedLineForRow(row) stackForRow: (bufferRow) -> @tokenizedLines[bufferRow]?.ruleStack @@ -405,7 +424,7 @@ class TokenizedBuffer extends Model iterator = @tokenizedLines[row].getTokenIterator() while iterator.next() - if iterator.getScreenEnd() > column + if iterator.getBufferEnd() > column scopes = iterator.getScopes() break @@ -418,17 +437,17 @@ class TokenizedBuffer extends Model tokenForPosition: (position) -> {row, column} = Point.fromObject(position) - @tokenizedLines[row].tokenAtBufferColumn(column) + @tokenizedLineForRow(row).tokenAtBufferColumn(column) tokenStartPositionForPosition: (position) -> {row, column} = Point.fromObject(position) - column = @tokenizedLines[row].tokenStartColumnForBufferColumn(column) + column = @tokenizedLineForRow(row).tokenStartColumnForBufferColumn(column) new Point(row, column) bufferRangeForScopeAtPosition: (selector, position) -> position = Point.fromObject(position) - {openScopes, tags} = @tokenizedLines[position.row] + {openScopes, tags} = @tokenizedLineForRow(position.row) scopes = openScopes.map (tag) -> atom.grammars.scopeForId(tag) startColumn = 0 diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index b8f7226c8..bd871fc4f 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -102,10 +102,11 @@ class TokenizedLine substringEnd += 1 else if (screenColumn + 1) % @tabLength is 0 - @specialTokens[tokenIndex] = SoftTab suffix = @tags[tokenIndex] - @tabLength - @tags.splice(tokenIndex, 1, @tabLength) - @tags.splice(tokenIndex + 1, 0, suffix) if suffix > 0 + if suffix >= 0 + @specialTokens[tokenIndex] = SoftTab + @tags.splice(tokenIndex, 1, @tabLength) + @tags.splice(tokenIndex + 1, 0, suffix) if suffix > 0 if @invisibles?.space if substringEnd > substringStart diff --git a/src/workspace.coffee b/src/workspace.coffee index 0285cf99d..157d002de 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -22,8 +22,8 @@ Task = require './task' # An instance of this class is available via the `atom.workspace` global. # # Interact with this object to open files, be notified of current and future -# editors, and manipulate panes. To add panels, you'll need to use the -# {WorkspaceView} class for now until we establish APIs at the model layer. +# editors, and manipulate panes. To add panels, use {Workspace::addTopPanel} +# and friends. # # * `editor` {TextEditor} the new editor # diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 9d87c61ca..a8b99f0bc 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -100,15 +100,6 @@ atom-text-editor { min-width: 0; } - .underlayer { - position: absolute; - z-index: -2; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - .highlight { background: none; padding: 0; diff --git a/static/text-editor-shadow.less b/static/text-editor-shadow.less index c67637290..cb63b45b0 100644 --- a/static/text-editor-shadow.less +++ b/static/text-editor-shadow.less @@ -81,15 +81,6 @@ min-width: 0; } -.underlayer { - position: absolute; - z-index: -2; - top: 0; - bottom: 0; - left: 0; - right: 0; -} - .highlight { background: none; padding: 0;