diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e8e3a897..19d4367f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -234,6 +234,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and | `installer` | [search][search-atom-repo-label-installer] | [search][search-atom-org-label-installer] | Related to the Atom installers for different OSes. | | `auto-updater` | [search][search-atom-repo-label-auto-updater] | [search][search-atom-org-label-auto-updater] | Related to the auto-updater for different OSes. | | `deprecation-help` | [search][search-atom-repo-label-deprecation-help] | [search][search-atom-org-label-deprecation-help] | Issues for helping package authors remove usage of deprecated APIs in packages. | +| `electron` | [search][search-atom-repo-label-electron] | [search][search-atom-org-label-electron] | Issues that require changes to [Electron](https://electron.atom.io) to fix or implement. | #### Core Team Project Management @@ -329,6 +330,8 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and [search-atom-org-label-auto-updater]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aauto-updater [search-atom-repo-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Adeprecation-help [search-atom-org-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Adeprecation-help +[search-atom-repo-label-electron]: https://github.com/issues?q=is%3Aissue+repo%3Aatom%2Fatom+is%3Aopen+label%3Aelectron +[search-atom-org-label-electron]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aelectron [search-atom-repo-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aon-deck [search-atom-org-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aon-deck [search-atom-repo-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ain-progress diff --git a/build/package.json b/build/package.json index a909f8cd4..1ed151e1f 100644 --- a/build/package.json +++ b/build/package.json @@ -11,7 +11,7 @@ "donna": "1.0.10", "formidable": "~1.0.14", "fs-plus": "2.x", - "github-releases": "~0.2.0", + "github-releases": "~0.3.0", "glob": "^5.0.14", "grunt": "~0.4.1", "grunt-babel": "^5.0.1", @@ -22,7 +22,7 @@ "grunt-contrib-less": "~0.8.0", "grunt-cson": "0.15.0", "grunt-download-electron": "^2.1.1", - "grunt-electron-installer": "1.0.0", + "grunt-electron-installer": "1.0.3-0", "grunt-lesslint": "0.17.0", "grunt-peg": "~1.1.0", "grunt-shell": "~0.3.1", diff --git a/build/tasks/mkrpm-task.coffee b/build/tasks/mkrpm-task.coffee index 5744343a5..c11e62f69 100644 --- a/build/tasks/mkrpm-task.coffee +++ b/build/tasks/mkrpm-task.coffee @@ -24,6 +24,13 @@ module.exports = (grunt) -> return done("Unsupported arch #{process.arch}") {name, version, description} = grunt.file.readJSON('package.json') + + # RPM versions can't have dashes in them. + # * http://www.rpm.org/max-rpm/ch-rpm-file-format.html + # * https://github.com/mojombo/semver/issues/145 + version = version.replace(/-beta$/, "~beta") + version = version.replace(/-dev$/, "~dev") + buildDir = grunt.config.get('atom.buildDir') rpmDir = path.join(buildDir, 'rpm') diff --git a/build/tasks/publish-build-task.coffee b/build/tasks/publish-build-task.coffee index 1b9eb07ce..1f8925aed 100644 --- a/build/tasks/publish-build-task.coffee +++ b/build/tasks/publish-build-task.coffee @@ -22,7 +22,7 @@ module.exports = (gruntObject) -> grunt.registerTask 'publish-build', 'Publish the built app', -> tasks = [] tasks.push('build-docs', 'prepare-docs') if process.platform is 'darwin' - tasks.push('upload-assets') if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH is 'master' + tasks.push('upload-assets') grunt.task.run(tasks) grunt.registerTask 'prepare-docs', 'Move api.json to atom-api.json', -> @@ -31,6 +31,14 @@ module.exports = (gruntObject) -> cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json') grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', -> + switch process.env.JANKY_BRANCH + when 'stable' + isPrerelease = false + when 'beta' + isPrerelease = true + else + return + doneCallback = @async() startTime = Date.now() done = (args...) -> @@ -46,7 +54,7 @@ module.exports = (gruntObject) -> zipAssets buildDir, assets, (error) -> return done(error) if error? - getAtomDraftRelease (error, release) -> + getAtomDraftRelease isPrerelease, (error, release) -> return done(error) if error? assetNames = (asset.assetName for asset in assets) deleteExistingAssets release, assetNames, (error) -> @@ -120,9 +128,9 @@ zipAssets = (buildDir, assets, callback) -> tasks.push(zip.bind(this, buildDir, sourcePath, assetName)) async.parallel(tasks, callback) -getAtomDraftRelease = (callback) -> +getAtomDraftRelease = (isPrerelease, callback) -> atomRepo = new GitHub({repo: 'atom/atom', token}) - atomRepo.getReleases (error, releases=[]) -> + atomRepo.getReleases {prerelease: isPrerelease}, (error, releases=[]) -> if error? logError('Fetching atom/atom releases failed', error, releases) callback(error) @@ -142,9 +150,9 @@ getAtomDraftRelease = (callback) -> firstDraft.assets = assets callback(null, firstDraft) else - createAtomDraftRelease(callback) + createAtomDraftRelease(isPrerelease, callback) -createAtomDraftRelease = (callback) -> +createAtomDraftRelease = (isPrerelease, callback) -> {version} = require('../../package.json') options = uri: 'https://api.github.com/repos/atom/atom/releases' @@ -152,6 +160,7 @@ createAtomDraftRelease = (callback) -> headers: defaultHeaders json: tag_name: "v#{version}" + prerelease: isPrerelease name: version draft: true body: """ diff --git a/build/tasks/set-version-task.coffee b/build/tasks/set-version-task.coffee index 48b6091c4..2cc148011 100644 --- a/build/tasks/set-version-task.coffee +++ b/build/tasks/set-version-task.coffee @@ -5,7 +5,7 @@ module.exports = (grunt) -> {spawn} = require('./task-helpers')(grunt) getVersion = (callback) -> - onBuildMachine = process.env.JANKY_SHA1 and process.env.JANKY_BRANCH is 'master' + onBuildMachine = process.env.JANKY_SHA1 and process.env.JANKY_BRANCH in ['stable', 'beta'] inRepository = fs.existsSync(path.resolve(__dirname, '..', '..', '.git')) {version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json')) if onBuildMachine or not inRepository diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md index 76ea7a025..d89033c82 100644 --- a/docs/build-instructions/windows.md +++ b/docs/build-instructions/windows.md @@ -14,19 +14,19 @@ If it is installed elsewhere, you can create a symbolic link to the directory containing the python.exe using: `mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27` - * [GitHub for Windows](http://windows.github.com/) + * [GitHub Desktop](http://desktop.github.com/) ### On Windows 8 or 10 * [Visual Studio Express 2013 or 2015 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2) * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x) * [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp)) - * [GitHub for Windows](http://windows.github.com/) + * [GitHub Desktop](http://desktop.github.com/) ## Instructions ```bash -# Use the `Git Shell` program which was installed by GitHub for Windows. -# Also make sure that you are logged into GitHub for Windows. +# Use the `Git Shell` program which was installed by GitHub Desktop. +# Also make sure that you are logged into GitHub Desktop. cd C:\ git clone https://github.com/atom/atom/ cd atom @@ -40,15 +40,14 @@ We will assume the git shell for these instructions. * `--build-dir` - Build the application in this directory. * `--verbose` - Verbose mode. A lot more information output. -## Why do I have to use GitHub for Windows? +## Why do I have to use GitHub Desktop? -You don't. You can use your existing Git! GitHub for Windows's Git Shell is just -easier to set up. +You don't. You can use your existing Git! GitHub Desktop's Git Shell is just easier to set up. If you _prefer_ using your existing Git installation, make sure git's cmd directory is in your PATH env variable (e.g. `C:\Program Files (x86)\Git\cmd`) before you open your powershell or command window. Note that you may have to open your command window as administrator. For powershell that doesn't seem to always be the case, though. -If none of this works, do install Github for Windows and use its Git shell. Makes life easier. +If none of this works, do install Github Desktop and use its Git shell. Makes life easier. ## Troubleshooting @@ -59,7 +58,6 @@ If none of this works, do install Github for Windows and use its Git shell. Make * If you just installed node, you'll need to restart your computer before node is available on your Path. - * `script/build` outputs only the Node and Python versions before returning * Try moving the repository to `C:\atom`. Most likely, the path is too long. @@ -73,7 +71,7 @@ If none of this works, do install Github for Windows and use its Git shell. Make * https://github.com/TooTallNate/node-gyp/issues/297 * https://code.google.com/p/gyp/issues/detail?id=393 -* `script/build` stops at installing runas with 'Failed at the runas@0.5.4 install script.' +* `script/build` stops at installing runas with 'Failed at the runas@x.y.z install script.' See the next item. diff --git a/package.json b/package.json index 4d6a67cd9..c0ff47282 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.0.12", + "version": "1.0.12-dev", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { @@ -92,7 +92,7 @@ "dev-live-reload": "0.46.0", "encoding-selector": "0.21.0", "exception-reporting": "0.36.0", - "find-and-replace": "0.180.0", + "find-and-replace": "0.181.0", "fuzzy-finder": "0.88.0", "git-diff": "0.56.0", "go-to-line": "0.30.0", @@ -124,7 +124,7 @@ "language-c": "0.47.1", "language-clojure": "0.16.0", "language-coffee-script": "0.41.0", - "language-csharp": "0.9.0", + "language-csharp": "0.10.0", "language-css": "0.34.0", "language-gfm": "0.81.0", "language-git": "0.10.0", @@ -132,20 +132,20 @@ "language-html": "0.41.2", "language-hyperlink": "0.14.0", "language-java": "0.16.0", - "language-javascript": "0.93.0", + "language-javascript": "0.94.0", "language-json": "0.16.0", "language-less": "0.28.2", "language-make": "0.17.0", "language-mustache": "0.12.0", "language-objective-c": "0.15.0", - "language-perl": "0.28.0", - "language-php": "0.29.0", + "language-perl": "0.29.0", + "language-php": "0.30.0", "language-property-list": "0.8.0", "language-python": "0.40.0", - "language-ruby": "0.57.0", + "language-ruby": "0.58.0", "language-ruby-on-rails": "0.22.0", "language-sass": "0.41.0", - "language-shellscript": "0.16.0", + "language-shellscript": "0.17.0", "language-source": "0.9.0", "language-sql": "0.17.0", "language-text": "0.7.0", diff --git a/script/railcar b/script/railcar new file mode 100755 index 000000000..fa3fca2cb --- /dev/null +++ b/script/railcar @@ -0,0 +1,89 @@ +#!/usr/bin/env node + +var fs = require('fs') +var exec = require('child_process').exec +var series = require('async').series +var semver = require('semver') + +series([ + section('Preparing to roll the railcars'), + checkCleanWorkingTree, + run('git fetch origin master:master beta:beta stable:stable'), + run('git fetch origin --tags'), + + section('Updating stable branch'), + run('git checkout stable'), + run('git merge --ff-only origin/stable'), + run('git merge --ff-only origin/beta'), + bumpStableVersion, + + section('Updating beta branch'), + run('git checkout beta'), + run('git merge --ff-only origin/beta'), + run('git merge --ff-only origin/master'), + run('git merge --strategy ours origin/stable'), + bumpBetaVersion, + + section('Updating master branch'), + run('git checkout master'), + run('git merge --ff-only origin/master'), + run('git merge --strategy ours origin/beta'), + bumpDevVersion, + + section('Pushing changes upstream'), + run('git push origin master:master beta:beta stable:stable'), + run('git push origin --tags') +], finish) + +function checkCleanWorkingTree (next) { + run('git status --porcelain')(function (error, output) { + if (error) return next(error) + if (output.trim().length > 0) return next(new Error('Cannot run the railcars with a dirty working tree')) + next() + }) +} + +function bumpStableVersion (next) { + run('npm version patch')(next) +} + +function bumpBetaVersion (next) { + var newVersion = semver.inc(getCurrentVersion(), 'prerelease', 'beta') + run('npm version ' + newVersion)(next) +} + +function bumpDevVersion (next) { + var newVersion = semver.inc(getCurrentVersion(), 'preminor', 'dev') + series([ + run('npm --no-git-tag-version version ' + newVersion), + run('git commit -am "' + newVersion + '"') + ], next) +} + +function finish (error) { + if (error) { + console.log('Error: ' + error.message) + process.exit(1) + } + + console.log('OK, now just wait for all CI builds to pass on beta and stable') +} + +function getCurrentVersion () { + return JSON.parse(fs.readFileSync(require.resolve('../package.json'))).version +} + +function run (command) { + return function (next) { + console.log('>', command) + exec(command, next) + } +} + +function section (message) { + return function (next) { + console.log() + console.log(message) + next() + } +} diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 2e914048e..2baa90ac7 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -108,7 +108,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLines() expect(tilesNodes[0].style.zIndex).toBe("2") expect(tilesNodes[1].style.zIndex).toBe("1") @@ -118,7 +118,7 @@ describe "TextEditorComponent", -> verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLines() expect(tilesNodes[0].style.zIndex).toBe("3") expect(tilesNodes[1].style.zIndex).toBe("2") @@ -130,7 +130,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLines() expect(tilesNodes.length).toBe(3) @@ -158,7 +158,7 @@ describe "TextEditorComponent", -> verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLines() expect(component.lineNodeForScreenRow(2)).toBeUndefined() expect(tilesNodes.length).toBe(3) @@ -187,7 +187,7 @@ describe "TextEditorComponent", -> editor.getBuffer().deleteRows(0, 1) nextAnimationFrame() - tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLines() expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels) @@ -202,7 +202,7 @@ describe "TextEditorComponent", -> editor.getBuffer().insert([0, 0], '\n\n') nextAnimationFrame() - tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLines() expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels) @@ -294,7 +294,7 @@ describe "TextEditorComponent", -> editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth() for lineNode in lineNodes - expect(lineNode.style.width).toBe editorFullWidth + 'px' + expect(lineNode.getBoundingClientRect().width).toBe(editorFullWidth) componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px' component.measureDimensions() @@ -302,7 +302,7 @@ describe "TextEditorComponent", -> scrollViewWidth = scrollViewNode.offsetWidth for lineNode in lineNodes - expect(lineNode.style.width).toBe scrollViewWidth + 'px' + expect(lineNode.getBoundingClientRect().width).toBe(scrollViewWidth) it "renders an nbsp on empty lines when no line-ending character is defined", -> atom.config.set("editor.showInvisibles", false) @@ -313,7 +313,7 @@ describe "TextEditorComponent", -> backgroundColor = getComputedStyle(wrapperNode).backgroundColor expect(linesNode.style.backgroundColor).toBe backgroundColor - for tileNode in linesNode.querySelectorAll(".tile") + for tileNode in component.tileNodesForLines() expect(tileNode.style.backgroundColor).toBe(backgroundColor) wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)' @@ -322,7 +322,7 @@ describe "TextEditorComponent", -> advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' - for tileNode in linesNode.querySelectorAll(".tile") + for tileNode in component.tileNodesForLines() expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)") @@ -606,7 +606,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLineNumbers() expect(tilesNodes[0].style.zIndex).toBe("2") expect(tilesNodes[1].style.zIndex).toBe("1") @@ -616,33 +616,13 @@ describe "TextEditorComponent", -> verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLineNumbers() expect(tilesNodes[0].style.zIndex).toBe("3") expect(tilesNodes[1].style.zIndex).toBe("2") expect(tilesNodes[2].style.zIndex).toBe("1") expect(tilesNodes[3].style.zIndex).toBe("0") - it "renders higher line numbers in front of lower ones", -> - wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' - component.measureDimensions() - nextAnimationFrame() - - # Tile 0 - expect(component.lineNumberNodeForScreenRow(0).style.zIndex).toBe("2") - expect(component.lineNumberNodeForScreenRow(1).style.zIndex).toBe("1") - expect(component.lineNumberNodeForScreenRow(2).style.zIndex).toBe("0") - - # Tile 1 - expect(component.lineNumberNodeForScreenRow(3).style.zIndex).toBe("2") - expect(component.lineNumberNodeForScreenRow(4).style.zIndex).toBe("1") - expect(component.lineNumberNodeForScreenRow(5).style.zIndex).toBe("0") - - # Tile 2 - expect(component.lineNumberNodeForScreenRow(6).style.zIndex).toBe("2") - expect(component.lineNumberNodeForScreenRow(7).style.zIndex).toBe("1") - expect(component.lineNumberNodeForScreenRow(8).style.zIndex).toBe("0") - it "gives the line numbers container the same height as the wrapper node", -> linesNode = componentNode.querySelector(".line-numbers") @@ -663,7 +643,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLineNumbers() expect(tilesNodes.length).toBe(3) expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" @@ -689,7 +669,7 @@ describe "TextEditorComponent", -> verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLineNumbers() expect(component.lineNumberNodeForScreenRow(2)).toBeUndefined() expect(tilesNodes.length).toBe(3) @@ -791,7 +771,7 @@ describe "TextEditorComponent", -> lineNumbersNode = gutterNode.querySelector('.line-numbers') {backgroundColor} = getComputedStyle(wrapperNode) expect(lineNumbersNode.style.backgroundColor).toBe backgroundColor - for tileNode in lineNumbersNode.querySelectorAll(".tile") + for tileNode in component.tileNodesForLineNumbers() expect(tileNode.style.backgroundColor).toBe(backgroundColor) # favor gutter color if it's assigned @@ -800,7 +780,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' - for tileNode in lineNumbersNode.querySelectorAll(".tile") + for tileNode in component.tileNodesForLineNumbers() expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)") it "hides or shows the gutter based on the '::isLineNumberGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", -> @@ -1133,7 +1113,7 @@ describe "TextEditorComponent", -> it "renders 2 regions for 2-line selections", -> editor.setSelectedScreenRange([[1, 6], [2, 10]]) nextAnimationFrame() - tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0] + tileNode = component.tileNodesForLines()[0] regions = tileNode.querySelectorAll('.selection .region') expect(regions.length).toBe 2 @@ -1154,7 +1134,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() # Tile 0 - tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0] + tileNode = component.tileNodesForLines()[0] regions = tileNode.querySelectorAll('.selection .region') expect(regions.length).toBe(3) @@ -1177,7 +1157,7 @@ describe "TextEditorComponent", -> expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right # Tile 3 - tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[1] + tileNode = component.tileNodesForLines()[1] regions = tileNode.querySelectorAll('.selection .region') expect(regions.length).toBe(3) @@ -2408,7 +2388,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") + tilesNodes = component.tileNodesForLines() top = 0 for tileNode in tilesNodes diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index a02e3b4e3..76589416e 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2035,15 +2035,10 @@ describe "TextEditorPresenter", -> lineNumberStateForScreenRow = (presenter, screenRow) -> editor = presenter.model tileRow = presenter.tileForRow(screenRow) - bufferRow = editor.bufferRowForScreenRow(screenRow) - wrapCount = screenRow - editor.screenRowForBufferRow(bufferRow) - if wrapCount > 0 - key = bufferRow + '-' + wrapCount - else - key = bufferRow + line = editor.tokenizedLineForScreenRow(screenRow) gutterState = getLineNumberGutterState(presenter) - gutterState.content.tiles[tileRow]?.lineNumbers[key] + gutterState.content.tiles[tileRow]?.lineNumbers[line?.id] tiledContentContract (presenter) -> getLineNumberGutterState(presenter).content diff --git a/spec/token-iterator-spec.coffee b/spec/token-iterator-spec.coffee new file mode 100644 index 000000000..ee8ac25e5 --- /dev/null +++ b/spec/token-iterator-spec.coffee @@ -0,0 +1,35 @@ +TextBuffer = require 'text-buffer' +TokenizedBuffer = require '../src/tokenized-buffer' + +describe "TokenIterator", -> + it "correctly terminates scopes at the beginning of the line (regression)", -> + grammar = atom.grammars.createGrammar('test', { + 'scopeName': 'text.broken' + 'name': 'Broken grammar' + 'patterns': [ + { + 'begin': 'start' + 'end': '(?=end)' + 'name': 'blue.broken' + } + { + 'match': '.' + 'name': 'yellow.broken' + } + ] + }) + + buffer = new TextBuffer(text: """ + start x + end x + x + """) + tokenizedBuffer = new TokenizedBuffer({buffer}) + tokenizedBuffer.setGrammar(grammar) + + tokenIterator = tokenizedBuffer.tokenizedLineForRow(1).getTokenIterator() + tokenIterator.next() + + expect(tokenIterator.getBufferStart()).toBe 0 + expect(tokenIterator.getScopeEnds()).toEqual [] + expect(tokenIterator.getScopeStarts()).toEqual ['text.broken', 'yellow.broken'] diff --git a/src/line-numbers-tile-component.coffee b/src/line-numbers-tile-component.coffee index 2ebccbc65..f00c968fc 100644 --- a/src/line-numbers-tile-component.coffee +++ b/src/line-numbers-tile-component.coffee @@ -9,7 +9,6 @@ class LineNumbersTileComponent constructor: ({@id}) -> @lineNumberNodesById = {} @domNode = document.createElement("div") - @domNode.classList.add("tile") @domNode.style.position = "absolute" @domNode.style.display = "block" @domNode.style.top = 0 # Cover the space occupied by a dummy lineNumber @@ -75,28 +74,32 @@ class LineNumbersTileComponent newLineNumbersHTML += @buildLineNumberHTML(lineNumberState) @oldTileState.lineNumbers[id] = _.clone(lineNumberState) - if newLineNumberIds? - WrapperDiv.innerHTML = newLineNumbersHTML - newLineNumberNodes = _.toArray(WrapperDiv.children) + return unless newLineNumberIds? - node = @domNode - for id, i in newLineNumberIds - lineNumberNode = newLineNumberNodes[i] - @lineNumberNodesById[id] = lineNumberNode - node.appendChild(lineNumberNode) + WrapperDiv.innerHTML = newLineNumbersHTML + newLineNumberNodes = _.toArray(WrapperDiv.children) + for id, i in newLineNumberIds + lineNumberNode = newLineNumberNodes[i] + @lineNumberNodesById[id] = lineNumberNode + if nextNode = @findNodeNextTo(lineNumberNode) + @domNode.insertBefore(lineNumberNode, nextNode) + else + @domNode.appendChild(lineNumberNode) + + findNodeNextTo: (node) -> + for nextNode in @domNode.children + return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode) return + screenRowForNode: (node) -> parseInt(node.dataset.screenRow) + buildLineNumberHTML: (lineNumberState) -> - {screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState - if screenRow? - style = "position: absolute; top: #{top}px; z-index: #{zIndex};" - else - style = "visibility: hidden;" + {screenRow, bufferRow, softWrapped, top, decorationClasses} = lineNumberState className = @buildLineNumberClassName(lineNumberState) innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped) - "
#{innerHTML}
" + "
#{innerHTML}
" buildLineNumberInnerHTML: (bufferRow, softWrapped) -> {maxLineNumberDigits} = @newState @@ -119,18 +122,15 @@ class LineNumbersTileComponent oldLineNumberState.foldable = newLineNumberState.foldable oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses) - unless oldLineNumberState.top is newLineNumberState.top - node.style.top = newLineNumberState.top + 'px' + unless oldLineNumberState.screenRow is newLineNumberState.screenRow and oldLineNumberState.bufferRow is newLineNumberState.bufferRow + node.innerHTML = @buildLineNumberInnerHTML(newLineNumberState.bufferRow, newLineNumberState.softWrapped) node.dataset.screenRow = newLineNumberState.screenRow - oldLineNumberState.top = newLineNumberState.top + node.dataset.bufferRow = newLineNumberState.bufferRow oldLineNumberState.screenRow = newLineNumberState.screenRow - - unless oldLineNumberState.zIndex is newLineNumberState.zIndex - node.style.zIndex = newLineNumberState.zIndex - oldLineNumberState.zIndex = newLineNumberState.zIndex + oldLineNumberState.bufferRow = newLineNumberState.bufferRow buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) -> - className = "line-number line-number-#{bufferRow}" + className = "line-number" className += " " + decorationClasses.join(' ') if decorationClasses? className += " foldable" if foldable and not softWrapped className diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index f823ea8ac..7f6de6397 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -21,7 +21,6 @@ class LinesTileComponent @screenRowsByLineId = {} @lineIdsByScreenRow = {} @domNode = document.createElement("div") - @domNode.classList.add("tile") @domNode.style.position = "absolute" @domNode.style.display = "block" @@ -110,10 +109,19 @@ class LinesTileComponent for id, i in newLineIds lineNode = newLineNodes[i] @lineNodesByLineId[id] = lineNode - @domNode.appendChild(lineNode) + if nextNode = @findNodeNextTo(lineNode) + @domNode.insertBefore(lineNode, nextNode) + else + @domNode.appendChild(lineNode) + findNodeNextTo: (node) -> + for nextNode, index in @domNode.children + continue if index is 0 # skips highlights node + return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode) return + screenRowForNode: (node) -> parseInt(node.dataset.screenRow) + buildLineHTML: (id) -> {width} = @newState {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id] @@ -124,7 +132,7 @@ class LinesTileComponent classes += decorationClass + ' ' classes += 'line' - lineHTML = "
" + lineHTML = "
" if text is "" lineHTML += @buildEmptyLineInnerHTML(id) @@ -284,9 +292,6 @@ class LinesTileComponent lineNode = @lineNodesByLineId[id] - if @newState.width isnt @oldState.width - lineNode.style.width = @newState.width + 'px' - newDecorationClasses = newLineState.decorationClasses oldDecorationClasses = oldLineState.decorationClasses @@ -302,10 +307,6 @@ class LinesTileComponent oldLineState.decorationClasses = newLineState.decorationClasses - if newLineState.top isnt oldLineState.top - lineNode.style.top = newLineState.top + 'px' - oldLineState.top = newLineState.top - if newLineState.screenRow isnt oldLineState.screenRow lineNode.dataset.screenRow = newLineState.screenRow oldLineState.screenRow = newLineState.screenRow diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 51bc2c463..73b03ce88 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -749,6 +749,13 @@ class TextEditorComponent tileComponent?.lineNumberNodeForScreenRow(screenRow) + tileNodesForLines: -> + @linesComponent.getTiles() + + tileNodesForLineNumbers: -> + gutterComponent = @gutterContainerComponent.getLineNumberGutterComponent() + gutterComponent.getTiles() + screenRowForNode: (node) -> while node? if screenRow = node.dataset.screenRow diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2c0900092..cf31186a9 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -20,7 +20,7 @@ class TextEditorPresenter @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileSize ?= 12 + @tileSize ?= 6 @disposables = new CompositeDisposable @emitter = new Emitter @@ -584,22 +584,15 @@ class TextEditorPresenter if startRow > 0 rowBeforeStartRow = startRow - 1 lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow) - wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow) else lastBufferRow = null - wrapCount = 0 if endRow > startRow bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1) - zIndex = bufferRows.length - 1 for bufferRow, i in bufferRows if bufferRow is lastBufferRow - wrapCount++ - id = bufferRow + '-' + wrapCount softWrapped = true else - id = bufferRow - wrapCount = 0 lastBufferRow = bufferRow softWrapped = false @@ -607,10 +600,10 @@ class TextEditorPresenter top = (screenRow - startRow) * @lineHeight decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) + id = @model.tokenizedLineForScreenRow(screenRow).id - tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable, zIndex} + tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} visibleLineNumberIds[id] = true - zIndex-- for id of tileState.lineNumbers delete tileState.lineNumbers[id] unless visibleLineNumberIds[id] diff --git a/src/tiled-component.coffee b/src/tiled-component.coffee index 33719dda5..06433f8be 100644 --- a/src/tiled-component.coffee +++ b/src/tiled-component.coffee @@ -1,3 +1,5 @@ +{values} = require 'underscore-plus' + cloneObject = (object) -> clone = {} clone[key] = value for key, value of object @@ -49,3 +51,6 @@ class TiledComponent getComponentForTile: (tileRow) -> @componentsByTileId[tileRow] + + getTiles: -> + values(@componentsByTileId).map (component) -> component.getDomNode() diff --git a/src/token-iterator.coffee b/src/token-iterator.coffee index 202b044ba..de874d499 100644 --- a/src/token-iterator.coffee +++ b/src/token-iterator.coffee @@ -31,11 +31,14 @@ class TokenIterator while @index < tags.length tag = tags[@index] if tag < 0 + scope = atom.grammars.scopeForId(tag) if tag % 2 is 0 - @scopeEnds.push(atom.grammars.scopeForId(tag + 1)) + if @scopeStarts[@scopeStarts.length - 1] is scope + @scopeStarts.pop() + else + @scopeEnds.push(scope) @scopes.pop() else - scope = atom.grammars.scopeForId(tag) @scopeStarts.push(scope) @scopes.push(scope) @index++ diff --git a/src/workspace.coffee b/src/workspace.coffee index 86e860fe2..62fda5aed 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -506,6 +506,15 @@ class Workspace extends Model # # Returns a {Disposable} on which `.dispose()` can be called to remove the # opener. + # + # Note that the opener will be called if and only if the URI is not already open + # in the current pane. The searchAllPanes flag expands the search from the + # current pane to all panes. If you wish to open a view of a different type for + # a file that is already open, consider changing the protocol of the URI. For + # example, perhaps you wish to preview a rendered version of the file `/foo/bar/baz.quux` + # that is already open in a text editor view. You could signal this by calling + # {Workspace::open} on the URI `quux-preview://foo/bar/baz.quux`. Then your opener + # can check the protocol for quux-preview and only handle those URIs that match. addOpener: (opener) -> if includeDeprecatedAPIs packageName = @getCallingPackageName() diff --git a/static/atom.less b/static/atom.less index 1acb0f54b..7a2384514 100644 --- a/static/atom.less +++ b/static/atom.less @@ -28,3 +28,4 @@ @import "text"; @import "utilities"; @import "octicons"; +@import "cursors"; diff --git a/static/cursors.less b/static/cursors.less new file mode 100644 index 000000000..b2807217e --- /dev/null +++ b/static/cursors.less @@ -0,0 +1,26 @@ +@import "./variables/syntax-variables"; +@import "syntax-variables"; + +@import "./variables/ui-variables"; +@import "ui-variables"; + +@ibeam-1x: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII='); +@ibeam-2x: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC'); + +.cursor-white() { + cursor: -webkit-image-set(@ibeam-1x 1x, @ibeam-2x 2x) 5 8, text; +} + +// Editors +& when ( lightness(@syntax-background-color) < 50% ) { + .platform-darwin atom-text-editor:not([mini])::shadow .editor-contents--private { + .cursor-white(); + } +} + +// Mini Editors +& when ( lightness(@input-background-color) < 50% ) { + .platform-darwin atom-text-editor[mini]::shadow .editor-contents--private { + .cursor-white(); + } +} diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 819fc565f..7fafade1e 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -46,6 +46,7 @@ atom-text-editor { } .line-number { + position: relative; white-space: nowrap; padding-left: .5em; opacity: 0.6; diff --git a/static/text-editor-shadow.less b/static/text-editor-shadow.less index 80dc4967b..e481d11b8 100644 --- a/static/text-editor-shadow.less +++ b/static/text-editor-shadow.less @@ -29,6 +29,7 @@ } .line-number { + position: relative; white-space: nowrap; padding-left: .5em; opacity: 0.6;