diff --git a/.gitignore b/.gitignore index 7103ce579..1257ab371 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,3 @@ debug.log docs/output docs/includes spec/fixtures/evil-files/ -resources/linux/Atom.desktop -resources/linux/debian/control diff --git a/apm/package.json b/apm/package.json index c55cf3869..46f42f23f 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.0.0" + "atom-package-manager": "1.1.0" } } diff --git a/build/package.json b/build/package.json index 91a7e30f9..8b2d55027 100644 --- a/build/package.json +++ b/build/package.json @@ -14,8 +14,8 @@ "grunt": "~0.4.1", "grunt-cli": "~0.1.9", "grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git", - "grunt-contrib-csslint": "~0.1.2", "grunt-contrib-coffee": "~0.9.0", + "grunt-contrib-csslint": "~0.1.2", "grunt-contrib-less": "~0.8.0", "grunt-cson": "0.8.0", "grunt-download-atom-shell": "~0.8.0", @@ -27,15 +27,15 @@ "json-front-matter": "~0.1.3", "legal-eagle": "~0.4.0", "minidump": "~0.7", - "read-package-json": "1.1.8", "normalize-package-data": "0.2.12", + "npm": "~1.4.5", "rcedit": "~0.1.2", + "read-package-json": "1.1.8", "request": "~2.27.0", "rimraf": "~2.2.2", "runas": "0.5.x", "underscore-plus": "1.x", "unzip": "~0.1.9", - "vm-compatibility-layer": "~0.1.0", - "npm": "~1.4.5" + "vm-compatibility-layer": "~0.1.0" } } diff --git a/build/tasks/install-task.coffee b/build/tasks/install-task.coffee index d3a3aa010..13d349a50 100644 --- a/build/tasks/install-task.coffee +++ b/build/tasks/install-task.coffee @@ -4,17 +4,13 @@ _ = require 'underscore-plus' fs = require 'fs-plus' runas = null -fillTemplate = (filePath, data) -> - template = _.template(String(fs.readFileSync(filePath + '.in'))) - filled = template(data) - fs.writeFileSync(filePath, filled) - module.exports = (grunt) -> {cp, mkdir, rm} = require('./task-helpers')(grunt) grunt.registerTask 'install', 'Install the built application', -> installDir = grunt.config.get('atom.installDir') shellAppDir = grunt.config.get('atom.shellAppDir') + if process.platform is 'win32' runas ?= require 'runas' copyFolder = path.resolve 'script', 'copy-folder.cmd' @@ -32,7 +28,6 @@ module.exports = (grunt) -> shareDir = path.join(installDir, 'share', 'atom') iconName = path.join(shareDir,'resources','app','resources','atom.png') - desktopFile = path.join('resources', 'linux', 'Atom.desktop') mkdir binDir cp 'atom.sh', path.join(binDir, 'atom') @@ -42,13 +37,17 @@ module.exports = (grunt) -> # Create Atom.desktop if installation not in temporary folder tmpDir = if process.env.TMPDIR? then process.env.TMPDIR else '/tmp' - desktopInstallFile = path.join(installDir,'share','applications','Atom.desktop') if installDir.indexOf(tmpDir) isnt 0 - mkdir path.dirname(desktopInstallFile) + desktopFile = path.join('resources', 'linux', 'Atom.desktop.in') + desktopInstallFile = path.join(installDir, 'share', 'applications', 'Atom.desktop') + {description} = grunt.file.readJSON('package.json') - installDir = path.join(installDir,'.') # To prevent "Exec=/usr/local//share/atom/atom" - fillTemplate(desktopFile, {description, installDir, iconName}) - cp desktopFile, desktopInstallFile + iconName = path.join(shareDir, 'resources', 'app', 'resources', 'atom.png') + installDir = path.join(installDir, '.') # To prevent "Exec=/usr/local//share/atom/atom" + template = _.template(String(fs.readFileSync(desktopFile))) + filled = template({description, installDir, iconName}) + + grunt.file.write(desktopInstallFile, filled) # Create relative symbol link for apm. process.chdir(binDir) diff --git a/build/tasks/mkdeb-task.coffee b/build/tasks/mkdeb-task.coffee index 090168446..ccf20b790 100644 --- a/build/tasks/mkdeb-task.coffee +++ b/build/tasks/mkdeb-task.coffee @@ -2,14 +2,17 @@ fs = require 'fs' path = require 'path' _ = require 'underscore-plus' -fillTemplate = (filePath, data) -> - template = _.template(String(fs.readFileSync(filePath + '.in'))) - filled = template(data) - fs.writeFileSync(filePath, filled) - module.exports = (grunt) -> {spawn} = require('./task-helpers')(grunt) + fillTemplate = (filePath, data) -> + template = _.template(String(fs.readFileSync("#{filePath}.in"))) + filled = template(data) + + outputPath = path.join(grunt.config.get('atom.buildDir'), path.basename(filePath)) + grunt.file.write(outputPath, filled) + outputPath + grunt.registerTask 'mkdeb', 'Create debian package', -> done = @async() @@ -27,13 +30,11 @@ module.exports = (grunt) -> iconName = 'atom' data = {name, version, description, section, arch, maintainer, installDir, iconName} - control = path.join('resources', 'linux', 'debian', 'control') - fillTemplate(control, data) - desktop = path.join('resources', 'linux', 'Atom.desktop') - fillTemplate(desktop, data) + controlFilePath = fillTemplate(path.join('resources', 'linux', 'debian', 'control'), data) + desktopFilePath = fillTemplate(path.join('resources', 'linux', 'Atom.desktop'), data) icon = path.join('resources', 'atom.png') buildDir = grunt.config.get('atom.buildDir') cmd = path.join('script', 'mkdeb') - args = [version, arch, control, desktop, icon, buildDir] + args = [version, arch, controlFilePath, desktopFilePath, icon, buildDir] spawn({cmd, args}, done) diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index 64db5848d..46a5f8a4d 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -6,34 +6,39 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. * OS with 64-bit or 32-bit architecture * C++ toolchain - * on Ubuntu/Debian: `sudo apt-get install build-essential` - * on Fedora: `sudo yum --assumeyes install make gcc gcc-c++ glibc-devel` + * git * [node.js](http://nodejs.org/download/) v0.10.x - * [Ubuntu/Debian/Mint instructions](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os) - * [Fedora instructions](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora) - * [npm](http://www.npmjs.org/) v1.4.x - * `npm` comes with node.js so no explicit installation is needed here. - * You can check `npm` 1.4 or above is installed by running `npm -v`. + * [npm](http://www.npmjs.org/) v1.4.x (bundled with node.js) + * `npm -v` to check the version. + * `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2. + * You might need to run this command as `sudo`, depending on how you have set up [npm](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os). * libgnome-keyring-dev - * on Ubuntu/Debian: `sudo apt-get install libgnome-keyring-dev` - * on Fedora: `sudo yum --assumeyes install libgnome-keyring-devel` - * on other distributions refer to the manual on how to install packages - * `npm config set python /usr/bin/python2 -g` to ensure that gyp uses Python 2 - * This command may require `sudo` depending on how you have - [configured npm](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os). - * Git - * on Ubuntu/Debian: `sudo apt-get install git` - * on Fedora: `sudo yum install git-core` + +### Ubuntu / Debian +* `sudo apt-get install build-essential git libgnome-keyring-dev` +* Instructions for [node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os). + +### Fedora +* `sudo yum --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel` +* Instructions for [node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora). + +### Arch +* `sudo pacman -S base-devel git nodejs libgnome-keyring` +* `export PYTHON=/usr/bin/python2` before building Atom. ## Instructions +If you have problems with permissions don't forget to prefix with `sudo` + ```sh git clone https://github.com/atom/atom cd atom script/build # Creates application at $TMPDIR/atom-build/Atom sudo script/grunt install # Installs command to /usr/local/bin/atom - script/grunt mkdeb # Generates a .deb package at $TMPDIR/atom-build + script/grunt mkdeb # Generates a .deb package at $TMPDIR/atom-build, e.g. /tmp/atom-build ``` + +To run `atom` and `apm` from a terminal open atom's command palette `ctrl+shift+p` and run `Window: Install Shell Commands` ## Troubleshooting diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md index 9b9dcb799..c74be5049 100644 --- a/docs/build-instructions/windows.md +++ b/docs/build-instructions/windows.md @@ -35,10 +35,14 @@ ## Why do I have to use GitHub for Windows? -You don't, You can use your existing Git! GitHub for Windows's Git Shell is just -easier to set up. You need to have Posix tools in your `%PATH%` (i.e. `grep`, -`sed`, et al.), which isn't the default configuration when you install Git. To -fix this, you probably need to fiddle with your system PATH. +You don't. You can use your existing Git! GitHub for Windows'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. + ## Troubleshooting @@ -62,5 +66,11 @@ fix this, you probably need to fiddle with your system PATH. * https://github.com/TooTallNate/node-gyp/issues/297 * https://code.google.com/p/gyp/issues/detail?id=393 +* Other `node-gyp` errors on first build attempt, even though the right node and python versions are installed. + * Do try the build command one more time, as experience shows it often works on second try in many of these cases. + + ### Windows build error reports in atom/atom -* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues) to get a list of reports about build errors on Windows. +* If all fails, use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues) to get a list of reports about build errors on Windows, and see if yours has already been reported. + +* If it hasn't, please open a new issue with your Windows version 32/64bit and a print/screenshot of your build output, incl. the node and python versions. diff --git a/exports/atom.coffee b/exports/atom.coffee index 773b24f3a..7b6318c7a 100644 --- a/exports/atom.coffee +++ b/exports/atom.coffee @@ -15,7 +15,10 @@ unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE module.exports.$ = $ module.exports.$$ = $$ module.exports.$$$ = $$$ - module.exports.EditorView = require '../src/editor-view' + if atom.config.get('core.useReactMiniEditors') + module.exports.EditorView = require '../src/react-editor-view' + else + module.exports.EditorView = require '../src/editor-view' module.exports.ScrollView = require '../src/scroll-view' module.exports.SelectListView = require '../src/select-list-view' module.exports.Task = require '../src/task' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index ec3290d91..01408fb38 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -29,6 +29,7 @@ 'ctrl-w': 'core:close' 'ctrl-f4': 'core:close' 'ctrl-z': 'core:undo' + 'ctrl-shift-z': 'core:redo' 'ctrl-y': 'core:redo' 'ctrl-x': 'core:cut' 'ctrl-c': 'core:copy' @@ -84,6 +85,8 @@ 'ctrl-delete': 'editor:delete-to-end-of-word' 'ctrl-home': 'core:move-to-top' 'ctrl-end': 'core:move-to-bottom' + 'ctrl-shift-home': 'core:select-to-top' + 'ctrl-shift-end': 'core:select-to-bottom' # Sublime Parity 'ctrl-a': 'core:select-all' diff --git a/package.json b/package.json index f4d9ef9ea..eaeb602bf 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ "delegato": "^1", "emissary": "^1.2.1", "first-mate": "^2.0.0", - "fs-plus": "^2.2.3", + "fs-plus": "^2.2.4", "fstream": "0.1.24", "fuzzaldrin": "^1.1", - "git-utils": "^2.0", + "git-utils": "^2.1", "grim": "0.11.0", "guid": "0.0.10", "jasmine-tagged": "^1.1.2", @@ -41,7 +41,7 @@ "nslog": "^1.0.0", "oniguruma": "^3.0.0", "optimist": "0.4.0", - "pathwatcher": "^2.0.2", + "pathwatcher": "^2.0.3", "property-accessors": "^1", "q": "^1.0.1", "random-words": "0.0.1", @@ -67,7 +67,7 @@ "atom-light-syntax": "0.20.0", "atom-light-ui": "0.28.0", "base16-tomorrow-dark-theme": "0.19.0", - "solarized-dark-syntax": "0.20.0", + "solarized-dark-syntax": "0.21.0", "solarized-light-syntax": "0.11.0", "archive-view": "1.0.0", "autocomplete": "0.28.0", @@ -81,7 +81,7 @@ "dev-live-reload": "1.0.0", "exception-reporting": "0.18.0", "feedback": "0.33.0", - "find-and-replace": "0.126.0", + "find-and-replace": "0.127.0", "fuzzy-finder": "0.57.0", "git-diff": "0.37.0", "go-to-line": "0.23.0", @@ -102,7 +102,7 @@ "symbols-view": "1.0.0", "tabs": "0.45.0", "timecop": "0.21.0", - "tree-view": "1.0.0", + "tree-view": "1.1.0", "update-package-dependencies": "0.6.0", "welcome": "0.17.0", "whitespace": "0.25.0", @@ -111,7 +111,7 @@ "language-c": "0.22.0", "language-coffee-script": "0.27.0", "language-css": "0.17.0", - "language-gfm": "0.42.0", + "language-gfm": "0.43.0", "language-git": "0.9.0", "language-go": "0.13.0", "language-html": "0.22.0", @@ -119,7 +119,7 @@ "language-java": "0.11.0", "language-javascript": "0.36.0", "language-json": "0.8.0", - "language-less": "0.12.0", + "language-less": "0.13.0", "language-make": "0.10.0", "language-objective-c": "0.11.0", "language-perl": "0.9.0", @@ -128,7 +128,7 @@ "language-python": "0.18.0", "language-ruby": "0.33.0", "language-ruby-on-rails": "0.15.0", - "language-sass": "0.13.0", + "language-sass": "0.14.0", "language-shellscript": "0.8.0", "language-source": "0.7.0", "language-sql": "0.9.0", @@ -136,7 +136,7 @@ "language-todo": "0.10.0", "language-toml": "0.12.0", "language-xml": "0.15.0", - "language-yaml": "0.11.0" + "language-yaml": "0.13.0" }, "private": true, "scripts": { diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index da551e2b4..f75220b85 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -6,7 +6,7 @@ EditorComponent = require '../src/editor-component' nbsp = String.fromCharCode(160) describe "EditorComponent", -> - [contentNode, editor, wrapperView, component, node, verticalScrollbarNode, horizontalScrollbarNode] = [] + [contentNode, editor, wrapperView, wrapperNode, component, componentNode, verticalScrollbarNode, horizontalScrollbarNode] = [] [lineHeightInPixels, charWidth, delayAnimationFrames, nextAnimationFrame, runSetImmediateCallbacks, lineOverdrawMargin] = [] beforeEach -> @@ -48,6 +48,7 @@ describe "EditorComponent", -> wrapperView = new ReactEditorView(editor, {lineOverdrawMargin}) wrapperView.attachToDom() + wrapperNode = wrapperView.element {component} = wrapperView component.performSyncUpdates = false @@ -56,13 +57,11 @@ describe "EditorComponent", -> lineHeightInPixels = editor.getLineHeightInPixels() charWidth = editor.getDefaultCharWidth() - node = component.getDOMNode() - verticalScrollbarNode = node.querySelector('.vertical-scrollbar') - horizontalScrollbarNode = node.querySelector('.horizontal-scrollbar') + componentNode = component.getDOMNode() + verticalScrollbarNode = componentNode.querySelector('.vertical-scrollbar') + horizontalScrollbarNode = componentNode.querySelector('.horizontal-scrollbar') - node.style.height = editor.getLineCount() * lineHeightInPixels + 'px' - node.style.width = '1000px' - component.measureScrollView() + component.measureHeightAndWidth() runSetImmediateCallbacks() afterEach -> @@ -70,13 +69,13 @@ describe "EditorComponent", -> describe "line rendering", -> it "renders the currently-visible lines plus the overdraw margin", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() - linesNode = node.querySelector('.lines') + linesNode = componentNode.querySelector('.lines') expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" - expect(node.querySelectorAll('.line').length).toBe 6 + 2 # no margin above + expect(componentNode.querySelectorAll('.line').length).toBe 6 + 2 # no margin above expect(component.lineNodeForScreenRow(0).textContent).toBe editor.lineForScreenRow(0).text expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 expect(component.lineNodeForScreenRow(5).textContent).toBe editor.lineForScreenRow(5).text @@ -86,7 +85,7 @@ describe "EditorComponent", -> verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, #{-4.5 * lineHeightInPixels}px, 0px)" - expect(node.querySelectorAll('.line').length).toBe 6 + 4 # margin above and below + expect(componentNode.querySelectorAll('.line').length).toBe 6 + 4 # margin above and below expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels expect(component.lineNodeForScreenRow(2).textContent).toBe editor.lineForScreenRow(2).text expect(component.lineNodeForScreenRow(9).offsetTop).toBe 9 * lineHeightInPixels @@ -96,7 +95,7 @@ describe "EditorComponent", -> editor.getBuffer().deleteRows(0, 1) runSetImmediateCallbacks() - lineNodes = node.querySelectorAll('.line') + lineNodes = componentNode.querySelectorAll('.line') expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels @@ -104,7 +103,7 @@ describe "EditorComponent", -> editor.getBuffer().insert([0, 0], '\n\n') runSetImmediateCallbacks() - lineNodes = node.querySelectorAll('.line') + lineNodes = componentNode.querySelectorAll('.line') expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 * lineHeightInPixels expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels @@ -112,8 +111,8 @@ describe "EditorComponent", -> expect(component.lineNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels it "updates the lines when lines are inserted or removed above the rendered row range", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() verticalScrollbarNode.scrollTop = 5 * lineHeightInPixels verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) @@ -161,20 +160,20 @@ describe "EditorComponent", -> it "renders the .lines div at the full height of the editor if there aren't enough lines to scroll vertically", -> editor.setText('') - node.style.height = '300px' - component.measureScrollView() + wrapperNode.style.height = '300px' + component.measureHeightAndWidth() runSetImmediateCallbacks() - linesNode = node.querySelector('.lines') + linesNode = componentNode.querySelector('.lines') expect(linesNode.offsetHeight).toBe 300 it "assigns the width of each line so it extends across the full width of the editor", -> - gutterWidth = node.querySelector('.gutter').offsetWidth - scrollViewNode = node.querySelector('.scroll-view') - lineNodes = node.querySelectorAll('.line') + gutterWidth = componentNode.querySelector('.gutter').offsetWidth + scrollViewNode = componentNode.querySelector('.scroll-view') + lineNodes = componentNode.querySelectorAll('.line') - node.style.width = gutterWidth + (30 * charWidth) + 'px' - component.measureScrollView() + componentNode.style.width = gutterWidth + (30 * charWidth) + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(editor.getScrollWidth()).toBeGreaterThan scrollViewNode.offsetWidth @@ -185,8 +184,8 @@ describe "EditorComponent", -> for lineNode in lineNodes expect(lineNode.style.width).toBe editor.getScrollWidth() + 'px' - node.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px' - component.measureScrollView() + componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() scrollViewWidth = scrollViewNode.offsetWidth @@ -259,8 +258,8 @@ describe "EditorComponent", -> editor.setText "a line that wraps " editor.setSoftWrap(true) runSetImmediateCallbacks() - node.style.width = 16 * charWidth + 'px' - component.measureScrollView() + componentNode.style.width = 16 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() it "doesn't show end of line invisibles at the end of wrapped lines", -> @@ -396,18 +395,18 @@ describe "EditorComponent", -> {gutter} = component.refs it "renders the currently-visible line numbers", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() - expect(node.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number + expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1" expect(component.lineNumberNodeForScreenRow(5).textContent).toBe "#{nbsp}6" verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) - expect(node.querySelectorAll('.line-number').length).toBe 6 + 4 + 1 # line overdraw margin above/below + dummy line number + expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 4 + 1 # line overdraw margin above/below + dummy line number expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}3" expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels @@ -418,7 +417,7 @@ describe "EditorComponent", -> editor.getBuffer().insert([0, 0], '\n\n') runSetImmediateCallbacks() - lineNumberNodes = node.querySelectorAll('.line-number') + lineNumberNodes = componentNode.querySelectorAll('.line-number') expect(component.lineNumberNodeForScreenRow(0).offsetTop).toBe 0 expect(component.lineNumberNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels @@ -438,12 +437,12 @@ describe "EditorComponent", -> it "renders • characters for soft-wrapped lines", -> editor.setSoftWrap(true) - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 30 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 30 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() - expect(node.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line node + expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line componentNode expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1" expect(component.lineNumberNodeForScreenRow(1).textContent).toBe "#{nbsp}•" expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}2" @@ -458,7 +457,7 @@ describe "EditorComponent", -> expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe "#{nbsp}#{screenRow + 1}" expect(component.lineNumberNodeForScreenRow(9).textContent).toBe "10" - gutterNode = node.querySelector('.gutter') + gutterNode = componentNode.querySelector('.gutter') initialGutterWidth = gutterNode.offsetWidth # Removes padding when the max number of digits goes down @@ -477,10 +476,10 @@ describe "EditorComponent", -> expect(gutterNode.offsetWidth).toBe initialGutterWidth it "renders the .line-numbers div at the full height of the editor even if it's taller than its content", -> - node.style.height = node.offsetHeight + 100 + 'px' - component.measureScrollView() + wrapperNode.style.height = componentNode.offsetHeight + 100 + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() - expect(node.querySelector('.line-numbers').offsetHeight).toBe node.offsetHeight + expect(componentNode.querySelector('.line-numbers').offsetHeight).toBe componentNode.offsetHeight describe "when the editor.showLineNumbers config is false", -> it "doesn't render any line numbers", -> @@ -523,7 +522,7 @@ describe "EditorComponent", -> runSetImmediateCallbacks() expect(lineNumberHasClass(11, 'foldable')).toBe false - it "adds, updates and removes the folded class on the correct line number nodes", -> + it "adds, updates and removes the folded class on the correct line number componentNodes", -> editor.foldBufferRow(4) runSetImmediateCallbacks() expect(lineNumberHasClass(4, 'folded')).toBe true @@ -544,7 +543,7 @@ describe "EditorComponent", -> buildMouseEvent('click', {target}) beforeEach -> - gutterNode = node.querySelector('.gutter') + gutterNode = componentNode.querySelector('.gutter') it "folds and unfolds the block represented by the fold indicator when clicked", -> expect(lineNumberHasClass(1, 'folded')).toBe false @@ -561,7 +560,7 @@ describe "EditorComponent", -> runSetImmediateCallbacks() expect(lineNumberHasClass(1, 'folded')).toBe false - it "does not fold when the line number node is clicked", -> + it "does not fold when the line number componentNode is clicked", -> lineNumber = component.lineNumberNodeForScreenRow(1) lineNumber.dispatchEvent(buildClickEvent(lineNumber)) runSetImmediateCallbacks() @@ -572,12 +571,12 @@ describe "EditorComponent", -> cursor1 = editor.getCursor() cursor1.setScreenPosition([0, 5]) - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 20 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 20 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() - cursorNodes = node.querySelectorAll('.cursor') + cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 expect(cursorNodes[0].offsetHeight).toBe lineHeightInPixels expect(cursorNodes[0].offsetWidth).toBe charWidth @@ -587,7 +586,7 @@ describe "EditorComponent", -> cursor3 = editor.addCursorAtScreenPosition([4, 10]) runSetImmediateCallbacks() - cursorNodes = node.querySelectorAll('.cursor') + cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 2 expect(cursorNodes[0].offsetTop).toBe 0 expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{5 * charWidth}px, #{0 * lineHeightInPixels}px, 0px)" @@ -598,14 +597,14 @@ describe "EditorComponent", -> horizontalScrollbarNode.scrollLeft = 3.5 * charWidth horizontalScrollbarNode.dispatchEvent(new UIEvent('scroll')) - cursorNodes = node.querySelectorAll('.cursor') + cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 2 expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{(11 - 3.5) * charWidth}px, #{(8 - 4.5) * lineHeightInPixels}px, 0px)" expect(cursorNodes[1].style['-webkit-transform']).toBe "translate3d(#{(10 - 3.5) * charWidth}px, #{(4 - 4.5) * lineHeightInPixels}px, 0px)" cursor3.destroy() runSetImmediateCallbacks() - cursorNodes = node.querySelectorAll('.cursor') + cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{(11 - 3.5) * charWidth}px, #{(6 - 2.5) * lineHeightInPixels}px, 0px)" @@ -614,7 +613,7 @@ describe "EditorComponent", -> editor.setCursorScreenPosition([0, 16]) runSetImmediateCallbacks() - cursor = node.querySelector('.cursor') + cursor = componentNode.querySelector('.cursor') cursorRect = cursor.getBoundingClientRect() cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.storage.type.function.js').firstChild @@ -639,7 +638,7 @@ describe "EditorComponent", -> runSetImmediateCallbacks() # re-measure characters once for a synchronous set of stylesheet changes runSetImmediateCallbacks() # update based on new measurements - cursor = node.querySelector('.cursor') + cursor = componentNode.querySelector('.cursor') cursorRect = cursor.getBoundingClientRect() cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.storage.type.function.js').firstChild @@ -656,18 +655,18 @@ describe "EditorComponent", -> it "sets the cursor to the default character width at the end of a line", -> editor.setCursorScreenPosition([0, Infinity]) runSetImmediateCallbacks() - cursorNode = node.querySelector('.cursor') + cursorNode = componentNode.querySelector('.cursor') expect(cursorNode.offsetWidth).toBe charWidth it "gives the cursor a non-zero width even if it's inside atomic tokens", -> editor.setCursorScreenPosition([1, 0]) runSetImmediateCallbacks() - cursorNode = node.querySelector('.cursor') + cursorNode = componentNode.querySelector('.cursor') expect(cursorNode.offsetWidth).toBe charWidth it "blinks cursors when they aren't moving", -> spyOn(_._, 'now').andCallFake -> window.now # Ensure _.debounce is based on our fake spec timeline - cursorsNode = node.querySelector('.cursors') + cursorsNode = componentNode.querySelector('.cursors') expect(cursorsNode.classList.contains('blink-off')).toBe false advanceClock(component.props.cursorBlinkPeriod / 2) @@ -689,7 +688,7 @@ describe "EditorComponent", -> editor.addCursorAtScreenPosition([6, 8]) runSetImmediateCallbacks() - cursorNodes = node.querySelectorAll('.cursor') + cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{8 * charWidth}px, #{6 * lineHeightInPixels}px, 0px)" @@ -697,21 +696,21 @@ describe "EditorComponent", -> editor.setCursorBufferPosition([1, 10]) component.setLineHeight(2) runSetImmediateCallbacks() - cursorNode = node.querySelector('.cursor') + cursorNode = componentNode.querySelector('.cursor') expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px, 0px)" it "updates cursor positions when the font size changes", -> editor.setCursorBufferPosition([1, 10]) component.setFontSize(10) runSetImmediateCallbacks() - cursorNode = node.querySelector('.cursor') + cursorNode = componentNode.querySelector('.cursor') expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px, 0px)" it "updates cursor positions when the font family changes", -> editor.setCursorBufferPosition([1, 10]) component.setFontFamily('sans-serif') runSetImmediateCallbacks() - cursorNode = node.querySelector('.cursor') + cursorNode = componentNode.querySelector('.cursor') {left} = editor.pixelPositionForScreenPosition([1, 10]) expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{left}px, #{editor.getLineHeightInPixels()}px, 0px)" @@ -720,14 +719,14 @@ describe "EditorComponent", -> [scrollViewNode, scrollViewClientLeft] = [] beforeEach -> - scrollViewNode = node.querySelector('.scroll-view') - scrollViewClientLeft = node.querySelector('.scroll-view').getBoundingClientRect().left + scrollViewNode = componentNode.querySelector('.scroll-view') + scrollViewClientLeft = componentNode.querySelector('.scroll-view').getBoundingClientRect().left it "renders 1 region for 1-line selections", -> # 1-line selection editor.setSelectedScreenRange([[1, 6], [1, 10]]) runSetImmediateCallbacks() - regions = node.querySelectorAll('.selection .region') + regions = componentNode.querySelectorAll('.selection .region') expect(regions.length).toBe 1 regionRect = regions[0].getBoundingClientRect() @@ -739,7 +738,7 @@ describe "EditorComponent", -> it "renders 2 regions for 2-line selections", -> editor.setSelectedScreenRange([[1, 6], [2, 10]]) runSetImmediateCallbacks() - regions = node.querySelectorAll('.selection .region') + regions = componentNode.querySelectorAll('.selection .region') expect(regions.length).toBe 2 region1Rect = regions[0].getBoundingClientRect() @@ -757,7 +756,7 @@ describe "EditorComponent", -> it "renders 3 regions for selections with more than 2 lines", -> editor.setSelectedScreenRange([[1, 6], [5, 10]]) runSetImmediateCallbacks() - regions = node.querySelectorAll('.selection .region') + regions = componentNode.querySelectorAll('.selection .region') expect(regions.length).toBe 3 region1Rect = regions[0].getBoundingClientRect() @@ -784,20 +783,20 @@ describe "EditorComponent", -> expect(editor.getSelection(0).isEmpty()).toBe true expect(editor.getSelection(1).isEmpty()).toBe true - expect(node.querySelectorAll('.selection').length).toBe 0 + expect(componentNode.querySelectorAll('.selection').length).toBe 0 it "updates selections when the line height changes", -> editor.setSelectedBufferRange([[1, 6], [1, 10]]) component.setLineHeight(2) runSetImmediateCallbacks() - selectionNode = node.querySelector('.region') + selectionNode = componentNode.querySelector('.region') expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels() it "updates selections when the font size changes", -> editor.setSelectedBufferRange([[1, 6], [1, 10]]) component.setFontSize(10) runSetImmediateCallbacks() - selectionNode = node.querySelector('.region') + selectionNode = componentNode.querySelector('.region') expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels() expect(selectionNode.offsetLeft).toBe 6 * editor.getDefaultCharWidth() @@ -805,14 +804,14 @@ describe "EditorComponent", -> editor.setSelectedBufferRange([[1, 6], [1, 10]]) component.setFontFamily('sans-serif') runSetImmediateCallbacks() - selectionNode = node.querySelector('.region') + selectionNode = componentNode.querySelector('.region') expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels() expect(selectionNode.offsetLeft).toBe editor.pixelPositionForScreenPosition([1, 6]).left it "will flash the selection when flash:true is passed to editor::setSelectedBufferRange", -> editor.setSelectedBufferRange([[1, 6], [1, 10]], flash: true) runSetImmediateCallbacks() - selectionNode = node.querySelector('.selection') + selectionNode = componentNode.querySelector('.selection') expect(selectionNode.classList.contains('flash')).toBe true advanceClock editor.selectionFlashDuration @@ -836,8 +835,8 @@ describe "EditorComponent", -> expect(lineAndLineNumberHaveClass(3, 'a')).toBe true # Shrink editor vertically - node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() # Add decorations that are out of range @@ -859,8 +858,8 @@ describe "EditorComponent", -> it "only applies decorations to screen rows that are spanned by their marker when lines are soft-wrapped", -> editor.setText("a line that wraps, ok") editor.setSoftWrap(true) - node.style.width = 16 * charWidth + 'px' - component.measureScrollView() + componentNode.style.width = 16 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() marker.destroy() @@ -968,15 +967,15 @@ describe "EditorComponent", -> describe "highlight decoration rendering", -> [marker, decoration, decorationParams, scrollViewClientLeft] = [] beforeEach -> - scrollViewClientLeft = node.querySelector('.scroll-view').getBoundingClientRect().left + scrollViewClientLeft = componentNode.querySelector('.scroll-view').getBoundingClientRect().left marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], invalidate: 'inside') decorationParams = {type: 'highlight', class: 'test-highlight'} decoration = editor.decorateMarker(marker, decorationParams) runSetImmediateCallbacks() it "does not render highlights for off-screen lines until they come on-screen", -> - node.style.height = 2.5 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 2.5 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() marker = editor.displayBuffer.markBufferRange([[9, 2], [9, 4]], invalidate: 'inside') @@ -986,7 +985,7 @@ describe "EditorComponent", -> # Should not be rendering range containing the marker expect(component.getRenderedRowRange()[1]).toBeLessThan 9 - regions = node.querySelectorAll('.some-highlight .region') + regions = componentNode.querySelectorAll('.some-highlight .region') # Nothing when outside the rendered row range expect(regions.length).toBe 0 @@ -994,7 +993,7 @@ describe "EditorComponent", -> verticalScrollbarNode.scrollTop = 3.5 * lineHeightInPixels verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) - regions = node.querySelectorAll('.some-highlight .region') + regions = componentNode.querySelectorAll('.some-highlight .region') expect(regions.length).toBe 1 regionRect = regions[0].style @@ -1004,24 +1003,24 @@ describe "EditorComponent", -> expect(regionRect.width).toBe 2 * charWidth + 'px' it "renders highlights decoration's marker is added", -> - regions = node.querySelectorAll('.test-highlight .region') + regions = componentNode.querySelectorAll('.test-highlight .region') expect(regions.length).toBe 2 it "removes highlights when a decoration is removed", -> decoration.destroy() runSetImmediateCallbacks() - regions = node.querySelectorAll('.test-highlight .region') + regions = componentNode.querySelectorAll('.test-highlight .region') expect(regions.length).toBe 0 it "does not render a highlight that is within a fold", -> editor.foldBufferRow(1) runSetImmediateCallbacks() - expect(node.querySelectorAll('.test-highlight').length).toBe 0 + expect(componentNode.querySelectorAll('.test-highlight').length).toBe 0 it "removes highlights when a decoration's marker is destroyed", -> marker.destroy() runSetImmediateCallbacks() - regions = node.querySelectorAll('.test-highlight .region') + regions = componentNode.querySelectorAll('.test-highlight .region') expect(regions.length).toBe 0 it "only renders highlights when a decoration's marker is valid", -> @@ -1029,20 +1028,20 @@ describe "EditorComponent", -> runSetImmediateCallbacks() expect(marker.isValid()).toBe false - regions = node.querySelectorAll('.test-highlight .region') + regions = componentNode.querySelectorAll('.test-highlight .region') expect(regions.length).toBe 0 editor.getBuffer().undo() runSetImmediateCallbacks() expect(marker.isValid()).toBe true - regions = node.querySelectorAll('.test-highlight .region') + regions = componentNode.querySelectorAll('.test-highlight .region') expect(regions.length).toBe 2 describe "when flashing a decoration via Decoration::flash()", -> highlightNode = null beforeEach -> - highlightNode = node.querySelector('.test-highlight') + highlightNode = componentNode.querySelector('.test-highlight') it "adds and removes the flash class specified in ::flash", -> expect(highlightNode.classList.contains('flash-class')).toBe false @@ -1074,46 +1073,46 @@ describe "EditorComponent", -> describe "when a decoration's marker moves", -> it "moves rendered highlights when the buffer is changed", -> - regionStyle = node.querySelector('.test-highlight .region').style + regionStyle = componentNode.querySelector('.test-highlight .region').style originalTop = parseInt(regionStyle.top) editor.getBuffer().insert([0, 0], '\n') runSetImmediateCallbacks() - regionStyle = node.querySelector('.test-highlight .region').style + regionStyle = componentNode.querySelector('.test-highlight .region').style newTop = parseInt(regionStyle.top) expect(newTop).toBe originalTop + lineHeightInPixels it "moves rendered highlights when the marker is manually moved", -> - regionStyle = node.querySelector('.test-highlight .region').style + regionStyle = componentNode.querySelector('.test-highlight .region').style expect(parseInt(regionStyle.top)).toBe 2 * lineHeightInPixels marker.setBufferRange([[5, 8], [5, 13]]) runSetImmediateCallbacks() - regionStyle = node.querySelector('.test-highlight .region').style + regionStyle = componentNode.querySelector('.test-highlight .region').style expect(parseInt(regionStyle.top)).toBe 5 * lineHeightInPixels describe "when a decoration is updated via Decoration::update", -> it "renders the decoration's new params", -> - expect(node.querySelector('.test-highlight')).toBeTruthy() + expect(componentNode.querySelector('.test-highlight')).toBeTruthy() decoration.update(type: 'highlight', class: 'new-test-highlight') runSetImmediateCallbacks() - expect(node.querySelector('.test-highlight')).toBeFalsy() - expect(node.querySelector('.new-test-highlight')).toBeTruthy() + expect(componentNode.querySelector('.test-highlight')).toBeFalsy() + expect(componentNode.querySelector('.new-test-highlight')).toBeTruthy() describe "hidden input field", -> it "renders the hidden input field at the position of the last cursor if the cursor is on screen and the editor is focused", -> editor.setVerticalScrollMargin(0) editor.setHorizontalScrollMargin(0) - inputNode = node.querySelector('.hidden-input') - node.style.height = 5 * lineHeightInPixels + 'px' - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + inputNode = componentNode.querySelector('.hidden-input') + wrapperNode.style.height = 5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(editor.getCursorScreenPosition()).toEqual [0, 0] @@ -1156,14 +1155,14 @@ describe "EditorComponent", -> beforeEach -> delayAnimationFrames = true - linesNode = node.querySelector('.lines') + linesNode = componentNode.querySelector('.lines') describe "when a non-folded line is single-clicked", -> describe "when no modifier keys are held down", -> it "moves the cursor to the nearest screen position", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() editor.setScrollTop(3.5 * lineHeightInPixels) editor.setScrollLeft(2 * charWidth) runSetImmediateCallbacks() @@ -1288,7 +1287,7 @@ describe "EditorComponent", -> gutterNode = null beforeEach -> - gutterNode = node.querySelector('.gutter') + gutterNode = componentNode.querySelector('.gutter') describe "when the gutter is clicked", -> it "moves the cursor to the beginning of the clicked row", -> @@ -1383,20 +1382,20 @@ describe "EditorComponent", -> inputNode = null beforeEach -> - inputNode = node.querySelector('.hidden-input') + inputNode = componentNode.querySelector('.hidden-input') it "transfers focus to the hidden input", -> expect(document.activeElement).toBe document.body - node.focus() + componentNode.focus() expect(document.activeElement).toBe inputNode it "adds the 'is-focused' class to the editor when the hidden input is focused", -> expect(document.activeElement).toBe document.body inputNode.focus() - expect(node.classList.contains('is-focused')).toBe true + expect(componentNode.classList.contains('is-focused')).toBe true expect(wrapperView.hasClass('is-focused')).toBe true inputNode.blur() - expect(node.classList.contains('is-focused')).toBe false + expect(componentNode.classList.contains('is-focused')).toBe false expect(wrapperView.hasClass('is-focused')).toBe false describe "selection handling", -> @@ -1408,20 +1407,20 @@ describe "EditorComponent", -> runSetImmediateCallbacks() it "adds the 'has-selection' class to the editor when there is a selection", -> - expect(node.classList.contains('has-selection')).toBe false + expect(componentNode.classList.contains('has-selection')).toBe false editor.selectDown() runSetImmediateCallbacks() - expect(node.classList.contains('has-selection')).toBe true + expect(componentNode.classList.contains('has-selection')).toBe true cursor.moveDown() runSetImmediateCallbacks() - expect(node.classList.contains('has-selection')).toBe false + expect(componentNode.classList.contains('has-selection')).toBe false describe "scrolling", -> it "updates the vertical scrollbar when the scrollTop is changed in the model", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(verticalScrollbarNode.scrollTop).toBe 0 @@ -1431,11 +1430,11 @@ describe "EditorComponent", -> expect(verticalScrollbarNode.scrollTop).toBe 10 it "updates the horizontal scrollbar and the x transform of the lines based on the scrollLeft of the model", -> - node.style.width = 30 * charWidth + 'px' - component.measureScrollView() + componentNode.style.width = 30 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() - linesNode = node.querySelector('.lines') + linesNode = componentNode.querySelector('.lines') expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" expect(horizontalScrollbarNode.scrollLeft).toBe 0 @@ -1445,8 +1444,8 @@ describe "EditorComponent", -> expect(horizontalScrollbarNode.scrollLeft).toBe 100 it "updates the scrollLeft of the model when the scrollLeft of the horizontal scrollbar changes", -> - node.style.width = 30 * charWidth + 'px' - component.measureScrollView() + componentNode.style.width = 30 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(editor.getScrollLeft()).toBe 0 @@ -1456,9 +1455,9 @@ describe "EditorComponent", -> expect(editor.getScrollLeft()).toBe 100 it "does not obscure the last line with the horizontal scrollbar", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() editor.setScrollBottom(editor.getScrollHeight()) runSetImmediateCallbacks() lastLineNode = component.lineNodeForScreenRow(editor.getLastScreenRow()) @@ -1467,17 +1466,17 @@ describe "EditorComponent", -> expect(bottomOfLastLine).toBe topOfHorizontalScrollbar # Scroll so there's no space below the last line when the horizontal scrollbar disappears - node.style.width = 100 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.width = 100 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() bottomOfLastLine = lastLineNode.getBoundingClientRect().bottom - bottomOfEditor = node.getBoundingClientRect().bottom + bottomOfEditor = componentNode.getBoundingClientRect().bottom expect(bottomOfLastLine).toBe bottomOfEditor it "does not obscure the last character of the longest line with the vertical scrollbar", -> - node.style.height = 7 * lineHeightInPixels + 'px' - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 7 * lineHeightInPixels + 'px' + wrapperNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() editor.setScrollLeft(Infinity) runSetImmediateCallbacks() @@ -1489,32 +1488,32 @@ describe "EditorComponent", -> expect(verticalScrollbarNode.style.display).toBe 'none' expect(horizontalScrollbarNode.style.display).toBe 'none' - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = '1000px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = '1000px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(verticalScrollbarNode.style.display).toBe '' expect(horizontalScrollbarNode.style.display).toBe 'none' - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + componentNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(verticalScrollbarNode.style.display).toBe '' expect(horizontalScrollbarNode.style.display).toBe '' - node.style.height = 20 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 20 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(verticalScrollbarNode.style.display).toBe 'none' expect(horizontalScrollbarNode.style.display).toBe '' it "makes the dummy scrollbar divs only as tall/wide as the actual scrollbars", -> - node.style.height = 4 * lineHeightInPixels + 'px' - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4 * lineHeightInPixels + 'px' + wrapperNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() atom.themes.applyStylesheet "test", """ @@ -1524,44 +1523,46 @@ describe "EditorComponent", -> } """ - scrollbarCornerNode = node.querySelector('.scrollbar-corner') + scrollbarCornerNode = componentNode.querySelector('.scrollbar-corner') expect(verticalScrollbarNode.offsetWidth).toBe 8 expect(horizontalScrollbarNode.offsetHeight).toBe 8 expect(scrollbarCornerNode.offsetWidth).toBe 8 expect(scrollbarCornerNode.offsetHeight).toBe 8 + atom.themes.removeStylesheet('test') + it "assigns the bottom/right of the scrollbars to the width of the opposite scrollbar if it is visible", -> - scrollbarCornerNode = node.querySelector('.scrollbar-corner') + scrollbarCornerNode = componentNode.querySelector('.scrollbar-corner') expect(verticalScrollbarNode.style.bottom).toBe '' expect(horizontalScrollbarNode.style.right).toBe '' - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = '1000px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = '1000px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(verticalScrollbarNode.style.bottom).toBe '' expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px' expect(scrollbarCornerNode.style.display).toBe 'none' - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + componentNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px' expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px' expect(scrollbarCornerNode.style.display).toBe '' - node.style.height = 20 * lineHeightInPixels + 'px' - component.measureScrollView() + wrapperNode.style.height = 20 * lineHeightInPixels + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px' expect(horizontalScrollbarNode.style.right).toBe '' expect(scrollbarCornerNode.style.display).toBe 'none' it "accounts for the width of the gutter in the scrollWidth of the horizontal scrollbar", -> - gutterNode = node.querySelector('.gutter') - node.style.width = 10 * charWidth + 'px' - component.measureScrollView() + gutterNode = componentNode.querySelector('.gutter') + componentNode.style.width = 10 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() expect(horizontalScrollbarNode.scrollWidth).toBe gutterNode.offsetWidth + editor.getScrollWidth() @@ -1572,64 +1573,72 @@ describe "EditorComponent", -> describe "updating scrollTop and scrollLeft", -> beforeEach -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 20 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 20 * charWidth + 'px' + component.measureHeightAndWidth() runSetImmediateCallbacks() it "updates the scrollLeft or scrollTop on mousewheel events depending on which delta is greater (x or y)", -> expect(verticalScrollbarNode.scrollTop).toBe 0 expect(horizontalScrollbarNode.scrollLeft).toBe 0 - node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10)) + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10)) + runSetImmediateCallbacks() expect(verticalScrollbarNode.scrollTop).toBe 10 expect(horizontalScrollbarNode.scrollLeft).toBe 0 - node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5)) + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5)) + runSetImmediateCallbacks() expect(verticalScrollbarNode.scrollTop).toBe 10 expect(horizontalScrollbarNode.scrollLeft).toBe 15 it "updates the scrollLeft or scrollTop according to the scroll sensitivity", -> atom.config.set('editor.scrollSensitivity', 50) - node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10)) + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10)) + runSetImmediateCallbacks() expect(horizontalScrollbarNode.scrollLeft).toBe 0 - node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5)) + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5)) + runSetImmediateCallbacks() expect(verticalScrollbarNode.scrollTop).toBe 5 expect(horizontalScrollbarNode.scrollLeft).toBe 7 it "uses the previous scrollSensitivity when the value is not an int", -> atom.config.set('editor.scrollSensitivity', 'nope') - node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -10)) + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -10)) + runSetImmediateCallbacks() expect(verticalScrollbarNode.scrollTop).toBe 10 it "parses negative scrollSensitivity values as positive", -> atom.config.set('editor.scrollSensitivity', -50) - node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -10)) + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -10)) + runSetImmediateCallbacks() expect(verticalScrollbarNode.scrollTop).toBe 5 describe "when the mousewheel event's target is a line", -> it "keeps the line on the DOM if it is scrolled off-screen", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 20 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 20 * charWidth + 'px' + component.measureHeightAndWidth() - lineNode = node.querySelector('.line') + lineNode = componentNode.querySelector('.line') wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -500) Object.defineProperty(wheelEvent, 'target', get: -> lineNode) - node.dispatchEvent(wheelEvent) + componentNode.dispatchEvent(wheelEvent) + runSetImmediateCallbacks() - expect(node.contains(lineNode)).toBe true + expect(componentNode.contains(lineNode)).toBe true it "does not set the mouseWheelScreenRow if scrolling horizontally", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 20 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 20 * charWidth + 'px' + component.measureHeightAndWidth() - lineNode = node.querySelector('.line') + lineNode = componentNode.querySelector('.line') wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 10, wheelDeltaY: 0) Object.defineProperty(wheelEvent, 'target', get: -> lineNode) - node.dispatchEvent(wheelEvent) + componentNode.dispatchEvent(wheelEvent) + runSetImmediateCallbacks() expect(component.mouseWheelScreenRow).toBe null @@ -1638,10 +1647,11 @@ describe "EditorComponent", -> expect(editor.getScrollTop()).toBe 0 - lineNode = node.querySelector('.line') + lineNode = componentNode.querySelector('.line') wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 10) Object.defineProperty(wheelEvent, 'target', get: -> lineNode) - node.dispatchEvent(wheelEvent) + componentNode.dispatchEvent(wheelEvent) + runSetImmediateCallbacks() expect(editor.getScrollTop()).toBe 0 @@ -1650,38 +1660,76 @@ describe "EditorComponent", -> expect(component.mouseWheelScreenRow).toBe null it "does not preserve the line if it is on screen", -> - expect(node.querySelectorAll('.line-number').length).toBe 14 # dummy line - lineNodes = node.querySelectorAll('.line') + expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line + lineNodes = componentNode.querySelectorAll('.line') expect(lineNodes.length).toBe 13 lineNode = lineNodes[0] wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 100) # goes nowhere, we're already at scrollTop 0 Object.defineProperty(wheelEvent, 'target', get: -> lineNode) - node.dispatchEvent(wheelEvent) + componentNode.dispatchEvent(wheelEvent) + runSetImmediateCallbacks() expect(component.mouseWheelScreenRow).toBe 0 editor.insertText("hello") - expect(node.querySelectorAll('.line-number').length).toBe 14 # dummy line - expect(node.querySelectorAll('.line').length).toBe 13 + expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line + expect(componentNode.querySelectorAll('.line').length).toBe 13 describe "when the mousewheel event's target is a line number", -> it "keeps the line number on the DOM if it is scrolled off-screen", -> - node.style.height = 4.5 * lineHeightInPixels + 'px' - node.style.width = 20 * charWidth + 'px' - component.measureScrollView() + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 20 * charWidth + 'px' + component.measureHeightAndWidth() - lineNumberNode = node.querySelectorAll('.line-number')[1] + lineNumberNode = componentNode.querySelectorAll('.line-number')[1] wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -500) Object.defineProperty(wheelEvent, 'target', get: -> lineNumberNode) - node.dispatchEvent(wheelEvent) + componentNode.dispatchEvent(wheelEvent) + runSetImmediateCallbacks() - expect(node.contains(lineNumberNode)).toBe true + expect(componentNode.contains(lineNumberNode)).toBe true + + it "only prevents the default action of the mousewheel event if it actually lead to scrolling", -> + spyOn(WheelEvent::, 'preventDefault').andCallThrough() + + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 20 * charWidth + 'px' + component.measureHeightAndWidth() + runSetImmediateCallbacks() + + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 50)) + expect(editor.getScrollTop()).toBe 0 + expect(WheelEvent::preventDefault).not.toHaveBeenCalled() + + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -3000)) + runSetImmediateCallbacks() + expect(editor.getScrollTop()).toBe editor.getScrollHeight() - editor.getHeight() + 15 + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() + + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -30)) + expect(editor.getScrollTop()).toBe editor.getScrollHeight() - editor.getHeight() + 15 + expect(WheelEvent::preventDefault).not.toHaveBeenCalled() + + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 50, wheelDeltaY: 0)) + expect(editor.getScrollLeft()).toBe 0 + expect(WheelEvent::preventDefault).not.toHaveBeenCalled() + + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -3000, wheelDeltaY: 0)) + runSetImmediateCallbacks() + expect(editor.getScrollLeft()).toBe editor.getScrollWidth() - editor.getWidth() + 15 + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() + + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -30, wheelDeltaY: 0)) + expect(editor.getScrollLeft()).toBe editor.getScrollWidth() - editor.getWidth() + 15 + expect(WheelEvent::preventDefault).not.toHaveBeenCalled() describe "input events", -> inputNode = null beforeEach -> - inputNode = node.querySelector('.hidden-input') + inputNode = componentNode.querySelector('.hidden-input') buildTextInputEvent = ({data, target}) -> event = new Event('textInput') @@ -1690,28 +1738,28 @@ describe "EditorComponent", -> event it "inserts the newest character in the input's value into the buffer", -> - node.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode)) runSetImmediateCallbacks() expect(editor.lineForBufferRow(0)).toBe 'xvar quicksort = function () {' - node.dispatchEvent(buildTextInputEvent(data: 'y', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: 'y', target: inputNode)) runSetImmediateCallbacks() expect(editor.lineForBufferRow(0)).toBe 'xyvar quicksort = function () {' it "replaces the last character if the length of the input's value doesn't increase, as occurs with the accented character menu", -> - node.dispatchEvent(buildTextInputEvent(data: 'u', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: 'u', target: inputNode)) runSetImmediateCallbacks() expect(editor.lineForBufferRow(0)).toBe 'uvar quicksort = function () {' # simulate the accented character suggestion's selection of the previous character inputNode.setSelectionRange(0, 1) - node.dispatchEvent(buildTextInputEvent(data: 'ü', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: 'ü', target: inputNode)) runSetImmediateCallbacks() expect(editor.lineForBufferRow(0)).toBe 'üvar quicksort = function () {' it "does not handle input events when input is disabled", -> component.setInputEnabled(false) - node.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode)) runSetImmediateCallbacks() expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {' @@ -1725,51 +1773,51 @@ describe "EditorComponent", -> event beforeEach -> - inputNode = inputNode = node.querySelector('.hidden-input') + inputNode = inputNode = componentNode.querySelector('.hidden-input') describe "when nothing is selected", -> it "inserts the chosen completion", -> - node.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'svar quicksort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'sdvar quicksort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) - node.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe '速度var quicksort = function () {' it "reverts back to the original text when the completion helper is dismissed", -> - node.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'svar quicksort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'sdvar quicksort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {' it "allows multiple accented character to be inserted with the ' on a US international layout", -> inputNode.value = "'" inputNode.setSelectionRange(0, 1) - node.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: "'", target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: "'", target: inputNode)) expect(editor.lineForBufferRow(0)).toBe "'var quicksort = function () {" - node.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) - node.dispatchEvent(buildTextInputEvent(data: 'á', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: 'á', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe "ávar quicksort = function () {" inputNode.value = "'" inputNode.setSelectionRange(0, 1) - node.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: "'", target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: "'", target: inputNode)) expect(editor.lineForBufferRow(0)).toBe "á'var quicksort = function () {" - node.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) - node.dispatchEvent(buildTextInputEvent(data: 'á', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: 'á', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe "áávar quicksort = function () {" describe "when a string is selected", -> @@ -1777,26 +1825,26 @@ describe "EditorComponent", -> editor.setSelectedBufferRange [[0, 4], [0, 9]] # select 'quick' it "inserts the chosen completion", -> - node.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'var ssort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'var sdsort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) - node.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'var 速度sort = function () {' it "reverts back to the original text when the completion helper is dismissed", -> - node.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'var ssort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'var sdsort = function () {' - node.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {' describe "commands", -> @@ -1806,12 +1854,34 @@ describe "EditorComponent", -> event = new CustomEvent('editor:consolidate-selections', bubbles: true, cancelable: true) event.abortKeyBinding = jasmine.createSpy("event.abortKeyBinding") - node.dispatchEvent(event) + componentNode.dispatchEvent(event) expect(editor.consolidateSelections).toHaveBeenCalled() expect(event.abortKeyBinding).toHaveBeenCalled() describe "hiding and showing the editor", -> + describe "when the editor is hidden when it is mounted", -> + it "defers measurement and rendering until the editor becomes visible", -> + wrapperView.remove() + + hiddenParent = document.createElement('div') + hiddenParent.style.display = 'none' + contentNode.appendChild(hiddenParent) + + wrapperView = new ReactEditorView(editor, {lineOverdrawMargin}) + wrapperNode = wrapperView.element + wrapperView.appendTo(hiddenParent) + + {component} = wrapperView + componentNode = component.getDOMNode() + expect(componentNode.querySelectorAll('.line').length).toBe 0 + + hiddenParent.style.display = 'block' + advanceClock(component.domPollingInterval) + runSetImmediateCallbacks() + + expect(componentNode.querySelectorAll('.line').length).toBeGreaterThan 0 + describe "when the lineHeight changes while the editor is hidden", -> it "does not attempt to measure the lineHeightInPixels until the editor becomes visible again", -> wrapperView.hide() @@ -1849,8 +1919,8 @@ describe "EditorComponent", -> editor.setCursorBufferPosition([0, Infinity]) runSetImmediateCallbacks() - cursorLeft = node.querySelector('.cursor').getBoundingClientRect().left - line0Right = node.querySelector('.line > span:last-child').getBoundingClientRect().right + cursorLeft = componentNode.querySelector('.cursor').getBoundingClientRect().left + line0Right = componentNode.querySelector('.line > span:last-child').getBoundingClientRect().right expect(cursorLeft).toBe line0Right describe "when the fontFamily changes while the editor is hidden", -> @@ -1876,8 +1946,8 @@ describe "EditorComponent", -> editor.setCursorBufferPosition([0, Infinity]) runSetImmediateCallbacks() - cursorLeft = node.querySelector('.cursor').getBoundingClientRect().left - line0Right = node.querySelector('.line > span:last-child').getBoundingClientRect().right + cursorLeft = componentNode.querySelector('.cursor').getBoundingClientRect().left + line0Right = componentNode.querySelector('.line > span:last-child').getBoundingClientRect().right expect(cursorLeft).toBe line0Right describe "when stylesheets change while the editor is hidden", -> @@ -1899,8 +1969,8 @@ describe "EditorComponent", -> editor.setCursorBufferPosition([0, Infinity]) runSetImmediateCallbacks() - cursorLeft = node.querySelector('.cursor').getBoundingClientRect().left - line0Right = node.querySelector('.line > span:last-child').getBoundingClientRect().right + cursorLeft = componentNode.querySelector('.cursor').getBoundingClientRect().left + line0Right = componentNode.querySelector('.line > span:last-child').getBoundingClientRect().right expect(cursorLeft).toBe line0Right describe "when lines are changed while the editor is hidden", -> @@ -1911,7 +1981,7 @@ describe "EditorComponent", -> editor.setCursorBufferPosition([0, Infinity]) runSetImmediateCallbacks() wrapperView.show() - expect(node.querySelector('.cursor').style['-webkit-transform']).toBe "translate3d(#{9 * charWidth}px, 0px, 0px)" + expect(componentNode.querySelector('.cursor').style['-webkit-transform']).toBe "translate3d(#{9 * charWidth}px, 0px, 0px)" describe "soft wrapping", -> beforeEach -> @@ -1919,25 +1989,25 @@ describe "EditorComponent", -> it "updates the wrap location when the editor is resized", -> newHeight = 4 * editor.getLineHeightInPixels() + "px" - expect(newHeight).toBeLessThan node.style.height - node.style.height = newHeight + expect(parseInt(newHeight)).toBeLessThan wrapperNode.offsetHeight + wrapperNode.style.height = newHeight - advanceClock(component.scrollViewMeasurementInterval) + advanceClock(component.domPollingInterval) runSetImmediateCallbacks() - expect(node.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1) + expect(componentNode.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1) - gutterWidth = node.querySelector('.gutter').offsetWidth - node.style.width = gutterWidth + 14 * charWidth + 'px' - advanceClock(component.scrollViewMeasurementInterval) + gutterWidth = componentNode.querySelector('.gutter').offsetWidth + componentNode.style.width = gutterWidth + 14 * charWidth + 'px' + advanceClock(component.domPollingInterval) runSetImmediateCallbacks() - expect(node.querySelector('.line').textContent).toBe "var quicksort " + expect(componentNode.querySelector('.line').textContent).toBe "var quicksort " it "accounts for the scroll view's padding when determining the wrap location", -> - scrollViewNode = node.querySelector('.scroll-view') + scrollViewNode = componentNode.querySelector('.scroll-view') scrollViewNode.style.paddingLeft = 20 + 'px' - node.style.width = 30 * charWidth + 'px' + componentNode.style.width = 30 * charWidth + 'px' - advanceClock(component.scrollViewMeasurementInterval) + advanceClock(component.domPollingInterval) runSetImmediateCallbacks() expect(component.lineNodeForScreenRow(0).textContent).toBe "var quicksort = " @@ -1990,6 +2060,50 @@ describe "EditorComponent", -> runSetImmediateCallbacks() expect(lineNumberHasClass(4, 'cursor-line-no-selection')).toBe false + describe "height", -> + describe "when the wrapper view has an explicit height", -> + it "does not assign a height on the component node", -> + wrapperNode.style.height = '200px' + component.measureHeightAndWidth() + expect(componentNode.style.height).toBe '' + + describe "when the wrapper view does not have an explicit height", -> + it "assigns a height on the component node based on the editor's content", -> + expect(wrapperNode.style.height).toBe '' + expect(componentNode.style.height).toBe editor.getScreenLineCount() * lineHeightInPixels + 'px' + + describe "when the 'mini' property is true", -> + beforeEach -> + component.setProps(mini: true) + + it "does not render the gutter", -> + expect(componentNode.querySelector('.gutter')).toBeNull() + + it "adds the 'mini' class to the wrapper view", -> + expect(wrapperNode.classList.contains('mini')).toBe true + + it "does not render invisible characters", -> + component.setInvisibles(eol: 'E') + component.setShowInvisibles(true) + expect(component.lineNodeForScreenRow(0).textContent).toBe 'var quicksort = function () {' + + it "does not assign an explicit line-height on the editor contents", -> + expect(componentNode.style.lineHeight).toBe '' + + it "does not apply cursor-line decorations", -> + expect(component.lineNodeForScreenRow(0).classList.contains('cursor-line')).toBe false + + describe "when placholderText is specified", -> + it "renders the placeholder text when the buffer is empty", -> + component.setProps(placeholderText: 'Hello World') + expect(componentNode.querySelector('.placeholder-text')).toBeNull() + editor.setText('') + runSetImmediateCallbacks() + expect(componentNode.querySelector('.placeholder-text').textContent).toBe "Hello World" + editor.setText('hey') + runSetImmediateCallbacks() + expect(componentNode.querySelector('.placeholder-text')).toBeNull() + describe "legacy editor compatibility", -> it "triggers the screen-lines-changed event before the editor:display-update event", -> editor.setSoftWrap(true) @@ -2002,6 +2116,13 @@ describe "EditorComponent", -> expect(callingOrder).toEqual ['screen-lines-changed', 'editor:display-updated'] + it "works with the ::setEditorHeightInLines and ::setEditorWidthInChars helpers", -> + setEditorHeightInLines(wrapperView, 7) + expect(componentNode.offsetHeight).toBe lineHeightInPixels * 7 + + setEditorWidthInChars(wrapperView, 10) + expect(componentNode.querySelector('.scroll-view').offsetWidth).toBe charWidth * 10 + buildMouseEvent = (type, properties...) -> properties = extend({bubbles: true, cancelable: true}, properties...) properties.detail ?= 1 @@ -2014,14 +2135,14 @@ describe "EditorComponent", -> clientCoordinatesForScreenPosition = (screenPosition) -> positionOffset = editor.pixelPositionForScreenPosition(screenPosition) - scrollViewClientRect = node.querySelector('.scroll-view').getBoundingClientRect() + scrollViewClientRect = componentNode.querySelector('.scroll-view').getBoundingClientRect() clientX = scrollViewClientRect.left + positionOffset.left - editor.getScrollLeft() clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop() {clientX, clientY} clientCoordinatesForScreenRowInGutter = (screenRow) -> positionOffset = editor.pixelPositionForScreenPosition([screenRow, 1]) - gutterClientRect = node.querySelector('.gutter').getBoundingClientRect() + gutterClientRect = componentNode.querySelector('.gutter').getBoundingClientRect() clientX = gutterClientRect.left + positionOffset.left - editor.getScrollLeft() clientY = gutterClientRect.top + positionOffset.top - editor.getScrollTop() {clientX, clientY} diff --git a/spec/jasmine-helper.coffee b/spec/jasmine-helper.coffee index 87765634c..3de6d065f 100644 --- a/spec/jasmine-helper.coffee +++ b/spec/jasmine-helper.coffee @@ -1,7 +1,8 @@ fs = require 'fs' module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) -> - {$, $$} = require 'atom' + {$, $$} = require '../src/space-pen-extensions' + window[key] = value for key, value of require '../vendor/jasmine' {TerminalReporter} = require 'jasmine-tagged' diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 0010393fc..407cbb8a9 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -318,9 +318,13 @@ window.setEditorWidthInChars = (editorView, widthInChars, charWidth=editorView.c editorView.width(charWidth * widthInChars + editorView.gutter.outerWidth()) $(window).trigger 'resize' # update width of editor view's on-screen lines -window.setEditorHeightInLines = (editorView, heightInChars, charHeight=editorView.lineHeight) -> - editorView.height(charHeight * heightInChars + editorView.renderedLines.position().top) - $(window).trigger 'resize' # update editor view's on-screen lines +window.setEditorHeightInLines = (editorView, heightInLines, lineHeight=editorView.lineHeight) -> + if editorView.hasClass('react') + editorView.height(editorView.getEditor().getLineHeightInPixels() * heightInLines) + editorView.component?.measureHeightAndWidth() + else + editorView.height(lineHeight * heightInLines + editorView.renderedLines.position().top) + $(window).trigger 'resize' # update editor view's on-screen lines $.fn.resultOfTrigger = (type) -> event = $.Event(type) diff --git a/spec/spec-suite.coffee b/spec/spec-suite.coffee index e0adaf24b..817de7986 100644 --- a/spec/spec-suite.coffee +++ b/spec/spec-suite.coffee @@ -1,6 +1,5 @@ _ = require 'underscore-plus' fs = require 'fs-plus' -{Git} = require 'atom' path = require 'path' require './spec-helper' diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index b33e231e5..503a8fe9b 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -12,14 +12,14 @@ CursorsComponent = React.createClass cursorBlinkIntervalHandle: null render: -> - {cursorPixelRects, scrollTop, scrollLeft, defaultCharWidth, useHardwareAcceleration} = @props + {performedInitialMeasurement, cursorPixelRects, scrollTop, scrollLeft, defaultCharWidth, useHardwareAcceleration} = @props {blinkOff} = @state className = 'cursors' className += ' blink-off' if blinkOff div {className}, - if @isMounted() + if performedInitialMeasurement for key, pixelRect of cursorPixelRects CursorComponent({key, pixelRect, scrollTop, scrollLeft, defaultCharWidth, useHardwareAcceleration}) diff --git a/src/decoration.coffee b/src/decoration.coffee index 7ac64d4e6..96acfcee1 100644 --- a/src/decoration.coffee +++ b/src/decoration.coffee @@ -61,7 +61,7 @@ class Decoration return if @isDestroyed @isDestroyed = true @displayBuffer.removeDecoration(this) - @emit 'destoryed' + @emit 'destroyed' # Public: Update the marker with new params. Allows you to change the decoration's class. # diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 1d1ce4318..fa3939cb6 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -22,6 +22,8 @@ EditorComponent = React.createClass statics: performSyncUpdates: false + visible: false + autoHeight: false pendingScrollTop: null pendingScrollLeft: null selectOnMouseMove: false @@ -35,27 +37,28 @@ EditorComponent = React.createClass gutterWidth: 0 refreshingScrollbars: false measuringScrollbars: true - pendingVerticalScrollDelta: 0 - pendingHorizontalScrollDelta: 0 mouseWheelScreenRow: null mouseWheelScreenRowClearDelay: 150 scrollSensitivity: 0.4 - scrollViewMeasurementRequested: false + heightAndWidthMeasurementRequested: false measureLineHeightAndDefaultCharWidthWhenShown: false remeasureCharacterWidthsIfVisibleAfterNextUpdate: false inputEnabled: true - scrollViewMeasurementInterval: 100 scopedCharacterWidthsChangeCount: null - scrollViewMeasurementPaused: false + domPollingInterval: 100 + domPollingIntervalId: null + domPollingPaused: false render: -> {focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state - {editor, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props + {editor, mini, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props maxLineNumberDigits = editor.getLineCount().toString().length - invisibles = if showInvisibles then @state.invisibles else {} + invisibles = if showInvisibles and not mini then @state.invisibles else {} hasSelection = editor.getSelection()? and !editor.getSelection().isEmpty() + style = {fontSize, fontFamily} + style.lineHeight = lineHeight unless mini - if @isMounted() + if @performedInitialMeasurement renderedRowRange = @getRenderedRowRange() [renderedStartRow, renderedEndRow] = renderedRowRange cursorPixelRects = @getCursorPixelRects(renderedRowRange) @@ -63,6 +66,7 @@ EditorComponent = React.createClass decorations = editor.decorationsForScreenRowRange(renderedStartRow, renderedEndRow) highlightDecorations = @getHighlightDecorations(decorations) lineDecorations = @getLineDecorations(decorations) + placeholderText = @props.placeholderText if @props.placeholderText? and editor.isEmpty() scrollHeight = editor.getScrollHeight() scrollWidth = editor.getScrollWidth() @@ -81,12 +85,14 @@ EditorComponent = React.createClass if @mouseWheelScreenRow? and not (renderedStartRow <= @mouseWheelScreenRow < renderedEndRow) mouseWheelScreenRow = @mouseWheelScreenRow - className = 'editor-contents editor-colors' + style.height = scrollViewHeight if @autoHeight + + className = 'editor-contents' className += ' is-focused' if focused className += ' has-selection' if hasSelection - div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1, - if showLineNumbers + div {className, style, tabIndex: -1}, + if not mini and showLineNumbers GutterComponent { ref: 'gutter', onMouseDown: @onGutterMouseDown, onWidthChanged: @onGutterWidthChanged, lineDecorations, defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight, @@ -103,14 +109,16 @@ EditorComponent = React.createClass CursorsComponent { scrollTop, scrollLeft, cursorPixelRects, cursorBlinkPeriod, cursorBlinkResumeDelay, - lineHeightInPixels, defaultCharWidth, @scopedCharacterWidthsChangeCount, @useHardwareAcceleration + lineHeightInPixels, defaultCharWidth, @scopedCharacterWidthsChangeCount, @useHardwareAcceleration, + @performedInitialMeasurement } LinesComponent { ref: 'lines', editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations, showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft, @scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles, - visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration + visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration, + placeholderText, @performedInitialMeasurement } ScrollbarComponent @@ -149,8 +157,7 @@ EditorComponent = React.createClass {editor} = @props Math.max(1, Math.ceil(editor.getHeight() / editor.getLineHeightInPixels())) - getInitialState: -> - visible: true + getInitialState: -> {} getDefaultProps: -> cursorBlinkPeriod: 800 @@ -166,7 +173,7 @@ EditorComponent = React.createClass componentDidMount: -> {editor} = @props - @scrollViewMeasurementIntervalId = setInterval(@measureScrollView, @scrollViewMeasurementInterval) + @domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval) @observeEditor() @listenForDOMEvents() @@ -175,17 +182,14 @@ EditorComponent = React.createClass @subscribe atom.themes, 'stylesheet-added stylsheet-removed', @onStylesheetsChanged @subscribe scrollbarStyle.changes, @refreshScrollbars - editor.setVisible(true) - - @measureLineHeightAndDefaultCharWidth() - @measureScrollView() - @measureScrollbars() + if @visible = @isVisible() + @performInitialMeasurement() componentWillUnmount: -> @props.parentView.trigger 'editor:will-be-removed', [@props.parentView] @unsubscribe() - clearInterval(@scrollViewMeasurementIntervalId) - @scrollViewMeasurementIntervalId = null + clearInterval(@domPollingIntervalId) + @domPollingIntervalId = null componentDidUpdate: (prevProps, prevState) -> cursorsMoved = @cursorsMoved @@ -197,13 +201,26 @@ EditorComponent = React.createClass if @props.editor.isAlive() @updateParentViewFocusedClassIfNeeded(prevState) + @updateParentViewMiniClassIfNeeded(prevState) @props.parentView.trigger 'cursor:moved' if cursorsMoved @props.parentView.trigger 'selection:changed' if selectionChanged @props.parentView.trigger 'editor:display-updated' - @measureScrollbars() if @measuringScrollbars - @measureLineHeightAndCharWidthsIfNeeded(prevState) - @remeasureCharacterWidthsIfNeeded(prevState) + @visible = @isVisible() + if @performedInitialMeasurement + @measureScrollbars() if @measuringScrollbars + @measureLineHeightAndDefaultCharWidthIfNeeded(prevState) + @remeasureCharacterWidthsIfNeeded(prevState) + + performInitialMeasurement: -> + @updatesPaused = true + @measureLineHeightAndDefaultCharWidth() + @measureHeightAndWidth() + @measureScrollbars() + @props.editor.setVisible(true) + @updatesPaused = false + @performedInitialMeasurement = true + @requestUpdate() requestUpdate: -> if @updatesPaused @@ -220,7 +237,7 @@ EditorComponent = React.createClass requestAnimationFrame: (fn) -> @updatesPaused = true - @pauseScrollViewMeasurement() + @pauseDOMPolling() requestAnimationFrame => fn() @updatesPaused = false @@ -273,7 +290,9 @@ EditorComponent = React.createClass cursorPixelRects getLineDecorations: (decorationsByMarkerId) -> - {editor} = @props + {editor, mini} = @props + return {} if mini + decorationsByScreenRow = {} for markerId, decorations of decorationsByMarkerId marker = editor.getMarker(markerId) @@ -350,7 +369,7 @@ EditorComponent = React.createClass scrollViewNode = @refs.scrollView.getDOMNode() scrollViewNode.addEventListener 'scroll', @onScrollViewScroll - window.addEventListener 'resize', @requestScrollViewMeasurement + window.addEventListener 'resize', @requestHeightAndWidthMeasurement @listenForIMEEvents() @@ -557,28 +576,23 @@ EditorComponent = React.createClass @pendingScrollLeft = null onMouseWheel: (event) -> - event.preventDefault() - animationFramePending = @pendingHorizontalScrollDelta isnt 0 or @pendingVerticalScrollDelta isnt 0 + {editor} = @props # Only scroll in one direction at a time {wheelDeltaX, wheelDeltaY} = event if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY) # Scrolling horizontally - @pendingHorizontalScrollDelta -= Math.round(wheelDeltaX * @scrollSensitivity) + previousScrollLeft = editor.getScrollLeft() + editor.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)) + event.preventDefault() unless previousScrollLeft is editor.getScrollLeft() else # Scrolling vertically - @pendingVerticalScrollDelta -= Math.round(wheelDeltaY * @scrollSensitivity) @mouseWheelScreenRow = @screenRowForNode(event.target) @clearMouseWheelScreenRowAfterDelay ?= debounce(@clearMouseWheelScreenRow, @mouseWheelScreenRowClearDelay) @clearMouseWheelScreenRowAfterDelay() - - unless animationFramePending - @requestAnimationFrame => - {editor} = @props - editor.setScrollTop(editor.getScrollTop() + @pendingVerticalScrollDelta) - editor.setScrollLeft(editor.getScrollLeft() + @pendingHorizontalScrollDelta) - @pendingVerticalScrollDelta = 0 - @pendingHorizontalScrollDelta = 0 + previousScrollTop = editor.getScrollTop() + editor.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) + event.preventDefault() unless previousScrollTop is editor.getScrollTop() onScrollViewScroll: -> if @isMounted() @@ -656,7 +670,7 @@ EditorComponent = React.createClass onStylesheetsChanged: (stylesheet) -> @refreshScrollbars() if @containsScrollbarSelector(stylesheet) @remeasureCharacterWidthsIfVisibleAfterNextUpdate = true - @requestUpdate() if @state.visible + @requestUpdate() if @visible onScreenLinesChanged: (change) -> {editor} = @props @@ -733,68 +747,87 @@ EditorComponent = React.createClass window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) - pauseScrollViewMeasurement: -> - @scrollViewMeasurementPaused = true - @resumeScrollViewMeasurementAfterDelay ?= debounce(@resumeScrollViewMeasurement, 100) - @resumeScrollViewMeasurementAfterDelay() + isVisible: -> + node = @getDOMNode() + node.offsetHeight > 0 or node.offsetWidth > 0 - resumeScrollViewMeasurement: -> - @scrollViewMeasurementPaused = false + pauseDOMPolling: -> + @domPollingPaused = true + @resumeDOMPollingAfterDelay ?= debounce(@resumeDOMPolling, 100) + @resumeDOMPollingAfterDelay() - resumeScrollViewMeasurementAfterDelay: null # created lazily + resumeDOMPolling: -> + @domPollingPaused = false - requestScrollViewMeasurement: -> - return if @scrollViewMeasurementRequested + resumeDOMPollingAfterDelay: null # created lazily - @scrollViewMeasurementRequested = true + pollDOM: -> + return if @domPollingPaused or not @isMounted() + + wasVisible = @visible + if @visible = @isVisible() + if wasVisible + @measureHeightAndWidth() + else + @performInitialMeasurement() + + requestHeightAndWidthMeasurement: -> + return if @heightAndWidthMeasurementRequested + + @heightAndWidthMeasurementRequested = true requestAnimationFrame => - @scrollViewMeasurementRequested = false - @measureScrollView() + @heightAndWidthMeasurementRequested = false + @measureHeightAndWidth() # Measure explicitly-styled height and width and relay them to the model. If # these values aren't explicitly styled, we assume the editor is unconstrained # and use the scrollHeight / scrollWidth as its height and width in # calculations. - measureScrollView: -> - return if @scrollViewMeasurementPaused + measureHeightAndWidth: -> return unless @isMounted() - {editor} = @props - editorNode = @getDOMNode() + {editor, parentView} = @props + parentNode = parentView.element scrollViewNode = @refs.scrollView.getDOMNode() - {position} = getComputedStyle(editorNode) - {width, height} = editorNode.style + {position} = getComputedStyle(parentNode) + {height} = parentNode.style if position is 'absolute' or height + if @autoHeight + @autoHeight = false + @forceUpdate() + clientHeight = scrollViewNode.clientHeight editor.setHeight(clientHeight) if clientHeight > 0 + else + editor.setHeight(null) + @autoHeight = true - if position is 'absolute' or width - clientWidth = scrollViewNode.clientWidth - paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft) - clientWidth -= paddingLeft - editor.setWidth(clientWidth) if clientWidth > 0 + clientWidth = scrollViewNode.clientWidth + paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft) + clientWidth -= paddingLeft + editor.setWidth(clientWidth) if clientWidth > 0 - measureLineHeightAndCharWidthsIfNeeded: (prevState) -> + measureLineHeightAndDefaultCharWidthIfNeeded: (prevState) -> if not isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily') - if @state.visible + if @visible @measureLineHeightAndDefaultCharWidth() else @measureLineHeightAndDefaultCharWidthWhenShown = true - else if @measureLineHeightAndDefaultCharWidthWhenShown and @state.visible and not prevState.visible + else if @measureLineHeightAndDefaultCharWidthWhenShown and @visible + @measureLineHeightAndDefaultCharWidthWhenShown = false @measureLineHeightAndDefaultCharWidth() measureLineHeightAndDefaultCharWidth: -> - @measureLineHeightAndDefaultCharWidthWhenShown = false @refs.lines.measureLineHeightAndDefaultCharWidth() remeasureCharacterWidthsIfNeeded: (prevState) -> if not isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily') - if @state.visible + if @visible @remeasureCharacterWidths() else @remeasureCharacterWidthsIfVisibleAfterNextUpdate = true - else if @remeasureCharacterWidthsIfVisibleAfterNextUpdate and @state.visible + else if @remeasureCharacterWidthsIfVisibleAfterNextUpdate and @visible @remeasureCharacterWidthsIfVisibleAfterNextUpdate = false @remeasureCharacterWidths() @@ -805,6 +838,7 @@ EditorComponent = React.createClass @requestUpdate() measureScrollbars: -> + return unless @visible @measuringScrollbars = false {editor} = @props @@ -861,12 +895,6 @@ EditorComponent = React.createClass node = node.parentNode null - hide: -> - @setState(visible: false) - - show: -> - @setState(visible: true) - getFontSize: -> @state.fontSize @@ -940,6 +968,10 @@ EditorComponent = React.createClass if prevState.focused isnt @state.focused @props.parentView.toggleClass('is-focused', @props.focused) + updateParentViewMiniClassIfNeeded: (prevProps) -> + if prevProps.mini isnt @props.mini + @props.parentView.toggleClass('mini', @props.mini) + runScrollBenchmark: -> unless process.env.NODE_ENV is 'production' ReactPerf = require 'react-atom-fork/lib/ReactDefaultPerf' diff --git a/src/editor.coffee b/src/editor.coffee index 0e622b3a0..870c14b6c 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -518,6 +518,8 @@ class Editor extends Model # {Delegates to: TextBuffer.isModified} isModified: -> @buffer.isModified() + isEmpty: -> @buffer.isEmpty() + # Public: Determine whether the user should be prompted to save before closing # this editor. shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors() diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 9dc6fb3e7..ae2e9f9f2 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -19,9 +19,7 @@ GutterComponent = React.createClass {scrollHeight, scrollViewHeight, onMouseDown} = @props div className: 'gutter', onClick: @onClick, onMouseDown: onMouseDown, - # The line-numbers div must have the 'editor-colors' class so it has an - # opaque background to avoid sub-pixel anti-aliasing problems on the GPU - div className: 'gutter line-numbers editor-colors', ref: 'lineNumbers', style: + div className: 'line-numbers', ref: 'lineNumbers', style: height: Math.max(scrollHeight, scrollViewHeight) WebkitTransform: @getTransform() @@ -53,6 +51,8 @@ GutterComponent = React.createClass ) {renderedRowRange, pendingChanges, lineDecorations} = newProps + return false unless renderedRowRange? + for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0 return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index 2cadcb289..dd0749ffd 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -9,7 +9,7 @@ HighlightsComponent = React.createClass render: -> div className: 'highlights', - @renderHighlights() if @isMounted() + @renderHighlights() if @props.performedInitialMeasurement renderHighlights: -> {editor, highlightDecorations, lineHeightInPixels} = @props diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 4908221f4..d1b1aba94 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -16,18 +16,19 @@ LinesComponent = React.createClass displayName: 'LinesComponent' render: -> - if @isMounted() - {editor, highlightDecorations, scrollHeight, scrollWidth} = @props + {performedInitialMeasurement} = @props + + if performedInitialMeasurement + {editor, highlightDecorations, scrollHeight, scrollWidth, placeholderText} = @props {lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props style = height: Math.max(scrollHeight, scrollViewHeight) width: scrollWidth WebkitTransform: @getTransform() - # The lines div must have the 'editor-colors' class so it has an opaque - # background to avoid sub-pixel anti-aliasing problems on the GPU - div {className: 'lines editor-colors', style}, - HighlightsComponent({editor, highlightDecorations, lineHeightInPixels, defaultCharWidth, scopedCharacterWidthsChangeCount}) + div {className: 'lines', style}, + div className: 'placeholder-text', placeholderText if placeholderText? + HighlightsComponent({editor, highlightDecorations, lineHeightInPixels, defaultCharWidth, scopedCharacterWidthsChangeCount, performedInitialMeasurement}) getTransform: -> {scrollTop, scrollLeft, useHardwareAcceleration} = @props @@ -48,10 +49,13 @@ LinesComponent = React.createClass return true unless isEqualForProperties(newProps, @props, 'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible', - 'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration' + 'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration', + 'placeholderText', 'performedInitialMeasurement' ) {renderedRowRange, pendingChanges} = newProps + return false unless renderedRowRange? + [renderedStartRow, renderedEndRow] = renderedRowRange for change in pendingChanges if change.screenDelta is 0 @@ -212,7 +216,7 @@ LinesComponent = React.createClass # Find a common prefix for scope, i in desiredScopes - break unless scopeStack[i]?.scope is desiredScopes[i] + break unless scopeStack[i] is desiredScopes[i] # Pop scopes until we're at the common prefx until scopeStack.length is i diff --git a/src/react-editor-view.coffee b/src/react-editor-view.coffee index 7a97d5355..80f21ef57 100644 --- a/src/react-editor-view.coffee +++ b/src/react-editor-view.coffee @@ -1,15 +1,33 @@ {View, $} = require 'space-pen' React = require 'react-atom-fork' -EditorComponent = require './editor-component' {defaults} = require 'underscore-plus' +TextBuffer = require 'text-buffer' +Editor = require './editor' +EditorComponent = require './editor-component' module.exports = class ReactEditorView extends View - @content: -> @div class: 'editor react' + @content: (params) -> + attributes = params.attributes ? {} + attributes.class = 'editor react editor-colors' + @div attributes focusOnAttach: false - constructor: (@editor, @props) -> + constructor: (editorOrParams, @props) -> + if editorOrParams instanceof Editor + @editor = editorOrParams + else + {@editor, mini, placeholderText} = editorOrParams + @props ?= {} + @props.mini = mini + @props.placeholderText = placeholderText + @editor ?= new Editor + buffer: new TextBuffer + softWrap: false + tabLength: 2 + softTabs: true + super getEditor: -> @editor @@ -122,7 +140,7 @@ class ReactEditorView extends View pane?.splitDown(pane?.copyActiveItem()).activeView getPane: -> - @closest('.pane').view() + @parent('.item-views').parents('.pane').view() focus: -> if @component? @@ -132,11 +150,18 @@ class ReactEditorView extends View hide: -> super - @component?.hide() + @pollComponentDOM() show: -> super - @component?.show() + @pollComponentDOM() + + pollComponentDOM: -> + return unless @component? + valueToRestore = @component.performSyncUpdates + @component.performSyncUpdates = true + @component.pollDOM() + @component.performSyncUpdates = valueToRestore pageDown: -> @editor.pageDown() @@ -208,3 +233,9 @@ class ReactEditorView extends View resetDisplay: -> # No-op shim for package specs redraw: -> # No-op shim + + setPlaceholderText: (placeholderText) -> + if @component? + @component.setProps({placeholderText}) + else + @props.placeholderText = placeholderText diff --git a/static/editor.less b/static/editor.less index 03ce26693..ed8d6ba84 100644 --- a/static/editor.less +++ b/static/editor.less @@ -3,6 +3,10 @@ @import "octicon-mixins"; .editor.react { + .editor-contents { + width: 100%; + } + .underlayer { position: absolute; top: 0; @@ -81,15 +85,18 @@ } } +.editor { + z-index: 0; + font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier; + line-height: 1.3; +} + .editor, .editor-contents { overflow: hidden; cursor: text; display: -webkit-flex; -webkit-user-select: none; position: relative; - z-index: 0; - font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier; - line-height: 1.3; } .editor .gutter .line-number.cursor-line {