From 78e8e90db9e3c5083a998cfb256b043e65f7365d Mon Sep 17 00:00:00 2001 From: vpeil Date: Thu, 12 Feb 2015 21:25:31 +0100 Subject: [PATCH 001/126] :memo: a little hint added for 'Building from source for linux' *script/build* has the shebang #!/usr/bin/env node. On some system this command is no available but nodejs (e.g. on debian). The softlink fixes this problem, and all the other steps work fine. --- docs/build-instructions/linux.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index 6f9dbfde5..b36e08322 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -18,6 +18,9 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. * `sudo apt-get install build-essential git libgnome-keyring-dev fakeroot` * Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os). + * Make sure the command `node` is available after Node.js installation (some sytems install it as `nodejs`). + * `which node` to check it. + * If output is emtpy run `sudo ln -s /usr/bin/nodejs /usr/bin/node` to fix this. ### Fedora / CentOS / RHEL From e5926395ceeeac4c1c50ed645a47e8f16f0e2043 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 12 Feb 2015 11:37:43 -0800 Subject: [PATCH 002/126] Move reactCompat option to transform as per 6to5 deprecation warning. Bumped the version of 6to5-core to `^3.6.0` to ensure `reactCompat` is available as a transform rather than an option. --- package.json | 2 +- src/6to5.coffee | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8b56e0bb4..6eeecbb3d 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ ], "atomShellVersion": "0.21.0", "dependencies": { - "6to5-core": "^3.0.14", + "6to5-core": "^3.6.0", "async": "0.2.6", "atom-keymap": "^3.1.2", "bootstrap": "git+https://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", diff --git a/src/6to5.coffee b/src/6to5.coffee index 15059ad62..97c8a11a9 100644 --- a/src/6to5.coffee +++ b/src/6to5.coffee @@ -18,11 +18,6 @@ defaultOptions = # when the source map is inlined. sourceMap: 'inline' - # Because Atom is currently packaged with a fork of React v0.11, - # it makes sense to use the --react-compat option so the React - # JSX transformer produces pre-v0.12 code. - reactCompat: true - # Blacklisted features do not get transpiled. Features that are # natively supported in the target environment should be listed # here. Because Atom uses a bleeding edge version of Node/io.js, @@ -40,6 +35,11 @@ defaultOptions = # Target a version of the regenerator runtime that # supports yield so the transpiled code is cleaner/smaller. 'asyncToGenerator' + + # Because Atom is currently packaged with a fork of React v0.11, + # it makes sense to use the reactCompat transform so the React + # JSX transformer produces pre-v0.12 code. + 'reactCompat' ] ### From 10c3fd25248b4ea30b166fc639c592e6f5440cac Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 09:22:39 -0800 Subject: [PATCH 003/126] :arrow_up: property-accessors@1.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89477634a..dc0c09b24 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "oniguruma": "^4.0.0", "optimist": "0.4.0", "pathwatcher": "^3.1.1", - "property-accessors": "^1", + "property-accessors": "^1.1.3", "q": "^1.1.2", "random-words": "0.0.1", "react-atom-fork": "^0.11.5", From fa46bf8f0027161599f9b0040fbfb0b064c90c07 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 09:23:11 -0800 Subject: [PATCH 004/126] :arrow_up: emissary@1.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dc0c09b24..5aef6d6b4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "coffeestack": "^1.1.1", "color": "^0.7.3", "delegato": "^1", - "emissary": "^1.3.1", + "emissary": "^1.3.3", "event-kit": "^1.0.2", "first-mate": "^3.0.0", "fs-plus": "^2.5", From ec183b0fae899f33839c1d35a5c07d236351b434 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 09:39:03 -0800 Subject: [PATCH 005/126] :arrow_up: language-ruby-on-rails@0.19 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73e447694..54b3b50e9 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "language-property-list": "0.8.0", "language-python": "0.32.0", "language-ruby": "0.48.0", - "language-ruby-on-rails": "0.18.0", + "language-ruby-on-rails": "0.19.0", "language-sass": "0.34.0", "language-shellscript": "0.12.0", "language-source": "0.9.0", From fdb0d02ef6ee2c7686f6ebc61d0b625daf73770d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 09:45:09 -0800 Subject: [PATCH 006/126] :arrow_up: language-gfm@0.64 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 54b3b50e9..30dfa6a54 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "language-coffee-script": "0.39.0", "language-csharp": "0.5.0", "language-css": "0.28.0", - "language-gfm": "0.63.0", + "language-gfm": "0.64.0", "language-git": "0.10.0", "language-go": "0.21.0", "language-html": "0.29.0", From b155ad3689eb7ca468792b3f9ee2ebdd4e141908 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 13 Feb 2015 10:40:41 -0800 Subject: [PATCH 007/126] :arrow_up: settings-view@0.182.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30dfa6a54..b24147c70 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "open-on-github": "0.32.0", "package-generator": "0.38.0", "release-notes": "0.50.0", - "settings-view": "0.181.0", + "settings-view": "0.182.0", "snippets": "0.74.0", "spell-check": "0.54.0", "status-bar": "0.60.0", From 86da2ba888e4d034d27c0738c70ec339c4402063 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 11:35:02 -0800 Subject: [PATCH 008/126] :memo: Use update-alternatives --- docs/build-instructions/linux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index b36e08322..f51899546 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -19,8 +19,8 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. * `sudo apt-get install build-essential git libgnome-keyring-dev fakeroot` * Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os). * Make sure the command `node` is available after Node.js installation (some sytems install it as `nodejs`). - * `which node` to check it. - * If output is emtpy run `sudo ln -s /usr/bin/nodejs /usr/bin/node` to fix this. + * Use `which node` to check if its available. + * Use `sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10` to update it. ### Fedora / CentOS / RHEL From 49d72f8c50e6fa8c76695b5d484924c9346d6f38 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 11:35:39 -0800 Subject: [PATCH 009/126] :memo: its -> it is --- docs/build-instructions/linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index f51899546..6cb985e87 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -19,7 +19,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. * `sudo apt-get install build-essential git libgnome-keyring-dev fakeroot` * Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os). * Make sure the command `node` is available after Node.js installation (some sytems install it as `nodejs`). - * Use `which node` to check if its available. + * Use `which node` to check if it is available. * Use `sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10` to update it. ### Fedora / CentOS / RHEL From c3ae0441b53f502bdf30620007033b2e8eb2648e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 11:38:28 -0800 Subject: [PATCH 010/126] :arrow_up: apm@0.139 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 91e03bdca..26e952077 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.138.0" + "atom-package-manager": "0.139.0" } } From f7138e51904d8503eda96f5fa158680a83c4b37f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 12:30:37 -0800 Subject: [PATCH 011/126] :arrow_up: pathwatcher@3.3 --- package.json | 2 +- src/git-repository-provider.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b24147c70..1f48c92b5 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "nslog": "^2.0.0", "oniguruma": "^4.0.0", "optimist": "0.4.0", - "pathwatcher": "^3.1.1", + "pathwatcher": "^3.3", "property-accessors": "^1.1.3", "q": "^1.1.2", "random-words": "0.0.1", diff --git a/src/git-repository-provider.coffee b/src/git-repository-provider.coffee index 9772da416..de8cd64d4 100644 --- a/src/git-repository-provider.coffee +++ b/src/git-repository-provider.coffee @@ -27,7 +27,7 @@ isValidGitDirectorySync = (directory) -> # the heuristic adopted by the valid_repository_path() function defined in # node_modules/git-utils/deps/libgit2/src/repository.c. return directoryExistsSync(directory.getSubdirectory('objects')) and - directory.getFile('HEAD').exists() and + directory.getFile('HEAD').existsSync() and directoryExistsSync(directory.getSubdirectory('refs')) # Returns a boolean indicating whether the specified directory exists. From c260a29434547493d5f22508e2afd2da96f13f40 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 12:58:02 -0800 Subject: [PATCH 012/126] :arrow_up: pathwatcher@3.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f48c92b5..4f2e2a621 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "nslog": "^2.0.0", "oniguruma": "^4.0.0", "optimist": "0.4.0", - "pathwatcher": "^3.3", + "pathwatcher": "^3.3.1", "property-accessors": "^1.1.3", "q": "^1.1.2", "random-words": "0.0.1", From ac7057bb2e74d00ddc38079bd5bf3f8583c576e2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 13 Feb 2015 13:04:22 -0800 Subject: [PATCH 013/126] Always open new window when --wait is passed Signed-off-by: Nathan Sobo --- src/browser/atom-application.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 2f46ba6d2..4cc25f551 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -354,9 +354,9 @@ class AtomApplication unless pidToKillWhenClosed or newWindow existingWindow = @windowForPaths(pathsToOpen, devMode) - # Default to using the specified window or the last focused window - if pathsToOpen.every((pathToOpen) -> fs.statSyncNoException(pathToOpen).isFile?()) - existingWindow ?= window ? @lastFocusedWindow + # Default to using the specified window or the last focused window + if pathsToOpen.every((pathToOpen) -> fs.statSyncNoException(pathToOpen).isFile?()) + existingWindow ?= window ? @lastFocusedWindow if existingWindow? openedWindow = existingWindow From 8f9b6a30821f16a7acc74aaffd159f942c8dd387 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 12 Feb 2015 20:10:30 -0800 Subject: [PATCH 014/126] Replace directoryExistsSync() with Directory::existsSync(). This makes it possible to remove a TODO in `GitRepositoryProvider`. --- src/git-repository-provider.coffee | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/git-repository-provider.coffee b/src/git-repository-provider.coffee index de8cd64d4..dd9c41f41 100644 --- a/src/git-repository-provider.coffee +++ b/src/git-repository-provider.coffee @@ -11,7 +11,7 @@ findGitDirectorySync = (directory) -> # can return cached values rather than always returning new objects: # getParent(), getFile(), getSubdirectory(). gitDir = directory.getSubdirectory('.git') - if directoryExistsSync(gitDir) and isValidGitDirectorySync gitDir + if gitDir.existsSync() and isValidGitDirectorySync gitDir gitDir else if directory.isRoot() return null @@ -26,19 +26,9 @@ isValidGitDirectorySync = (directory) -> # To decide whether a directory has a valid .git folder, we use # the heuristic adopted by the valid_repository_path() function defined in # node_modules/git-utils/deps/libgit2/src/repository.c. - return directoryExistsSync(directory.getSubdirectory('objects')) and + return directory.getSubdirectory('objects').existsSync() and directory.getFile('HEAD').existsSync() and - directoryExistsSync(directory.getSubdirectory('refs')) - -# Returns a boolean indicating whether the specified directory exists. -# -# * `directory` {Directory} to check for existence. -directoryExistsSync = (directory) -> - # TODO: Directory should have its own existsSync() method. Currently, File has - # an exists() method, which is synchronous, so it may be tricky to achieve - # consistency between the File and Directory APIs. Once Directory has its own - # method, this function should be replaced with direct calls to existsSync(). - return fs.existsSync(directory.getPath()) + directory.getSubdirectory('refs').existsSync() # Provider that conforms to the atom.repository-provider@0.1.0 service. module.exports = From 7e32dc6e7dbb8cf18af2425bd0355d3ed6b8ec03 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 13 Feb 2015 14:33:24 -0700 Subject: [PATCH 015/126] Tempororily disable random presenter spec --- spec/text-editor-presenter-spec.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 18aa579ec..2ae278521 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1912,7 +1912,9 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") expect(presenter.state.height).toBe editor.getScreenLineCount() * 20 - describe "when the model and view measurements are mutated randomly", -> + # disabled until we fix an issue with display buffer markers not updating when + # they are moved on screen but not in the buffer + xdescribe "when the model and view measurements are mutated randomly", -> [editor, buffer, presenterParams, presenter, statements] = [] it "correctly maintains the presenter state", -> From 52681e6544109e588b2d995c963d5e2e565b43ab Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 13:57:20 -0800 Subject: [PATCH 016/126] :arrow_up: language-c@0.39 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4f2e2a621..7086bd93b 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "welcome": "0.21.0", "whitespace": "0.29.0", "wrap-guide": "0.31.0", - "language-c": "0.38.0", + "language-c": "0.39.0", "language-clojure": "0.12.0", "language-coffee-script": "0.39.0", "language-csharp": "0.5.0", From ea38926e2694d6872249c93ad28612f07ca8285a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Feb 2015 16:05:05 -0800 Subject: [PATCH 017/126] :arrow_up: text-buffer@4.1.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7086bd93b..08c968cc6 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "space-pen": "3.8.2", "stacktrace-parser": "0.1.1", "temp": "0.8.1", - "text-buffer": "^4.1.4", + "text-buffer": "^4.1.5", "theorist": "^1.0.2", "underscore-plus": "^1.6.6", "vm-compatibility-layer": "0.1.0" From 9517410ef417e7f9955538ef8dbbe5ec1a963d14 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 13 Feb 2015 18:05:05 -0800 Subject: [PATCH 018/126] Incrementally initialize presenter in random mutation spec Fix resulting failures Signed-off-by: Nathan Sobo --- spec/text-editor-presenter-spec.coffee | 78 +++++++++++++++++++++----- src/text-editor-presenter.coffee | 24 +++++--- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 2ae278521..f77a6862c 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1917,13 +1917,16 @@ describe "TextEditorPresenter", -> xdescribe "when the model and view measurements are mutated randomly", -> [editor, buffer, presenterParams, presenter, statements] = [] + recordStatement = (statement) -> statements.push(statement) + it "correctly maintains the presenter state", -> _.times 20, -> waits(0) runs -> performSetup() + performRandomInitialization(recordStatement) _.times 20, -> - performRandomAction (statement) -> statements.push(statement) + performRandomAction recordStatement expectValidState() performTeardown() @@ -1939,18 +1942,25 @@ describe "TextEditorPresenter", -> editor.setEditorWidthInChars(80) presenterParams = model: editor - explicitHeight: 50 - contentFrameWidth: 300 - scrollTop: 0 - scrollLeft: 0 - lineHeight: 10 - baseCharacterWidth: 10 lineOverdrawMargin: 1 - horizontalScrollbarHeight: 5 - verticalScrollbarWidth: 5 presenter = new TextEditorPresenter(presenterParams) statements = [] + performRandomInitialization = (log) -> + actions = _.shuffle([ + changeScrollLeft + changeScrollTop + changeExplicitHeight + changeContentFrameWidth + changeLineHeight + changeBaseCharacterWidth + changeHorizontalScrollbarHeight + changeVerticalScrollbarWidth + ]) + for action in actions + action(log) + expectValidState() + performTeardown = -> buffer.destroy() @@ -1991,14 +2001,16 @@ describe "TextEditorPresenter", -> ])(log) changeScrollTop = (log) -> - scrollHeight = presenterParams.lineHeight * editor.getScreenLineCount() - newScrollTop = Math.max(0, _.random(0, scrollHeight - presenterParams.explicitHeight)) + scrollHeight = (presenterParams.lineHeight ? 10) * editor.getScreenLineCount() + explicitHeight = (presenterParams.explicitHeight ? 500) + newScrollTop = Math.max(0, _.random(0, scrollHeight - explicitHeight)) log "presenter.setScrollTop(#{newScrollTop})" presenter.setScrollTop(newScrollTop) changeScrollLeft = (log) -> - scrollWidth = presenter.scrollWidth - newScrollLeft = Math.max(0, _.random(0, scrollWidth - presenterParams.contentFrameWidth)) + scrollWidth = presenter.scrollWidth ? 300 + contentFrameWidth = presenter.contentFrameWidth ? 200 + newScrollLeft = Math.max(0, _.random(0, scrollWidth - contentFrameWidth)) log """ presenterParams.scrollLeft = #{newScrollLeft} presenter.setScrollLeft(#{newScrollLeft}) @@ -2007,7 +2019,7 @@ describe "TextEditorPresenter", -> presenter.setScrollLeft(newScrollLeft) changeExplicitHeight = (log) -> - scrollHeight = presenterParams.lineHeight * editor.getScreenLineCount() + scrollHeight = (presenterParams.lineHeight ? 10) * editor.getScreenLineCount() newExplicitHeight = _.random(30, scrollHeight * 1.5) log """ presenterParams.explicitHeight = #{newExplicitHeight} @@ -2017,7 +2029,7 @@ describe "TextEditorPresenter", -> presenter.setExplicitHeight(newExplicitHeight) changeContentFrameWidth = (log) -> - scrollWidth = presenter.scrollWidth + scrollWidth = presenter.scrollWidth ? 300 newContentFrameWidth = _.random(100, scrollWidth * 1.5) log """ presenterParams.contentFrameWidth = #{newContentFrameWidth} @@ -2026,6 +2038,42 @@ describe "TextEditorPresenter", -> presenterParams.contentFrameWidth = newContentFrameWidth presenter.setContentFrameWidth(newContentFrameWidth) + changeLineHeight = (log) -> + newLineHeight = _.random(5, 15) + log """ + presenterParams.lineHeight = #{newLineHeight} + presenter.setLineHeight(#{newLineHeight}) + """ + presenterParams.lineHeight = newLineHeight + presenter.setLineHeight(newLineHeight) + + changeBaseCharacterWidth = (log) -> + newBaseCharacterWidth = _.random(5, 15) + log """ + presenterParams.baseCharacterWidth = #{newBaseCharacterWidth} + presenter.setBaseCharacterWidth(#{newBaseCharacterWidth}) + """ + presenterParams.baseCharacterWidth = newBaseCharacterWidth + presenter.setBaseCharacterWidth(newBaseCharacterWidth) + + changeHorizontalScrollbarHeight = (log) -> + newHorizontalScrollbarHeight = _.random(2, 15) + log """ + presenterParams.horizontalScrollbarHeight = #{newHorizontalScrollbarHeight} + presenter.setHorizontalScrollbarHeight(#{newHorizontalScrollbarHeight}) + """ + presenterParams.horizontalScrollbarHeight = newHorizontalScrollbarHeight + presenter.setHorizontalScrollbarHeight(newHorizontalScrollbarHeight) + + changeVerticalScrollbarWidth = (log) -> + newVerticalScrollbarWidth = _.random(2, 15) + log """ + presenterParams.verticalScrollbarWidth = #{newVerticalScrollbarWidth} + presenter.setVerticalScrollbarWidth(#{newVerticalScrollbarWidth}) + """ + presenterParams.verticalScrollbarWidth = newVerticalScrollbarWidth + presenter.setVerticalScrollbarWidth(newVerticalScrollbarWidth) + toggleSoftWrap = (log) -> softWrapped = not editor.isSoftWrapped() log "editor.setSoftWrapped(#{softWrapped})" diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index b8b9de943..04245aade 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -330,14 +330,14 @@ class TextEditorPresenter @updateScrollTop() updateContentDimensions: -> - return unless @lineHeight? and @baseCharacterWidth? + if @lineHeight? + oldContentHeight = @contentHeight + @contentHeight = @lineHeight * @model.getScreenLineCount() - oldContentHeight = @contentHeight - @contentHeight = @lineHeight * @model.getScreenLineCount() - - oldContentWidth = @contentWidth - @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left - @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width + if @baseCharacterWidth? + oldContentWidth = @contentWidth + @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left + @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width if @contentHeight isnt oldContentHeight @updateHeight() @@ -447,7 +447,7 @@ class TextEditorPresenter setScrollTop: (scrollTop) -> scrollTop = @constrainScrollTop(scrollTop) - unless @scrollTop is scrollTop + unless @scrollTop is scrollTop or Number.isNaN(scrollTop) @scrollTop = scrollTop @model.setScrollTop(scrollTop) @updateStartRow() @@ -478,7 +478,7 @@ class TextEditorPresenter setScrollLeft: (scrollLeft) -> scrollLeft = @constrainScrollLeft(scrollLeft) - unless @scrollLeft is scrollLeft + unless @scrollLeft is scrollLeft or Number.isNaN(scrollLeft) oldScrollLeft = @scrollLeft @scrollLeft = scrollLeft @model.setScrollLeft(scrollLeft) @@ -493,6 +493,7 @@ class TextEditorPresenter @updateScrollbarDimensions() @updateScrollbarsState() @updateVerticalScrollState() + @updateHorizontalScrollState() @updateCursorsState() unless oldHorizontalScrollbarHeight? setVerticalScrollbarWidth: (verticalScrollbarWidth) -> @@ -502,6 +503,7 @@ class TextEditorPresenter @model.setVerticalScrollbarWidth(verticalScrollbarWidth) @updateScrollbarDimensions() @updateScrollbarsState() + @updateVerticalScrollState() @updateHorizontalScrollState() @updateCursorsState() unless oldVerticalScrollbarWidth? @@ -567,7 +569,9 @@ class TextEditorPresenter @updateStartRow() @updateEndRow() @updateHeightState() + @updateHorizontalScrollState() @updateVerticalScrollState() + @updateScrollbarsState() @updateDecorations() @updateLinesState() @updateCursorsState() @@ -613,6 +617,8 @@ class TextEditorPresenter @updateContentDimensions() @updateHorizontalScrollState() + @updateVerticalScrollState() + @updateScrollbarsState() @updateContentState() @updateDecorations() @updateLinesState() From 55a70da3cf68be175a9df41f5dbc7d4a34b7a67e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 13 Feb 2015 19:37:27 -0700 Subject: [PATCH 019/126] Hide scrollbars on mini editors Fixes #5548 --- spec/text-editor-presenter-spec.coffee | 12 ++++++++++++ src/text-editor-presenter.coffee | 12 ++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index f77a6862c..b1ee17ef1 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -77,6 +77,18 @@ describe "TextEditorPresenter", -> presenter.setExplicitHeight((editor.getLineCount() * 10) - 1) expect(state.horizontalScrollbar.visible).toBe true + it "is false if the editor is mini", -> + presenter = buildPresenter + explicitHeight: editor.getLineCount() * 10 + contentFrameWidth: editor.getMaxScreenLineLength() * 10 - 10 + baseCharacterWidth: 10 + + expect(presenter.state.horizontalScrollbar.visible).toBe true + editor.setMini(true) + expect(presenter.state.horizontalScrollbar.visible).toBe false + editor.setMini(false) + expect(presenter.state.horizontalScrollbar.visible).toBe true + describe ".height", -> it "tracks the value of ::horizontalScrollbarHeight", -> presenter = buildPresenter(horizontalScrollbarHeight: 10) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 04245aade..56a2fad98 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -59,6 +59,8 @@ class TextEditorPresenter @disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this)) @disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this)) @disposables.add @model.onDidChangeMini => + @updateScrollbarDimensions() + @updateScrollbarsState() @updateContentState() @updateDecorations() @updateLinesState() @@ -395,12 +397,14 @@ class TextEditorPresenter clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @measuredHorizontalScrollbarHeight horizontalScrollbarVisible = - @contentWidth > clientWidthWithoutVerticalScrollbar or - @contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar + not @model.isMini() and + (@contentWidth > clientWidthWithoutVerticalScrollbar or + @contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar) verticalScrollbarVisible = - @contentHeight > clientHeightWithoutHorizontalScrollbar or - @contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar + not @model.isMini() and + (@contentHeight > clientHeightWithoutHorizontalScrollbar or + @contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar) horizontalScrollbarHeight = if horizontalScrollbarVisible From 5805bf967553b2337521b1dc725ebc9b23fdd19e Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 12 Feb 2015 17:11:06 -0800 Subject: [PATCH 020/126] Try to set the Project's repo if it does not have one when a new RepositoryProvider is registered. I tested this using my test `HgRepositoryProvider`. Now when I run the following from the command line: atom And then run the following in the console: atom.project.getRepositories() I get an array with an `HgRepository` in it. Previously, I got an empty array because the `Project`'s paths were set before my `HgRepositoryProvider` was registered. --- spec/project-spec.coffee | 33 +++++++++++++++++++++++++++++++++ spec/spec-helper.coffee | 2 +- src/project.coffee | 10 +++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index dbd0a0fbd..ed4cd8c2f 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -13,6 +13,39 @@ describe "Project", -> beforeEach -> atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')]) + describe "constructor", -> + it "tries to update repositories when a new RepositoryProvider is registered", -> + atom.project.setPaths(["/tmp"]) + expect(atom.project.getRepositories()).toEqual [null] + expect(atom.project.repositoryProviders.length).toEqual 1 + + # Register a new RepositoryProvider. + dummyRepository = destroy: () -> + repositoryProvider = + repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository) + repositoryForDirectorySync: (directory) -> dummyRepository + atom.packages.serviceHub.provide( + "atom.repository-provider", "0.1.0", repositoryProvider) + + expect(atom.project.repositoryProviders.length).toBe 2 + expect(atom.project.getRepositories()).toEqual [dummyRepository] + + it "does not update @repositories if every path has a Repository", -> + repositories = atom.project.getRepositories() + expect(repositories.length).toEqual 1 + [repository] = repositories + expect(repository).toBeTruthy() + + # Register a new RepositoryProvider. + dummyRepository = destroy: () -> + repositoryProvider = + repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository) + repositoryForDirectorySync: (directory) -> dummyRepository + atom.packages.serviceHub.provide( + "atom.repository-provider", "0.1.0", repositoryProvider) + + expect(atom.project.getRepositories()).toBe repositories + describe "serialization", -> deserializedProject = null diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 484ade2d9..a8b7478c1 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -76,9 +76,9 @@ beforeEach -> $.fx.off = true documentTitle = null projectPath = specProjectPath ? path.join(@specDirectory, 'fixtures') + atom.packages.serviceHub = new ServiceHub atom.project = new Project(paths: [projectPath]) atom.workspace = new Workspace() - atom.packages.serviceHub = new ServiceHub atom.keymaps.keyBindings = _.clone(keyBindingsToRestore) atom.commands.restoreSnapshot(commandsToRestore) atom.styles.restoreSnapshot(styleElementsToRestore) diff --git a/src/project.coffee b/src/project.coffee index 150e28d9a..4615a01b0 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -53,7 +53,15 @@ class Project extends Model atom.packages.serviceHub.consume( 'atom.repository-provider', '^0.1.0', - (provider) => @repositoryProviders.push(provider)) + (provider) => + @repositoryProviders.push(provider) + + # If a path in getPaths() does not have a corresponding Repository, try + # to assign one by running through setPaths() again now that + # @repositoryProviders has been updated. + if null in @repositories + @setPaths(@getPaths()) + ) @subscribeToBuffer(buffer) for buffer in @buffers From 6256f62217998bdcd32b046b769d8af1424deba9 Mon Sep 17 00:00:00 2001 From: Jeremy Engel Date: Sat, 14 Feb 2015 09:36:20 -0800 Subject: [PATCH 021/126] Change "optinal" to "optional" --- src/atom.coffee | 2 +- src/git-repository.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index dcc8e0cfb..723d235e5 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -803,7 +803,7 @@ class Atom extends Model # require completes. # # * `id` The {String} module name or path. - # * `globals` An optinal {Object} to set as globals during require. + # * `globals` An optional {Object} to set as globals during require. requireWithGlobals: (id, globals={}) -> existingGlobals = {} for key, value of globals diff --git a/src/git-repository.coffee b/src/git-repository.coffee index 2ffb4e033..d523b562f 100644 --- a/src/git-repository.coffee +++ b/src/git-repository.coffee @@ -59,7 +59,7 @@ class GitRepository # Public: Creates a new GitRepository instance. # # * `path` The {String} path to the Git repository to open. - # * `options` An optinal {Object} with the following keys: + # * `options` An optional {Object} with the following keys: # * `refreshOnWindowFocus` A {Boolean}, `true` to refresh the index and # statuses when the window is focused. # From 84ad7c377ba4ade8f4c1208734545d72af780ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C5=BDu=C5=BEak?= Date: Sun, 15 Feb 2015 16:42:00 +0100 Subject: [PATCH 022/126] :arrow_up: background-tips@0.23.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08c968cc6..5e851bb19 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "autocomplete": "0.44.0", "autoflow": "0.22.0", "autosave": "0.20.0", - "background-tips": "0.22.0", + "background-tips": "0.23.0", "bookmarks": "0.35.0", "bracket-matcher": "0.71.0", "command-palette": "0.34.0", From ada4d5a036448603306d172badd15fb36705bb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C5=BDu=C5=BEak?= Date: Sun, 15 Feb 2015 16:46:13 +0100 Subject: [PATCH 023/126] :arrow_up: keybinding-resolver@0.29.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e851bb19..ebe870941 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "grammar-selector": "0.45.0", "image-view": "0.49.0", "incompatible-packages": "0.22.0", - "keybinding-resolver": "0.28.0", + "keybinding-resolver": "0.29.0", "link": "0.30.0", "markdown-preview": "0.137.0", "metrics": "0.43.0", From 5489d7d3514214d8b36580cf0c6bfa4ec1bc76b9 Mon Sep 17 00:00:00 2001 From: postcasio Date: Mon, 16 Feb 2015 16:33:30 +0000 Subject: [PATCH 024/126] Use double colons for "before" and "after" pseudo-elements --- static/icons.less | 2 +- static/jasmine.less | 2 +- static/lists.less | 10 +++++----- static/select-list.less | 2 +- static/text-editor-light.less | 8 ++++---- static/text-editor-shadow.less | 8 ++++---- static/variables/octicon-mixins.less | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/static/icons.less b/static/icons.less index d42d32e79..4fe56a8c1 100644 --- a/static/icons.less +++ b/static/icons.less @@ -1,6 +1,6 @@ @import "ui-variables"; -.icon:before { +.icon::before { margin-right: @component-icon-padding; } diff --git a/static/jasmine.less b/static/jasmine.less index e0a920302..2b2659a9d 100644 --- a/static/jasmine.less +++ b/static/jasmine.less @@ -68,7 +68,7 @@ body { color: #eee; } - &:before { + &::before { content: "\02022"; } } diff --git a/static/lists.less b/static/lists.less index d1613cda9..2e325e150 100644 --- a/static/lists.less +++ b/static/lists.less @@ -25,10 +25,10 @@ white-space: nowrap; } - // The background highlight uses :before rather than the item background so + // The background highlight uses ::before rather than the item background so // it can span the entire width of the parent container rather than the size // of the list item. - .selected:before { + .selected::before { content: ''; background-color: @background-color-selected; position: absolute; @@ -42,7 +42,7 @@ position: relative; } - .icon:before { + .icon::before { margin-right: @component-icon-padding; position: relative; top: 1px; @@ -73,7 +73,7 @@ // Nested items always get disclosure arrows .list-nested-item > .list-item { .octicon(chevron-down, @disclosure-arrow-size); - &:before{ + &::before{ position: relative; top: -1px; margin-right: @component-icon-padding; @@ -81,7 +81,7 @@ } .list-nested-item.collapsed > .list-item { .octicon(chevron-right, @disclosure-arrow-size); - &:before{ + &::before{ left: 1px; } } diff --git a/static/select-list.less b/static/select-list.less index a9c43b8fc..df95d4add 100644 --- a/static/select-list.less +++ b/static/select-list.less @@ -6,7 +6,7 @@ .loading-message { .octicon(hourglass); - &:before { + &::before { font-size: 1.1em; width: 1.1em; height: 1.1em; diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 43902c97b..3ba4e8313 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -62,7 +62,7 @@ atom-text-editor { opacity: .6; padding: 0 .4em; - &:before { + &::before { text-align: center; } } @@ -84,7 +84,7 @@ atom-text-editor { visibility: visible; - &:before { + &::before { position: relative; left: -.1em; } @@ -129,7 +129,7 @@ atom-text-editor { .line { white-space: pre; - &.cursor-line .fold-marker:after { + &.cursor-line .fold-marker::after { opacity: 1; } } @@ -137,7 +137,7 @@ atom-text-editor { .fold-marker { cursor: default; - &:after { + &::after { .icon(0.8em, inline); content: @ellipsis; diff --git a/static/text-editor-shadow.less b/static/text-editor-shadow.less index 62be81739..63d27de7f 100644 --- a/static/text-editor-shadow.less +++ b/static/text-editor-shadow.less @@ -44,7 +44,7 @@ opacity: .6; padding: 0 .4em; - &:before { + &::before { text-align: center; } } @@ -66,7 +66,7 @@ visibility: visible; - &:before { + &::before { position: relative; left: -.1em; } @@ -111,7 +111,7 @@ .line { white-space: pre; - &.cursor-line .fold-marker:after { + &.cursor-line .fold-marker::after { opacity: 1; } } @@ -119,7 +119,7 @@ .fold-marker { cursor: default; - &:after { + &::after { .icon(0.8em, inline); content: @ellipsis; diff --git a/static/variables/octicon-mixins.less b/static/variables/octicon-mixins.less index e1fbe3e5f..0cb614814 100644 --- a/static/variables/octicon-mixins.less +++ b/static/variables/octicon-mixins.less @@ -18,7 +18,7 @@ .octicon(@name, @size: 16px) { @import "octicon-utf-codes.less"; - &:before { + &::before { .icon(@size); content: @@name } @@ -26,7 +26,7 @@ .mega-octicon(@name, @size: 32px) { @import "octicon-utf-codes.less"; - &:before { + &::before { .icon(@size); content: @@name } From 38e3ebd3b20d4bbbe177089fc59756fe311dd6fe Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 08:51:45 -0800 Subject: [PATCH 025/126] :arrow_up: language-javascript@0.57 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ebe870941..a07b4275d 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "language-html": "0.29.0", "language-hyperlink": "0.12.2", "language-java": "0.14.0", - "language-javascript": "0.56.0", + "language-javascript": "0.57.0", "language-json": "0.12.0", "language-less": "0.24.0", "language-make": "0.13.0", From 2764f8a5a512d57dcc97ab8385777d24d89405f6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 09:54:12 -0800 Subject: [PATCH 026/126] :arrow_up: apm@0.140 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 26e952077..e491d584e 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.139.0" + "atom-package-manager": "0.140.0" } } From 39ba0307384488c7a6201241c77113ba62883516 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 10:07:31 -0800 Subject: [PATCH 027/126] :arrow_up: tree-view@0.156 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a07b4275d..bc8a8ef00 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "symbols-view": "0.83.0", "tabs": "0.67.0", "timecop": "0.30.0", - "tree-view": "0.155.0", + "tree-view": "0.156.0", "update-package-dependencies": "0.8.0", "welcome": "0.21.0", "whitespace": "0.29.0", From 222db8af2156c480d1525dab6edfb234533916f0 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 17 Feb 2015 10:09:57 -0800 Subject: [PATCH 028/126] `./script/grunt lint` and `temp.mkdirSync('atom-project')` instead of `'tmp'`. --- spec/project-spec.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index ed4cd8c2f..a1e503751 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -15,12 +15,13 @@ describe "Project", -> describe "constructor", -> it "tries to update repositories when a new RepositoryProvider is registered", -> - atom.project.setPaths(["/tmp"]) + tmp = temp.mkdirSync('atom-project') + atom.project.setPaths([tmp]) expect(atom.project.getRepositories()).toEqual [null] expect(atom.project.repositoryProviders.length).toEqual 1 # Register a new RepositoryProvider. - dummyRepository = destroy: () -> + dummyRepository = destroy: -> repositoryProvider = repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository) repositoryForDirectorySync: (directory) -> dummyRepository @@ -37,7 +38,7 @@ describe "Project", -> expect(repository).toBeTruthy() # Register a new RepositoryProvider. - dummyRepository = destroy: () -> + dummyRepository = destroy: -> repositoryProvider = repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository) repositoryForDirectorySync: (directory) -> dummyRepository From 3c13b37a18e03c1a85187564b9d4559708fa86fe Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 11:23:41 -0800 Subject: [PATCH 029/126] :arrow_up: settings-view@0.183 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc8a8ef00..2e5c26fac 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "open-on-github": "0.32.0", "package-generator": "0.38.0", "release-notes": "0.50.0", - "settings-view": "0.182.0", + "settings-view": "0.183.0", "snippets": "0.74.0", "spell-check": "0.54.0", "status-bar": "0.60.0", From 9b20239b9afa22425e283764bf9b13379a4fba7f Mon Sep 17 00:00:00 2001 From: a-moses Date: Wed, 18 Feb 2015 00:51:47 +0200 Subject: [PATCH 030/126] add comtable to debian testing and ubuntu 14.04 * libgcrypt11-dev on debian is a dammy file "shortcut" to libgcrypt20 . so to make possible to install atom on debian testing version we should add on depends ** libgcrypt20. libgcrypt20 has backward compatibility to libgcrypt11 . ** libgcrypt20 is comfortable with debian unstable/testing , and ubuntu 14.04TLS . * libgcrypt11 (without -dev) not exists on debian testing and makeing bugs with atom deb file . --- resources/linux/debian/control.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/linux/debian/control.in b/resources/linux/debian/control.in index 13442a882..1578f544b 100644 --- a/resources/linux/debian/control.in +++ b/resources/linux/debian/control.in @@ -1,6 +1,6 @@ Package: <%= name %> Version: <%= version %> -Depends: git, gconf2, gconf-service, libgtk2.0-0, libudev0 | libudev1, libgcrypt11, libnotify4, libxtst6, libnss3, python, gvfs-bin, xdg-utils +Depends: git, gconf2, gconf-service, libgtk2.0-0, libudev0 | libudev1, libgcrypt11 | libgcrypt20, libnotify4, libxtst6, libnss3, python, gvfs-bin, xdg-utils Suggests: libgnome-keyring0, gir1.2-gnomekeyring-1.0 Section: <%= section %> Priority: optional From 31c6b6fc9468144b1128453dbd494e666ab7dce0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 15:25:32 -0800 Subject: [PATCH 031/126] :arrow_up: tree-view@0.157 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e5c26fac..005932608 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "symbols-view": "0.83.0", "tabs": "0.67.0", "timecop": "0.30.0", - "tree-view": "0.156.0", + "tree-view": "0.157.0", "update-package-dependencies": "0.8.0", "welcome": "0.21.0", "whitespace": "0.29.0", From bcfa4ef6082eaf597278fff3386ecaab6ce8bfac Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Feb 2015 15:25:50 -0800 Subject: [PATCH 032/126] Restore old behavior for multiple path CLI args Signed-off-by: Nathan Sobo --- spec/integration/startup-spec.coffee | 23 ++++++++++++++++++++++- src/browser/atom-application.coffee | 8 ++++++-- src/browser/atom-window.coffee | 1 + src/browser/main.coffee | 5 ++++- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 4cc5b1525..ae0001814 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -86,7 +86,7 @@ describe "Starting Atom", -> .waitForPaneItemCount(1, 5000) it "allows multiple project directories to be passed as separate arguments", -> - runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) -> + runAtom [tempDirPath, otherTempDirPath, "--multi-folder"], {ATOM_HOME: AtomHome}, (client) -> client .waitForExist("atom-workspace", 5000) .then((exists) -> expect(exists).toBe true) @@ -100,3 +100,24 @@ describe "Starting Atom", -> .waitForPaneItemCount(1, 5000) .execute(-> atom.project.getPaths()) .then(({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath])) + + it "opens each path in its own window unless the --multi-folder flag is passed", -> + runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) -> + projectPaths = [] + + client + .waitForWindowCount(2, 5000) + .windowHandles() + .then ({value: windowHandles}) -> + @window(windowHandles[0]) + .execute(-> atom.project.getPaths()) + .then ({value}) -> + expect(value).toHaveLength(1) + projectPaths.push(value[0]) + .window(windowHandles[1]) + .execute(-> atom.project.getPaths()) + .then ({value}) -> + expect(value).toHaveLength(1) + projectPaths.push(value[0]) + .then -> + expect(projectPaths.sort()).toEqual([tempDirPath, otherTempDirPath].sort()) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 4cc25f551..44e43bdd7 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -82,11 +82,15 @@ class AtomApplication @openWithOptions(options) # Opens a new window based on the options provided. - openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile}) -> + openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, enableMultiFolderProject}) -> if test @runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile}) else if pathsToOpen.length > 0 - @openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode}) + if enableMultiFolderProject + @openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode}) + else + for pathToOpen in pathsToOpen + @openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode}) else if urlsToOpen.length > 0 @openUrl({urlToOpen, devMode, safeMode}) for urlToOpen in urlsToOpen else diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index bf43b0241..19c74d943 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -58,6 +58,7 @@ class AtomWindow else pathToOpen loadSettings.initialPaths.sort() + @projectPaths = loadSettings.initialPaths @browserWindow.loadSettings = loadSettings @browserWindow.once 'window:loaded', => diff --git a/src/browser/main.coffee b/src/browser/main.coffee index 3b6018d5b..b73556307 100644 --- a/src/browser/main.coffee +++ b/src/browser/main.coffee @@ -118,6 +118,7 @@ parseCommandLine = -> options.alias('v', 'version').boolean('v').describe('v', 'Print the version.') options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.') options.string('socket-path') + options.boolean('multi-folder') args = options.argv if args.help @@ -139,6 +140,7 @@ parseCommandLine = -> pidToKillWhenClosed = args['pid'] if args['wait'] logFile = args['log-file'] socketPath = args['socket-path'] + enableMultiFolderProject = args['multi-folder'] if args['resource-path'] devMode = true @@ -163,6 +165,7 @@ parseCommandLine = -> # explicitly pass it by command line, see http://git.io/YC8_Ew. process.env.PATH = args['path-environment'] if args['path-environment'] - {resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, socketPath} + {resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, + devMode, safeMode, newWindow, specDirectory, logFile, socketPath, enableMultiFolderProject} start() From 7fcec6e3c3ac3f153ea5b10e5892a48fd6ea46a7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Feb 2015 15:59:49 -0800 Subject: [PATCH 033/126] Restore old behavior of atom.open --- src/browser/atom-application.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 44e43bdd7..a3326690b 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -60,7 +60,7 @@ class AtomApplication exit: (status) -> app.exit(status) constructor: (options) -> - {@resourcePath, @version, @devMode, @safeMode, @socketPath} = options + {@resourcePath, @version, @devMode, @safeMode, @socketPath, @enableMultiFolderProject} = options # Normalize to make sure drive letter case is consistent on Windows @resourcePath = path.normalize(@resourcePath) if @resourcePath @@ -82,15 +82,11 @@ class AtomApplication @openWithOptions(options) # Opens a new window based on the options provided. - openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, enableMultiFolderProject}) -> + openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile}) -> if test @runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile}) else if pathsToOpen.length > 0 - if enableMultiFolderProject - @openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode}) - else - for pathToOpen in pathsToOpen - @openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode}) + @openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode}) else if urlsToOpen.length > 0 @openUrl({urlToOpen, devMode, safeMode}) for urlToOpen in urlsToOpen else @@ -352,6 +348,11 @@ class AtomApplication # :windowDimensions - Object with height and width keys. # :window - {AtomWindow} to open file paths in. openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) -> + if pathsToOpen?.length > 1 and not @enableMultiFolderProject + for pathToOpen in pathsToOpen + @openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}) + return + pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen) locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen) From b290f0d200cb52f2e2dd1cb9a6d3298ac3d7b4b7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 16:42:08 -0800 Subject: [PATCH 034/126] Prepare 0.181 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 005932608..352c1693c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "0.180.0", + "version": "0.181.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 983027b27f4f26e75eff04e599257c780574ef5f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 16:50:03 -0800 Subject: [PATCH 035/126] :racehorse: inline vm compatibility patch --- package.json | 3 +-- static/index.js | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 352c1693c..4fe7e960b 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,7 @@ "temp": "0.8.1", "text-buffer": "^4.1.5", "theorist": "^1.0.2", - "underscore-plus": "^1.6.6", - "vm-compatibility-layer": "0.1.0" + "underscore-plus": "^1.6.6" }, "packageDependencies": { "atom-dark-syntax": "0.26.0", diff --git a/static/index.js b/static/index.js index fb56a84bd..bd9fbe9a0 100644 --- a/static/index.js +++ b/static/index.js @@ -44,8 +44,7 @@ window.onload = function() { extra: {_version: loadSettings.appVersion} }); - require('vm-compatibility-layer'); - + setupVmCompatibility(); setupCsonCache(cacheDir); setupSourceMapCache(cacheDir); setup6to5(cacheDir); @@ -104,3 +103,9 @@ var setupCsonCache = function(cacheDir) { var setupSourceMapCache = function(cacheDir) { require('coffeestack').setCacheDirectory(path.join(cacheDir, 'coffee', 'source-maps')); } + +var setupVmCompatibility = function() { + var vm = require('vm'); + if (!vm.Script.createContext) + vm.Script.createContext = vm.createContext; +} From 670e61cf0975ad17d29684aff74488c27801e959 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 17 Feb 2015 17:01:35 -0800 Subject: [PATCH 036/126] :arrow_up: less-cache@0.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4fe7e960b..f3f6902bb 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", "jquery": "^2.1.1", - "less-cache": "0.21", + "less-cache": "0.22", "marked": "^0.3", "mixto": "^1", "nslog": "^2.0.0", From 52506a0df2325b89018c77976c74822d81616ce9 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Wed, 18 Feb 2015 12:23:58 +1100 Subject: [PATCH 037/126] Rename 6to5 to Babel --- package.json | 2 +- spec/{6to5-spec.coffee => babel-spec.coffee} | 30 ++++++++++++------- spec/compile-cache-spec.coffee | 6 ++-- .../6to5-double-quotes.js} | 0 .../6to5-single-quotes.js} | 0 spec/fixtures/babel/babel-double-quotes.js | 3 ++ spec/fixtures/babel/babel-single-quotes.js | 3 ++ spec/fixtures/{6to5 => babel}/invalid.js | 0 src/{6to5.coffee => babel.coffee} | 26 ++++++++-------- src/compile-cache.coffee | 6 ++-- static/index.js | 10 +++---- 11 files changed, 51 insertions(+), 35 deletions(-) rename spec/{6to5-spec.coffee => babel-spec.coffee} (51%) rename spec/fixtures/{6to5/double-quotes.js => babel/6to5-double-quotes.js} (100%) rename spec/fixtures/{6to5/single-quotes.js => babel/6to5-single-quotes.js} (100%) create mode 100644 spec/fixtures/babel/babel-double-quotes.js create mode 100644 spec/fixtures/babel/babel-single-quotes.js rename spec/fixtures/{6to5 => babel}/invalid.js (100%) rename src/{6to5.coffee => babel.coffee} (85%) diff --git a/package.json b/package.json index ebe870941..0b4c6b814 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ ], "atomShellVersion": "0.21.0", "dependencies": { - "6to5-core": "^3.6.0", "async": "0.2.6", "atom-keymap": "^3.1.2", "atom-space-pen-views": "^2.0.4", + "babel-core": "^4.0.2", "bootstrap": "git+https://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", "clear-cut": "0.4.0", "coffee-cash": "0.7.0", diff --git a/spec/6to5-spec.coffee b/spec/babel-spec.coffee similarity index 51% rename from spec/6to5-spec.coffee rename to spec/babel-spec.coffee index 2cb8d1ba6..f9abac930 100644 --- a/spec/6to5-spec.coffee +++ b/spec/babel-spec.coffee @@ -1,8 +1,8 @@ -to5 = require '../src/6to5' +babel = require '../src/babel' crypto = require 'crypto' -describe "6to5 transpiler support", -> - describe "::create6to5VersionAndOptionsDigest", -> +describe "Babel transpiler support", -> + describe "::createBabelVersionAndOptionsDigest", -> it "returns a digest for the library version and specified options", -> defaultOptions = blacklist: [ @@ -16,26 +16,36 @@ describe "6to5 transpiler support", -> sourceMap: 'inline' version = '3.0.14' shasum = crypto.createHash('sha1') - shasum.update('6to5-core', 'utf8') + shasum.update('babel-core', 'utf8') shasum.update('\0', 'utf8') shasum.update(version, 'utf8') shasum.update('\0', 'utf8') shasum.update('{"blacklist": ["useStrict",],"experimental": true,"optional": ["asyncToGenerator",],"reactCompat": true,"sourceMap": "inline",}') expectedDigest = shasum.digest('hex') - observedDigest = to5.create6to5VersionAndOptionsDigest(version, defaultOptions) + observedDigest = babel.createBabelVersionAndOptionsDigest(version, defaultOptions) expect(observedDigest).toEqual expectedDigest + describe "when a .js file starts with 'use babel';", -> + it "transpiles it using babel", -> + transpiled = require('./fixtures/babel/babel-single-quotes.js') + expect(transpiled(3)).toBe 4 + describe "when a .js file starts with 'use 6to5';", -> it "transpiles it using 6to5", -> - transpiled = require('./fixtures/6to5/single-quotes.js') + transpiled = require('./fixtures/babel/6to5-single-quotes.js') + expect(transpiled(3)).toBe 4 + + describe 'when a .js file starts with "use babel";', -> + it "transpiles it using babel", -> + transpiled = require('./fixtures/babel/babel-double-quotes.js') expect(transpiled(3)).toBe 4 describe 'when a .js file starts with "use 6to5";', -> - it "transpiles it using 6to5", -> - transpiled = require('./fixtures/6to5/double-quotes.js') + it "transpiles it using babel", -> + transpiled = require('./fixtures/babel/6to5-double-quotes.js') expect(transpiled(3)).toBe 4 describe "when a .js file does not start with 'use 6to6';", -> - it "does not transpile it using 6to5", -> - expect(-> require('./fixtures/6to5/invalid.js')).toThrow() + it "does not transpile it using babel", -> + expect(-> require('./fixtures/babel/invalid.js')).toThrow() diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee index 0cae70434..c01948f73 100644 --- a/spec/compile-cache-spec.coffee +++ b/spec/compile-cache-spec.coffee @@ -2,12 +2,12 @@ path = require 'path' CSON = require 'season' CoffeeCache = require 'coffee-cash' -to5 = require '../src/6to5' +babel = require '../src/babel' CompileCache = require '../src/compile-cache' describe "Compile Cache", -> describe ".addPathToCache(filePath)", -> - it "adds the path to the correct CSON, CoffeeScript, or 6to5 cache", -> + it "adds the path to the correct CSON, CoffeeScript, or babel cache", -> spyOn(CSON, 'readFileSync').andCallThrough() spyOn(CoffeeCache, 'addPathToCache').andCallThrough() spyOn(to5, 'addPathToCache').andCallThrough() @@ -22,7 +22,7 @@ describe "Compile Cache", -> expect(CoffeeCache.addPathToCache.callCount).toBe 1 expect(to5.addPathToCache.callCount).toBe 0 - CompileCache.addPathToCache(path.join(__dirname, 'fixtures', '6to5', 'double-quotes.js')) + CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'babel', 'double-quotes.js')) expect(CSON.readFileSync.callCount).toBe 1 expect(CoffeeCache.addPathToCache.callCount).toBe 1 expect(to5.addPathToCache.callCount).toBe 1 diff --git a/spec/fixtures/6to5/double-quotes.js b/spec/fixtures/babel/6to5-double-quotes.js similarity index 100% rename from spec/fixtures/6to5/double-quotes.js rename to spec/fixtures/babel/6to5-double-quotes.js diff --git a/spec/fixtures/6to5/single-quotes.js b/spec/fixtures/babel/6to5-single-quotes.js similarity index 100% rename from spec/fixtures/6to5/single-quotes.js rename to spec/fixtures/babel/6to5-single-quotes.js diff --git a/spec/fixtures/babel/babel-double-quotes.js b/spec/fixtures/babel/babel-double-quotes.js new file mode 100644 index 000000000..7d8ebfad7 --- /dev/null +++ b/spec/fixtures/babel/babel-double-quotes.js @@ -0,0 +1,3 @@ +"use babel"; + +module.exports = v => v + 1 diff --git a/spec/fixtures/babel/babel-single-quotes.js b/spec/fixtures/babel/babel-single-quotes.js new file mode 100644 index 000000000..980fe51be --- /dev/null +++ b/spec/fixtures/babel/babel-single-quotes.js @@ -0,0 +1,3 @@ +'use babel'; + +module.exports = v => v + 1 diff --git a/spec/fixtures/6to5/invalid.js b/spec/fixtures/babel/invalid.js similarity index 100% rename from spec/fixtures/6to5/invalid.js rename to spec/fixtures/babel/invalid.js diff --git a/src/6to5.coffee b/src/babel.coffee similarity index 85% rename from src/6to5.coffee rename to src/babel.coffee index 57dd6babd..98b6de07e 100644 --- a/src/6to5.coffee +++ b/src/babel.coffee @@ -1,5 +1,5 @@ ### -Cache for source code transpiled by 6to5. +Cache for source code transpiled by Babel. Inspired by https://github.com/atom/atom/blob/6b963a562f8d495fbebe6abdbafbc7caf705f2c3/src/coffee-cache.coffee. ### @@ -7,7 +7,7 @@ Inspired by https://github.com/atom/atom/blob/6b963a562f8d495fbebe6abdbafbc7caf7 crypto = require 'crypto' fs = require 'fs-plus' path = require 'path' -to5 = null # Defer until used +babel = null # Defer until used stats = hits: 0 @@ -28,7 +28,7 @@ defaultOptions = ] # Includes support for es7 features listed at: - # http://6to5.org/docs/usage/transformers/#es7-experimental-. + # http://babeljs.io/docs/usage/transformers/#es7-experimental-. experimental: true optional: [ @@ -79,10 +79,10 @@ updateDigestForJsonValue = (shasum, value) -> shasum.update(',', 'utf8') shasum.update('}', 'utf8') -create6to5VersionAndOptionsDigest = (version, options) -> +createBabelVersionAndOptionsDigest = (version, options) -> shasum = crypto.createHash('sha1') - # Include the version of 6to5 in the hash. - shasum.update('6to5-core', 'utf8') + # Include the version of babel in the hash. + shasum.update('babel-core', 'utf8') shasum.update('\0', 'utf8') shasum.update(version, 'utf8') shasum.update('\0', 'utf8') @@ -96,8 +96,8 @@ getCachePath = (sourceCode) -> digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex') unless jsCacheDir? - to5Version = require('6to5-core/package.json').version - jsCacheDir = path.join(cacheDir, create6to5VersionAndOptionsDigest(to5Version, defaultOptions)) + to5Version = require('babel-core/package.json').version + jsCacheDir = path.join(cacheDir, createBabelVersionAndOptionsDigest(to5Version, defaultOptions)) path.join(jsCacheDir, "#{digest}.js") @@ -109,7 +109,7 @@ getCachedJavaScript = (cachePath) -> return cachedJavaScript null -# Returns the 6to5 options that should be used to transpile filePath. +# Returns the babel options that should be used to transpile filePath. createOptions = (filePath) -> options = filename: filePath for key, value of defaultOptions @@ -118,8 +118,8 @@ createOptions = (filePath) -> transpile = (sourceCode, filePath, cachePath) -> options = createOptions(filePath) - to5 ?= require '6to5-core' - js = to5.transform(sourceCode, options).code + babel ?= require 'babel-core' + js = babel.transform(sourceCode, options).code stats.misses++ try @@ -132,7 +132,7 @@ transpile = (sourceCode, filePath, cachePath) -> # either generated on the fly or pulled from cache. loadFile = (module, filePath) -> sourceCode = fs.readFileSync(filePath, 'utf8') - unless sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'") + unless /^("use 6to5"|'use 6to5'|"use babel"|'use babel')/.test(sourceCode) return module._compile(sourceCode, filePath) cachePath = getCachePath(sourceCode) @@ -158,7 +158,7 @@ module.exports = getCacheHits: -> stats.hits # Visible for testing. - create6to5VersionAndOptionsDigest: create6to5VersionAndOptionsDigest + createBabelVersionAndOptionsDigest: createBabelVersionAndOptionsDigest addPathToCache: (filePath) -> return if path.extname(filePath) isnt '.js' diff --git a/src/compile-cache.coffee b/src/compile-cache.coffee index 287916d94..c31f5bdd1 100644 --- a/src/compile-cache.coffee +++ b/src/compile-cache.coffee @@ -1,7 +1,7 @@ path = require 'path' CSON = require 'season' CoffeeCache = require 'coffee-cash' -to5 = require './6to5' +babel = require './babel' # This file is required directly by apm so that files can be cached during # package install so that the first package load in Atom doesn't have to @@ -15,7 +15,7 @@ exports.addPathToCache = (filePath, atomHome) -> CoffeeCache.setCacheDirectory(path.join(cacheDir, 'coffee')) CSON.setCacheDir(path.join(cacheDir, 'cson')) - to5.setCacheDirectory(path.join(cacheDir, 'js', '6to5')) + babel.setCacheDirectory(path.join(cacheDir, 'js', 'babel')) switch path.extname(filePath) when '.coffee' @@ -23,4 +23,4 @@ exports.addPathToCache = (filePath, atomHome) -> when '.cson' CSON.readFileSync(filePath) when '.js' - to5.addPathToCache(filePath) + babel.addPathToCache(filePath) diff --git a/static/index.js b/static/index.js index fb56a84bd..8164540d8 100644 --- a/static/index.js +++ b/static/index.js @@ -48,7 +48,7 @@ window.onload = function() { setupCsonCache(cacheDir); setupSourceMapCache(cacheDir); - setup6to5(cacheDir); + setupBabel(cacheDir); require(loadSettings.bootstrapScript); require('ipc').sendChannel('window-command', 'window:loaded'); @@ -91,10 +91,10 @@ var setupAtomHome = function() { } } -var setup6to5 = function(cacheDir) { - var to5 = require('../src/6to5'); - to5.setCacheDirectory(path.join(cacheDir, 'js', '6to5')); - to5.register(); +var setupBabel = function(cacheDir) { + var babel = require('../src/babel'); + babel.setCacheDirectory(path.join(cacheDir, 'js', 'babel')); + babel.register(); } var setupCsonCache = function(cacheDir) { From 31bca7ea930eab9c708c111fea7d0d2bf17b67a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C5=BDu=C5=BEak?= Date: Wed, 18 Feb 2015 17:23:23 +0100 Subject: [PATCH 038/126] Remove markdown:guides task which doesn't exist anymore --- build/Gruntfile.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index be093d93e..05efaeb38 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -220,7 +220,6 @@ module.exports = (grunt) -> grunt.registerTask('compile', ['coffee', 'prebuild-less', 'cson', 'peg']) grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint']) grunt.registerTask('test', ['shell:kill-atom', 'run-specs']) - grunt.registerTask('docs', ['markdown:guides', 'build-docs']) ciTasks = ['output-disk-space', 'download-atom-shell', 'download-atom-shell-chromedriver', 'build'] ciTasks.push('dump-symbols') if process.platform isnt 'win32' From 82eb1569328addf63b9026e4e485674ac65b0014 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 09:05:25 -0800 Subject: [PATCH 039/126] :arrow_up: language-c@0.40 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3f6902bb..d5950f4ba 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "welcome": "0.21.0", "whitespace": "0.29.0", "wrap-guide": "0.31.0", - "language-c": "0.39.0", + "language-c": "0.40.0", "language-clojure": "0.12.0", "language-coffee-script": "0.39.0", "language-csharp": "0.5.0", From ec9a1f1f84fad58230b61729b58057436cf9b281 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 09:06:53 -0800 Subject: [PATCH 040/126] :arrow_up: release-notes@0.51 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5950f4ba..5ea126e4a 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "notifications": "0.27.0", "open-on-github": "0.32.0", "package-generator": "0.38.0", - "release-notes": "0.50.0", + "release-notes": "0.51.0", "settings-view": "0.183.0", "snippets": "0.74.0", "spell-check": "0.54.0", From 63af713a3ff3c4d278ffb57022cd986145750f2b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 09:11:48 -0800 Subject: [PATCH 041/126] Guard against detected repository that does not open Closes #5609 --- spec/git-repository-provider-spec.coffee | 18 ++++++++++++++++-- src/git-repository-provider.coffee | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/spec/git-repository-provider-spec.coffee b/spec/git-repository-provider-spec.coffee index 2082bd50f..7d77adc36 100644 --- a/spec/git-repository-provider-spec.coffee +++ b/spec/git-repository-provider-spec.coffee @@ -1,11 +1,12 @@ path = require 'path' +fs = require 'fs-plus' +temp = require 'temp' {Directory} = require 'pathwatcher' GitRepository = require '../src/git-repository' GitRepositoryProvider = require '../src/git-repository-provider' describe "GitRepositoryProvider", -> describe ".repositoryForDirectory(directory)", -> - describe "when specified a Directory with a Git repository", -> it "returns a Promise that resolves to a GitRepository", -> waitsForPromise -> @@ -37,6 +38,19 @@ describe "GitRepositoryProvider", -> it "returns a Promise that resolves to null", -> waitsForPromise -> provider = new GitRepositoryProvider atom.project - directory = new Directory '/tmp' + directory = new Directory temp.mkdirSync('dir') + provider.repositoryForDirectory(directory).then (result) -> + expect(result).toBe null + + describe "when specified a Directory with an invalid Git repository", -> + it "returns a Promise that resolves to null", -> + waitsForPromise -> + provider = new GitRepositoryProvider atom.project + dirPath = temp.mkdirSync('dir') + fs.writeFileSync(path.join(dirPath, '.git', 'objects'), '') + fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), '') + fs.writeFileSync(path.join(dirPath, '.git', 'refs'), '') + + directory = new Directory dirPath provider.repositoryForDirectory(directory).then (result) -> expect(result).toBe null diff --git a/src/git-repository-provider.coffee b/src/git-repository-provider.coffee index dd9c41f41..210197599 100644 --- a/src/git-repository-provider.coffee +++ b/src/git-repository-provider.coffee @@ -62,6 +62,7 @@ class GitRepositoryProvider repo = @pathToRepository[gitDirPath] unless repo repo = GitRepository.open(gitDirPath, project: @project) + return null unless repo repo.onDidDestroy(=> delete @pathToRepository[gitDirPath]) @pathToRepository[gitDirPath] = repo repo.refreshIndex() From 9bf0300a560af78cddcdf5fe3c8f34781584443a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 09:29:54 -0800 Subject: [PATCH 042/126] :arrow_up: notifications@0.28 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ea126e4a..fb3ca001e 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "link": "0.30.0", "markdown-preview": "0.137.0", "metrics": "0.43.0", - "notifications": "0.27.0", + "notifications": "0.28.0", "open-on-github": "0.32.0", "package-generator": "0.38.0", "release-notes": "0.51.0", From 2518c40942f01cb3d81eac1e25939c56f6e2e6e4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 09:34:31 -0800 Subject: [PATCH 043/126] :arrow_up: snippets@0.75 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fb3ca001e..95cf26dc9 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "package-generator": "0.38.0", "release-notes": "0.51.0", "settings-view": "0.183.0", - "snippets": "0.74.0", + "snippets": "0.75.0", "spell-check": "0.54.0", "status-bar": "0.60.0", "styleguide": "0.44.0", From 39a225821ecc05af143c9e97d209e877e7d2a30f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Feb 2015 10:29:48 -0800 Subject: [PATCH 044/126] Reuse current window if it has no project path Fixes #5615 --- spec/integration/startup-spec.coffee | 10 ++++++++++ src/browser/atom-application.coffee | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index ae0001814..8e2e5d981 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -121,3 +121,13 @@ describe "Starting Atom", -> projectPaths.push(value[0]) .then -> expect(projectPaths.sort()).toEqual([tempDirPath, otherTempDirPath].sort()) + + it "opens the path in the current window if it doesn't have a project path yet", -> + runAtom [], {ATOM_HOME: AtomHome}, (client) -> + client + .waitForExist("atom-workspace") + .startAnotherAtom([tempDirPath], ATOM_HOME: AtomHome) + .waitUntil((-> + @title() + .then(({value}) -> value.indexOf(path.basename(tempDirPath)) >= 0)), 5000) + .waitForWindowCount(1, 5000) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index a3326690b..32e8ca293 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -360,8 +360,13 @@ class AtomApplication existingWindow = @windowForPaths(pathsToOpen, devMode) # Default to using the specified window or the last focused window + currentWindow = window ? @lastFocusedWindow + if pathsToOpen.every((pathToOpen) -> fs.statSyncNoException(pathToOpen).isFile?()) - existingWindow ?= window ? @lastFocusedWindow + existingWindow ?= currentWindow + + unless currentWindow?.projectPaths?.length > 0 + existingWindow ?= currentWindow if existingWindow? openedWindow = existingWindow From 2bd897cbf80f3f6e80cd0b7cd529e916110c8196 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 18 Feb 2015 10:54:53 -0800 Subject: [PATCH 045/126] :arrow_up: metrics@0.44.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95cf26dc9..3b3728bdb 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "keybinding-resolver": "0.29.0", "link": "0.30.0", "markdown-preview": "0.137.0", - "metrics": "0.43.0", + "metrics": "0.44.0", "notifications": "0.28.0", "open-on-github": "0.32.0", "package-generator": "0.38.0", From fc5e3e58f9c7cea0d212aaaa39f61dc4fc97bb2c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 18 Feb 2015 10:55:23 -0800 Subject: [PATCH 046/126] :arrow_up: welcome@0.22.0 :tada: --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b3728bdb..26fb32270 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "timecop": "0.30.0", "tree-view": "0.157.0", "update-package-dependencies": "0.8.0", - "welcome": "0.21.0", + "welcome": "0.22.0", "whitespace": "0.29.0", "wrap-guide": "0.31.0", "language-c": "0.40.0", From 83b27fc93e55522187cb4464830d6ac13f7e80a3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 11:06:58 -0800 Subject: [PATCH 047/126] :arrow_up: Squirrel for Windows 0.8.5 --- build/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/package.json b/build/package.json index 4bfc940a2..f7784e956 100644 --- a/build/package.json +++ b/build/package.json @@ -12,7 +12,7 @@ "fs-plus": "2.x", "github-releases": "~0.2.0", "grunt": "~0.4.1", - "grunt-atom-shell-installer": "^0.21.0", + "grunt-atom-shell-installer": "^0.23.0", "grunt-cli": "~0.1.9", "grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git#cfb99aa99811d52687969532bd5a98011ed95bfe", "grunt-contrib-coffee": "~0.12.0", From 0eb742566fc02ac3b28a0b77ed62a3fbf38b526e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Feb 2015 11:17:47 -0800 Subject: [PATCH 048/126] Always open w/ one untitled buffer when no paths are given Fixes #5607 --- spec/integration/startup-spec.coffee | 11 +++++++++++ src/atom.coffee | 2 +- src/browser/atom-window.coffee | 19 +++++++++++-------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 8e2e5d981..c2b1b960c 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -131,3 +131,14 @@ describe "Starting Atom", -> @title() .then(({value}) -> value.indexOf(path.basename(tempDirPath)) >= 0)), 5000) .waitForWindowCount(1, 5000) + + it "always opens with a single untitled buffer when launched w/ no path", -> + runAtom [], {ATOM_HOME: AtomHome}, (client) -> + client + .waitForExist("atom-workspace") + .waitForPaneItemCount(1, 5000) + + runAtom [], {ATOM_HOME: AtomHome}, (client) -> + client + .waitForExist("atom-workspace") + .waitForPaneItemCount(1, 5000) diff --git a/src/atom.coffee b/src/atom.coffee index 723d235e5..385180dfd 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -96,7 +96,7 @@ class Atom extends Model filename = 'spec' when 'editor' {initialPaths} = @getLoadSettings() - if initialPaths + if initialPaths?.length > 0 sha1 = crypto.createHash('sha1').update(initialPaths.join("\n")).digest('hex') filename = "editor-#{sha1}" diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index 19c74d943..77888fed4 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -18,8 +18,9 @@ class AtomWindow isSpec: null constructor: (settings={}) -> - {@resourcePath, pathToOpen, @locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings - @locationsToOpen ?= [{pathToOpen}] if pathToOpen + {@resourcePath, pathToOpen, locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings + locationsToOpen ?= [{pathToOpen}] if pathToOpen + locationsToOpen ?= [] # Normalize to make sure drive letter case is consistent on Windows @resourcePath = path.normalize(@resourcePath) if @resourcePath @@ -52,11 +53,13 @@ class AtomWindow @constructor.includeShellLoadTime = false loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime - loadSettings.initialPaths = for {pathToOpen} in (@locationsToOpen ? []) - if fs.statSyncNoException(pathToOpen).isFile?() - path.dirname(pathToOpen) - else - pathToOpen + loadSettings.initialPaths = + for {pathToOpen} in locationsToOpen when pathToOpen + if fs.statSyncNoException(pathToOpen).isFile?() + path.dirname(pathToOpen) + else + pathToOpen + loadSettings.initialPaths.sort() @projectPaths = loadSettings.initialPaths @@ -70,7 +73,7 @@ class AtomWindow @browserWindow.loadUrl @getUrl(loadSettings) @browserWindow.focusOnWebView() if @isSpec - @openLocations(@locationsToOpen) unless @isSpecWindow() + @openLocations(locationsToOpen) unless @isSpecWindow() getUrl: (loadSettingsObj) -> # Ignore the windowState when passing loadSettings via URL, since it could From 55b8afdbe5498616067329822fdf2d8925cd6db2 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 18 Feb 2015 12:05:30 -0800 Subject: [PATCH 049/126] :arrow_up: welcome@0.23.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26fb32270..cc355a425 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "timecop": "0.30.0", "tree-view": "0.157.0", "update-package-dependencies": "0.8.0", - "welcome": "0.22.0", + "welcome": "0.23.0", "whitespace": "0.29.0", "wrap-guide": "0.31.0", "language-c": "0.40.0", From 9cbe7a80a58fc0cf4500eb74b54ed2decde6a894 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 13:32:44 -0800 Subject: [PATCH 050/126] :arrow_up: welcome@0.24 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cc355a425..eb6a896f6 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "timecop": "0.30.0", "tree-view": "0.157.0", "update-package-dependencies": "0.8.0", - "welcome": "0.23.0", + "welcome": "0.24.0", "whitespace": "0.29.0", "wrap-guide": "0.31.0", "language-c": "0.40.0", From 8c3988a7901025093228cd67bec817575c446a83 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Feb 2015 13:36:53 -0800 Subject: [PATCH 051/126] Fix race when starting atom twice in spec --- spec/integration/helpers/start-atom.coffee | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee index 8597dabdd..5e8265996 100644 --- a/spec/integration/helpers/start-atom.coffee +++ b/spec/integration/helpers/start-atom.coffee @@ -89,23 +89,26 @@ buildAtomClient = (args, env) -> done() module.exports = (args, env, fn) -> - chromedriver = spawn(ChromedriverPath, [ - "--verbose", - "--port=#{ChromedriverPort}", - "--url-base=/wd/hub" - ]) + [chromedriver, chromedriverLogs, chromedriverExit] = [] - waits(50) + runs -> + chromedriver = spawn(ChromedriverPath, [ + "--verbose", + "--port=#{ChromedriverPort}", + "--url-base=/wd/hub" + ]) - chromedriverLogs = [] - chromedriverExit = new Promise (resolve) -> - errorCode = null - chromedriver.on "exit", (code, signal) -> - errorCode = code unless signal? - chromedriver.stderr.on "data", (log) -> - chromedriverLogs.push(log.toString()) - chromedriver.stderr.on "close", -> - resolve(errorCode) + chromedriverLogs = [] + chromedriverExit = new Promise (resolve) -> + errorCode = null + chromedriver.on "exit", (code, signal) -> + errorCode = code unless signal? + chromedriver.stderr.on "data", (log) -> + chromedriverLogs.push(log.toString()) + chromedriver.stderr.on "close", -> + resolve(errorCode) + + waits(100) waitsFor("webdriver to finish", (done) -> finish = once -> From 2168a59828b5d4e274c237c6163cd9de60c41dff Mon Sep 17 00:00:00 2001 From: Brendan Nee Date: Wed, 18 Feb 2015 13:45:17 -0800 Subject: [PATCH 052/126] Add "Checking for Update" to menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When state is “checking”, menu should say “Checking for Update” --- menus/darwin.cson | 1 + menus/win32.cson | 1 + src/browser/application-menu.coffee | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/menus/darwin.cson b/menus/darwin.cson index 4c221c62c..7283ba16f 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -7,6 +7,7 @@ { label: 'VERSION', enabled: false } { label: 'Restart and Install Update', command: 'application:install-update', visible: false} { label: 'Check for Update', command: 'application:check-for-update', visible: false} + { label: 'Checking for Update', enabled: false, visible: false} { label: 'Downloading Update', enabled: false, visible: false} { type: 'separator' } { label: 'Preferences...', command: 'application:show-settings' } diff --git a/menus/win32.cson b/menus/win32.cson index 0067b9128..a3e5c8b8d 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -166,6 +166,7 @@ { label: 'VERSION', enabled: false } { label: 'Restart and Install Update', command: 'application:install-update', visible: false} { label: 'Check for Update', command: 'application:check-for-update', visible: false} + { label: 'Checking for Update', enabled: false, visible: false} { label: 'Downloading Update', enabled: false, visible: false} { type: 'separator' } { label: '&Documentation', command: 'application:open-documentation' } diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index 56f7c3d8a..5218ff304 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -92,19 +92,23 @@ class ApplicationMenu # Sets the proper visible state the update menu items showUpdateMenuItem: (state) -> checkForUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Check for Update') + checkingForUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Checking for Update') downloadingUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Downloading Update') installUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Restart and Install Update') - return unless checkForUpdateItem? and downloadingUpdateItem? and installUpdateItem? + return unless checkForUpdateItem? and checkingForUpdateItem? and downloadingUpdateItem? and installUpdateItem? checkForUpdateItem.visible = false + checkingForUpdateItem.visible = false downloadingUpdateItem.visible = false installUpdateItem.visible = false switch state when 'idle', 'error', 'no-update-available' checkForUpdateItem.visible = true - when 'checking', 'downloading' + when 'checking' + checkingForUpdateItem.visible = true + when 'downloading' downloadingUpdateItem.visible = true when 'update-available' installUpdateItem.visible = true From cc2fcf8a91e356622918f1bf6047d025e574485b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 13:57:36 -0800 Subject: [PATCH 053/126] Prepare 0.182 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb6a896f6..e7d2051f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "0.181.0", + "version": "0.182.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From ef9cca26e88b455bda4665661ebc6cbfebc502da Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 15:56:23 -0800 Subject: [PATCH 054/126] Remove unneeded apm deduping --- script/bootstrap | 8 -------- 1 file changed, 8 deletions(-) diff --git a/script/bootstrap b/script/bootstrap index 0843730f8..cda896d46 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -68,14 +68,12 @@ function bootstrap() { var apmInstallOptions = {cwd: apmInstallPath}; var moduleInstallCommand = apmPath + ' install' + apmFlags; var dedupeApmCommand = apmPath + ' dedupe' + apmFlags; - var dedupeNpmCommand = npmPath + npmFlags + 'dedupe'; if (process.argv.indexOf('--no-quiet') === -1) { buildInstallCommand += ' --loglevel error'; apmInstallCommand += ' --loglevel error'; moduleInstallCommand += ' --loglevel error'; dedupeApmCommand += ' --quiet'; - dedupeNpmCommand += ' --quiet'; buildInstallOptions.ignoreStdout = true; apmInstallOptions.ignoreStdout = true; } @@ -99,12 +97,6 @@ function bootstrap() { apmPath + ' clean' + apmFlags, moduleInstallCommand, dedupeApmCommand + ' ' + packagesToDedupe.join(' '), - { - command: dedupeNpmCommand + ' request semver', - options: { - cwd: path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager') - } - }, ]; process.chdir(path.dirname(__dirname)); From b3002b43a186027d1255c86d5cf301811674ec22 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 16:01:37 -0800 Subject: [PATCH 055/126] Use node 0.12 in default nodenv config --- .node-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.node-version b/.node-version index b58b2ff99..87a1cf595 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v0.10.33 +v0.12.0 From d70e2b49128442c1b0ee72f91de4afbc393fc5c8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 16:01:45 -0800 Subject: [PATCH 056/126] Install apm with a 0.10.35 target --- script/bootstrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/bootstrap b/script/bootstrap index cda896d46..270abff7c 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -64,7 +64,7 @@ function bootstrap() { var buildInstallCommand = initialNpmCommand + npmFlags + 'install'; var buildInstallOptions = {cwd: path.resolve(__dirname, '..', 'build')}; - var apmInstallCommand = npmPath + npmFlags + 'install'; + var apmInstallCommand = npmPath + npmFlags + '--target=0.10.35 ' + 'install'; var apmInstallOptions = {cwd: apmInstallPath}; var moduleInstallCommand = apmPath + ' install' + apmFlags; var dedupeApmCommand = apmPath + ' dedupe' + apmFlags; From c049ab7e62b4efdb16586ce30dd91a2396b3d594 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 16:02:23 -0800 Subject: [PATCH 057/126] :arrow_up: npm@2.5.1 --- build/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/package.json b/build/package.json index f7784e956..ea5b8e6de 100644 --- a/build/package.json +++ b/build/package.json @@ -26,7 +26,7 @@ "harmony-collections": "~0.3.8", "legal-eagle": "~0.9.0", "minidump": "~0.8", - "npm": "~1.4.5", + "npm": "2.5.1", "rcedit": "~0.3.0", "request": "~2.27.0", "rimraf": "~2.2.2", From 65030f8252326d913fd8868953cab51a93125f64 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 18 Feb 2015 16:17:38 -0800 Subject: [PATCH 058/126] :memo: Mention node 0.12.x and io.js 1.x support --- docs/build-instructions/linux.md | 2 +- docs/build-instructions/os-x.md | 2 +- docs/build-instructions/windows.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index 6cb985e87..83815dd4a 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -7,7 +7,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. * OS with 64-bit or 32-bit architecture * C++ toolchain * [Git](http://git-scm.com/) - * [Node.js](http://nodejs.org/download/) v0.10.x + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) * [npm](https://www.npmjs.com/) 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. diff --git a/docs/build-instructions/os-x.md b/docs/build-instructions/os-x.md index b07e3bcf8..d8685cff8 100644 --- a/docs/build-instructions/os-x.md +++ b/docs/build-instructions/os-x.md @@ -3,7 +3,7 @@ ## Requirements * OS X 10.8 or later - * [node.js](http://nodejs.org/download/) v0.10.x + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) * Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install) ## Instructions diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md index 02934ee33..fddb941c7 100644 --- a/docs/build-instructions/windows.md +++ b/docs/build-instructions/windows.md @@ -5,7 +5,7 @@ ### On Windows 7 * [Visual C++ 2010 Express](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_4) * [Visual Studio 2010 Service Pack 1](http://www.microsoft.com/en-us/download/details.aspx?id=23691) - * [node.js](http://nodejs.org/download/) v0.10.x + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) * For 64-bit builds of node and native modules you **must** have the [Windows 7 64-bit SDK](http://www.microsoft.com/en-us/download/details.aspx?id=8279). You may also need the [compiler update for the Windows SDK 7.1](http://www.microsoft.com/en-us/download/details.aspx?id=4422) @@ -18,7 +18,7 @@ ### On Windows 8 * [Visual Studio Express 2013 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2) - * [node.js](http://nodejs.org/download/) v0.10.x + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) * [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp)) * [GitHub for Windows](http://windows.github.com/) From a9adfa6a76e972b1d8b2fbb5ef1f4b9932e3d6f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Feb 2015 16:47:21 -0800 Subject: [PATCH 059/126] Fix bug in Project::relativize --- spec/project-spec.coffee | 6 ++++++ src/project.coffee | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index a1e503751..a4e6c0a03 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -332,10 +332,16 @@ describe "Project", -> describe ".relativize(path)", -> it "returns the path, relative to whichever root directory it is inside of", -> + atom.project.addPath(temp.mkdirSync("another-path")) + rootPath = atom.project.getPaths()[0] childPath = path.join(rootPath, "some", "child", "directory") expect(atom.project.relativize(childPath)).toBe path.join("some", "child", "directory") + rootPath = atom.project.getPaths()[1] + childPath = path.join(rootPath, "some", "child", "directory") + expect(atom.project.relativize(childPath)).toBe path.join("some", "child", "directory") + it "returns the given path if it is not in any of the root directories", -> randomPath = path.join("some", "random", "path") expect(atom.project.relativize(randomPath)).toBe randomPath diff --git a/src/project.coffee b/src/project.coffee index 4615a01b0..ac2242a28 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -241,8 +241,8 @@ class Project extends Model relativize: (fullPath) -> return fullPath if fullPath?.match(/[A-Za-z0-9+-.]+:\/\//) # leave path alone if it has a scheme for rootDirectory in @rootDirectories - if (relativePath = rootDirectory.relativize(fullPath))? - return relativePath + relativePath = rootDirectory.relativize(fullPath) + return relativePath if relativePath isnt fullPath fullPath # Public: Determines whether the given path (real or symbolic) is inside the From ea7cd518d9c2519863dd868b2f5c11ba1362f670 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Feb 2015 17:02:45 -0800 Subject: [PATCH 060/126] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e7d2051f7..167e47c5c 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "symbols-view": "0.83.0", "tabs": "0.67.0", "timecop": "0.30.0", - "tree-view": "0.157.0", + "tree-view": "0.159.0", "update-package-dependencies": "0.8.0", "welcome": "0.24.0", "whitespace": "0.29.0", From 4b6f163bd462b1d0d8f4092702bd68734bcb3ce8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 19 Feb 2015 09:31:18 -0800 Subject: [PATCH 061/126] :arrow_up: deprecation-cop@0.37 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 167e47c5c..40e5b96ad 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "bookmarks": "0.35.0", "bracket-matcher": "0.71.0", "command-palette": "0.34.0", - "deprecation-cop": "0.36.0", + "deprecation-cop": "0.37.0", "dev-live-reload": "0.41.0", "encoding-selector": "0.18.0", "exception-reporting": "0.24.0", From d4298bf077decbf724bdb715d6606b332f08fe34 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Feb 2015 10:02:37 -0800 Subject: [PATCH 062/126] Add Project::removePath The tree-view needs to be able to remove a path from the project --- spec/project-spec.coffee | 22 ++++++++++++++++++++++ src/project.coffee | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index a4e6c0a03..976a5774b 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -330,6 +330,28 @@ describe "Project", -> expect(atom.project.getPaths()).toEqual([oldPath]) expect(onDidChangePathsSpy).not.toHaveBeenCalled() + describe ".removePath(path)", -> + onDidChangePathsSpy = null + + beforeEach -> + onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths listener') + atom.project.onDidChangePaths(onDidChangePathsSpy) + + it "removes the directory and repository for the path", -> + result = atom.project.removePath(atom.project.getPaths()[0]) + expect(atom.project.getDirectories()).toEqual([]) + expect(atom.project.getRepositories()).toEqual([]) + expect(atom.project.getPaths()).toEqual([]) + expect(result).toBe true + expect(onDidChangePathsSpy).toHaveBeenCalled() + + it "does nothing if the path is not one of the project's root paths", -> + originalPaths = atom.project.getPaths() + result = atom.project.removePath(originalPaths[0] + "xyz") + expect(result).toBe false + expect(atom.project.getPaths()).toEqual(originalPaths) + expect(onDidChangePathsSpy).not.toHaveBeenCalled() + describe ".relativize(path)", -> it "returns the path, relative to whichever root directory it is inside of", -> atom.project.addPath(temp.mkdirSync("another-path")) diff --git a/src/project.coffee b/src/project.coffee index ac2242a28..8844fa231 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -209,6 +209,29 @@ class Project extends Model @emit "path-changed" @emitter.emit 'did-change-paths', @getPaths() + # Public: remove a path from the project's list of root paths. + # + # * `projectPath` {String} The path to remove. + removePath: (projectPath) -> + projectPath = path.normalize(projectPath) + + indexToRemove = null + for directory, i in @rootDirectories + if directory.getPath() is projectPath + indexToRemove = i + break + + if indexToRemove? + [removedDirectory] = @rootDirectories.splice(indexToRemove, 1) + [removedRepository] = @repositories.splice(indexToRemove, 1) + removedDirectory.off() + removedRepository?.destroy() + @emit "path-changed" + @emitter.emit "did-change-paths", @getPaths() + true + else + false + # Public: Get an {Array} of {Directory}s associated with this project. getDirectories: -> @rootDirectories From cf60855245445710f278878b89dddba62314593f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Feb 2015 10:28:54 -0800 Subject: [PATCH 063/126] Don't destroy repo in ::removePath if it is still needed --- spec/project-spec.coffee | 6 ++++++ src/project.coffee | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 976a5774b..956895952 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -352,6 +352,12 @@ describe "Project", -> expect(atom.project.getPaths()).toEqual(originalPaths) expect(onDidChangePathsSpy).not.toHaveBeenCalled() + it "doesn't destroy the repository if it is shared by another root directory", -> + atom.project.setPaths([__dirname, path.join(__dirname, "..", "src")]) + atom.project.removePath(__dirname) + expect(atom.project.getPaths()).toEqual([path.join(__dirname, "..", "src")]) + expect(atom.project.getRepositories()[0].isSubmodule("src")).toBe false + describe ".relativize(path)", -> it "returns the path, relative to whichever root directory it is inside of", -> atom.project.addPath(temp.mkdirSync("another-path")) diff --git a/src/project.coffee b/src/project.coffee index 8844fa231..50bda3ed8 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -225,7 +225,7 @@ class Project extends Model [removedDirectory] = @rootDirectories.splice(indexToRemove, 1) [removedRepository] = @repositories.splice(indexToRemove, 1) removedDirectory.off() - removedRepository?.destroy() + removedRepository?.destroy() unless removedRepository in @repositories @emit "path-changed" @emitter.emit "did-change-paths", @getPaths() true From a43362c5b2754b2955c3a892a9579fbfcaab2354 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 19 Feb 2015 11:15:56 -0800 Subject: [PATCH 064/126] :arrow_up: grim@1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40e5b96ad..0ce591fea 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "^3.0.0", - "grim": "1.1.2", + "grim": "1.2", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", "jquery": "^2.1.1", From d721cc62e3de5773649a89db4577daf4c4c40088 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 19 Feb 2015 11:16:50 -0800 Subject: [PATCH 065/126] Report deprecations in tasks --- spec/fixtures/task-handler-with-deprecations.coffee | 3 +++ spec/task-spec.coffee | 12 ++++++++++++ src/task-bootstrap.coffee | 8 ++++++++ src/task.coffee | 4 ++++ 4 files changed, 27 insertions(+) create mode 100644 spec/fixtures/task-handler-with-deprecations.coffee diff --git a/spec/fixtures/task-handler-with-deprecations.coffee b/spec/fixtures/task-handler-with-deprecations.coffee new file mode 100644 index 000000000..6ba8e86e8 --- /dev/null +++ b/spec/fixtures/task-handler-with-deprecations.coffee @@ -0,0 +1,3 @@ +{Git} = require 'atom' + +module.exports = -> diff --git a/spec/task-spec.coffee b/spec/task-spec.coffee index 403e4650d..93a5f3ab1 100644 --- a/spec/task-spec.coffee +++ b/spec/task-spec.coffee @@ -1,4 +1,5 @@ Task = require '../src/task' +Grim = require 'grim' describe "Task", -> describe "@once(taskPath, args..., callback)", -> @@ -43,3 +44,14 @@ describe "Task", -> runs -> expect(eventSpy).not.toHaveBeenCalled() + + it "reports deprecations in tasks", -> + jasmine.snapshotDeprecations() + task = new Task(require.resolve('./fixtures/task-handler-with-deprecations')) + + waitsFor (done) -> task.start(done) + + runs -> + deprecations = Grim.getDeprecations() + expect(deprecations.length).toBe 1 + jasmine.restoreDeprecationsSnapshot() diff --git a/src/task-bootstrap.coffee b/src/task-bootstrap.coffee index 4ed618d1a..c8d7f74d2 100644 --- a/src/task-bootstrap.coffee +++ b/src/task-bootstrap.coffee @@ -41,6 +41,14 @@ handleEvents = -> result = handler.bind({async})(args...) emit('task:completed', result) unless isAsync +setupDeprecations = -> + Grim = require 'grim' + Grim.on 'updated', -> + deprecations = Grim.getDeprecations().map (deprecation) -> deprecation.serialize() + emit('task:deprecations', deprecations) + Grim.clearDeprecations() + setupGlobals() handleEvents() +setupDeprecations() handler = require(taskPath) diff --git a/src/task.coffee b/src/task.coffee index 6fe055ca6..9572494b8 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -1,6 +1,7 @@ _ = require 'underscore-plus' {fork} = require 'child_process' {Emitter} = require 'emissary' +Grim = require 'grim' # Extended: Run a node script in a separate process. # @@ -87,6 +88,9 @@ class Task @on "task:log", -> console.log(arguments...) @on "task:warn", -> console.warn(arguments...) @on "task:error", -> console.error(arguments...) + @on "task:deprecations", (deprecations) -> + Grim.addSerializedDeprecation(deprecation) for deprecation in deprecations + return @on "task:completed", (args...) => @callback?(args...) @handleEvents() From 6f2aec4395975a9f92a05e1154ff4bb915cf9112 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 19 Feb 2015 11:22:08 -0800 Subject: [PATCH 066/126] Clear deprecations after mapping over them --- src/task-bootstrap.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task-bootstrap.coffee b/src/task-bootstrap.coffee index c8d7f74d2..ebb5cdc2b 100644 --- a/src/task-bootstrap.coffee +++ b/src/task-bootstrap.coffee @@ -45,8 +45,8 @@ setupDeprecations = -> Grim = require 'grim' Grim.on 'updated', -> deprecations = Grim.getDeprecations().map (deprecation) -> deprecation.serialize() - emit('task:deprecations', deprecations) Grim.clearDeprecations() + emit('task:deprecations', deprecations) setupGlobals() handleEvents() From 0674244f5c9f4bd2cb362af98df4bcb8da58c2a1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Feb 2015 14:30:58 -0800 Subject: [PATCH 067/126] Always create new window when opening w/ no path Fixes #5631 --- spec/integration/startup-spec.coffee | 6 ++++++ src/browser/atom-application.coffee | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index c2b1b960c..3161cd932 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -142,3 +142,9 @@ describe "Starting Atom", -> client .waitForExist("atom-workspace") .waitForPaneItemCount(1, 5000) + + # Opening with no file paths always creates a new window, even if + # existing windows have no project paths. + .waitForNewWindow(-> + @startAnotherAtom([], ATOM_HOME: AtomHome) + , 5000) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 32e8ca293..7324aa460 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -361,12 +361,11 @@ class AtomApplication # Default to using the specified window or the last focused window currentWindow = window ? @lastFocusedWindow - - if pathsToOpen.every((pathToOpen) -> fs.statSyncNoException(pathToOpen).isFile?()) - existingWindow ?= currentWindow - - unless currentWindow?.projectPaths?.length > 0 - existingWindow ?= currentWindow + stats = (fs.statSyncNoException(pathToOpen) for pathToOpen in pathsToOpen) + existingWindow ?= currentWindow if ( + stats.every((stat) -> stat.isFile?()) or + stats.some((stat) -> stat.isDirectory?()) and not currentWindow?.hasProjectPath() + ) if existingWindow? openedWindow = existingWindow From 05ffaec2acb4ca7d5be880598dbbd3f6cb9f9f72 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 19 Feb 2015 14:43:21 -0800 Subject: [PATCH 068/126] :arrow_up: fuzzy-finder@0.67 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ce591fea..fb5a14629 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "encoding-selector": "0.18.0", "exception-reporting": "0.24.0", "find-and-replace": "0.157.0", - "fuzzy-finder": "0.66.0", + "fuzzy-finder": "0.67.0", "git-diff": "0.52.0", "go-to-line": "0.30.0", "grammar-selector": "0.45.0", From ebefc3b36d5dec88ff529cee2fa25d4c74b4eff0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 19 Feb 2015 14:53:15 -0800 Subject: [PATCH 069/126] Assert stack trace location --- spec/task-spec.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/task-spec.coffee b/spec/task-spec.coffee index 93a5f3ab1..81a8713ad 100644 --- a/spec/task-spec.coffee +++ b/spec/task-spec.coffee @@ -47,11 +47,13 @@ describe "Task", -> it "reports deprecations in tasks", -> jasmine.snapshotDeprecations() - task = new Task(require.resolve('./fixtures/task-handler-with-deprecations')) + handlerPath = require.resolve('./fixtures/task-handler-with-deprecations') + task = new Task(handlerPath) waitsFor (done) -> task.start(done) runs -> deprecations = Grim.getDeprecations() expect(deprecations.length).toBe 1 + expect(deprecations[0].getStacks()[0][1].fileName).toBe handlerPath jasmine.restoreDeprecationsSnapshot() From 883af7a83e2faf9ccf8d0440a4435c1e666e4178 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 10 Feb 2015 13:45:47 -0700 Subject: [PATCH 070/126] Update cursor nodes manually --- spec/text-editor-component-spec.coffee | 6 +-- src/cursor-component.coffee | 14 ------ src/cursors-component.coffee | 63 +++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 24 deletions(-) delete mode 100644 src/cursor-component.coffee diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index d4a64c300..f576685c3 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -706,13 +706,13 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 2 - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)" - expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)" + expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)" wrapperView.on 'cursor:moved', cursorMovedListener = jasmine.createSpy('cursorMovedListener') cursor3.setScreenPosition([4, 11], autoscroll: false) nextAnimationFrame() - expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)" expect(cursorMovedListener).toHaveBeenCalled() cursor3.destroy() diff --git a/src/cursor-component.coffee b/src/cursor-component.coffee deleted file mode 100644 index ec7e04c1c..000000000 --- a/src/cursor-component.coffee +++ /dev/null @@ -1,14 +0,0 @@ -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' -{isEqualForProperties} = require 'underscore-plus' - -module.exports = -CursorComponent = React.createClass - displayName: 'CursorComponent' - - render: -> - {pixelRect} = @props - {top, left, height, width} = pixelRect - WebkitTransform = "translate(#{left}px, #{top}px)" - - div className: 'cursor', style: {height, width, WebkitTransform} diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index 646618e9b..bf704a1a5 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -2,18 +2,67 @@ React = require 'react-atom-fork' {div} = require 'reactionary-atom-fork' {debounce, toArray, isEqualForProperties, isEqual} = require 'underscore-plus' SubscriberMixin = require './subscriber-mixin' -CursorComponent = require './cursor-component' module.exports = CursorsComponent = React.createClass displayName: 'CursorsComponent' + oldState: null + cursorNodesById: null render: -> - {presenter} = @props + div className: 'cursors' - className = 'cursors' - className += ' blink-off' if presenter.state.content.blinkCursorsOff + componentWillMount: -> + @cursorNodesById = {} - div {className}, - for key, pixelRect of presenter.state.content.cursors - CursorComponent({key, pixelRect}) + componentDidMount: -> + @updateSync() + + componentDidUpdate: -> + @updateSync() + + updateSync: -> + node = @getDOMNode() + newState = @props.presenter.state.content + @oldState ?= {cursors: {}} + + # update blink class + if newState.blinkCursorsOff isnt @oldState.blinkCursorsOff + if newState.blinkCursorsOff + node.classList.add 'blink-off' + else + node.classList.remove 'blink-off' + @oldState.blinkCursorsOff = newState.blinkCursorsOff + + # remove cursors + for id of @oldState.cursors + unless newState.cursors[id]? + @cursorNodesById[id].remove() + delete @cursorNodesById[id] + delete @oldState.cursors[id] + + # add or update cursors + for id, cursorState of newState.cursors + unless @oldState.cursors[id]? + cursorNode = document.createElement('div') + cursorNode.classList.add('cursor') + @cursorNodesById[id] = cursorNode + node.appendChild(cursorNode) + @updateCursorNode(id, cursorState) + + updateCursorNode: (id, newCursorState) -> + cursorNode = @cursorNodesById[id] + oldCursorState = (@oldState.cursors[id] ?= {}) + + if newCursorState.top isnt oldCursorState.top or newCursorState.left isnt oldCursorState.left + cursorNode.style['-webkit-transform'] = "translate(#{newCursorState.left}px, #{newCursorState.top}px)" + oldCursorState.top = newCursorState.top + oldCursorState.left = newCursorState.left + + if newCursorState.height isnt oldCursorState.height + cursorNode.style.height = newCursorState.height + 'px' + oldCursorState.height = newCursorState.height + + if newCursorState.width isnt oldCursorState.width + cursorNode.style.width = newCursorState.width + 'px' + oldCursorState.width = newCursorState.width From c294bb105a630c9f803620e55b143bf4e34ed783 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 10 Feb 2015 15:42:25 -0700 Subject: [PATCH 071/126] Update highlight nodes manually --- src/highlight-component.coffee | 50 -------------- src/highlights-component.coffee | 113 +++++++++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 60 deletions(-) delete mode 100644 src/highlight-component.coffee diff --git a/src/highlight-component.coffee b/src/highlight-component.coffee deleted file mode 100644 index ef090ea7b..000000000 --- a/src/highlight-component.coffee +++ /dev/null @@ -1,50 +0,0 @@ -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' -{isEqualForProperties} = require 'underscore-plus' - -module.exports = -HighlightComponent = React.createClass - displayName: 'HighlightComponent' - currentFlashCount: 0 - currentFlashClass: null - - render: -> - {state} = @props - - className = 'highlight' - className += " #{state.class}" if state.class? - - div {className}, - for region, i in state.regions - regionClassName = 'region' - regionClassName += " #{state.deprecatedRegionClass}" if state.deprecatedRegionClass? - div className: regionClassName, key: i, style: region - - componentDidMount: -> - @flashIfRequested() - - componentDidUpdate: -> - @flashIfRequested() - - flashIfRequested: -> - if @props.state.flashCount > @currentFlashCount - @currentFlashCount = @props.state.flashCount - - node = @getDOMNode() - {flashClass, flashDuration} = @props.state - - addFlashClass = => - node.classList.add(flashClass) - @currentFlashClass = flashClass - @flashTimeoutId = setTimeout(removeFlashClass, flashDuration) - - removeFlashClass = => - node.classList.remove(@currentFlashClass) - @currentFlashClass = null - clearTimeout(@flashTimeoutId) - - if @currentFlashClass? - removeFlashClass() - requestAnimationFrame(addFlashClass) - else - addFlashClass() diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index 1d7ae4de3..e402ccbe2 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -1,25 +1,118 @@ React = require 'react-atom-fork' {div} = require 'reactionary-atom-fork' -{isEqualForProperties} = require 'underscore-plus' -HighlightComponent = require './highlight-component' + +RegionStyleProperties = ['top', 'left', 'right', 'width', 'height'] module.exports = HighlightsComponent = React.createClass displayName: 'HighlightsComponent' + oldState: null + highlightNodesById: null + regionNodesByHighlightId: null render: -> - div className: 'highlights', - @renderHighlights() + div className: 'highlights' - renderHighlights: -> - {presenter} = @props - highlightComponents = [] - for key, state of presenter.state.content.highlights - highlightComponents.push(HighlightComponent({key, state})) - highlightComponents + componentWillMount: -> + @highlightNodesById = {} + @regionNodesByHighlightId = {} componentDidMount: -> if atom.config.get('editor.useShadowDOM') insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', '.underlayer') @getDOMNode().appendChild(insertionPoint) + + componentDidUpdate: -> + @updateSync() + + updateSync: -> + node = @getDOMNode() + newState = @props.presenter.state.content.highlights + @oldState ?= {} + + # remove highlights + for id of @oldState + unless newState[id]? + @highlightNodesById[id].remove() + delete @highlightNodesById[id] + delete @regionNodesByHighlightId[id] + delete @oldState[id] + + # add or update highlights + for id, highlightState of newState + unless @oldState[id]? + highlightNode = document.createElement('div') + highlightNode.classList.add('highlight') + @highlightNodesById[id] = highlightNode + @regionNodesByHighlightId[id] = {} + node.appendChild(highlightNode) + @updateHighlightNode(id, highlightState) + + updateHighlightNode: (id, newHighlightState) -> + highlightNode = @highlightNodesById[id] + oldHighlightState = (@oldState[id] ?= {regions: [], flashCount: 0}) + + # update class + if newHighlightState.class isnt oldHighlightState.class + highlightNode.classList.remove(oldHighlightState.class) if oldHighlightState.class? + highlightNode.classList.add(newHighlightState.class) + oldHighlightState.class = newHighlightState.class + + @updateHighlightRegions(id, newHighlightState) + @flashHighlightNodeIfRequested(id, newHighlightState) + + updateHighlightRegions: (id, newHighlightState) -> + oldHighlightState = @oldState[id] + highlightNode = @highlightNodesById[id] + + # remove regions + while oldHighlightState.regions.length > newHighlightState.regions.length + oldHighlightState.regions.pop() + @regionNodesByHighlightId[id][oldHighlightState.regions.length].remove() + delete @regionNodesByHighlightId[id][oldHighlightState.regions.length] + + # add or update regions + for newRegionState, i in newHighlightState.regions + unless oldHighlightState.regions[i]? + oldHighlightState.regions[i] = {} + regionNode = document.createElement('div') + regionNode.classList.add('region') + regionNode.classList.add(newHighlightState.deprecatedRegionClass) if newHighlightState.deprecatedRegionClass? + @regionNodesByHighlightId[id][i] = regionNode + highlightNode.appendChild(regionNode) + + oldRegionState = oldHighlightState.regions[i] + regionNode = @regionNodesByHighlightId[id][i] + + for property in RegionStyleProperties + if newRegionState[property] isnt oldRegionState[property] + oldRegionState[property] = newRegionState[property] + if newRegionState[property]? + regionNode.style[property] = newRegionState[property] + 'px' + else + regionNode.style[property] = '' + + flashHighlightNodeIfRequested: (id, newHighlightState) -> + oldHighlightState = @oldState[id] + return unless newHighlightState.flashCount > oldHighlightState.flashCount + + highlightNode = @highlightNodesById[id] + + addFlashClass = => + highlightNode.classList.add(newHighlightState.flashClass) + oldHighlightState.flashClass = newHighlightState.flashClass + @flashTimeoutId = setTimeout(removeFlashClass, newHighlightState.flashDuration) + + removeFlashClass = => + highlightNode.classList.remove(oldHighlightState.flashClass) + oldHighlightState.flashClass = null + clearTimeout(@flashTimeoutId) + + if oldHighlightState.flashClass? + removeFlashClass() + requestAnimationFrame(addFlashClass) + else + addFlashClass() + + oldHighlightState.flashCount = newHighlightState.flashCount From b4ecb65fb947399e69d2499e15ea6b748f4e76df Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 10 Feb 2015 16:40:18 -0700 Subject: [PATCH 072/126] Make CursorsComponent a plain object instead of using React --- src/cursors-component.coffee | 30 ++++++++---------------------- src/lines-component.coffee | 14 ++++++++++---- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index bf704a1a5..e11019d6c 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -1,37 +1,23 @@ -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' -{debounce, toArray, isEqualForProperties, isEqual} = require 'underscore-plus' -SubscriberMixin = require './subscriber-mixin' - module.exports = -CursorsComponent = React.createClass - displayName: 'CursorsComponent' +class CursorsComponent oldState: null - cursorNodesById: null - render: -> - div className: 'cursors' - - componentWillMount: -> + constructor: (@presenter) -> @cursorNodesById = {} - - componentDidMount: -> - @updateSync() - - componentDidUpdate: -> + @domNode = document.createElement('div') + @domNode.classList.add('cursors') @updateSync() updateSync: -> - node = @getDOMNode() - newState = @props.presenter.state.content + newState = @presenter.state.content @oldState ?= {cursors: {}} # update blink class if newState.blinkCursorsOff isnt @oldState.blinkCursorsOff if newState.blinkCursorsOff - node.classList.add 'blink-off' + @domNode.classList.add 'blink-off' else - node.classList.remove 'blink-off' + @domNode.classList.remove 'blink-off' @oldState.blinkCursorsOff = newState.blinkCursorsOff # remove cursors @@ -47,7 +33,7 @@ CursorsComponent = React.createClass cursorNode = document.createElement('div') cursorNode.classList.add('cursor') @cursorNodesById[id] = cursorNode - node.appendChild(cursorNode) + @domNode.appendChild(cursorNode) @updateCursorNode(id, cursorState) updateCursorNode: (id, newCursorState) -> diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 1f830038f..d7d41cbdc 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -31,7 +31,6 @@ LinesComponent = React.createClass div {className: 'lines', style}, div className: 'placeholder-text', placeholderText if placeholderText? - CursorsComponent {presenter} HighlightsComponent {presenter} getTransform: -> @@ -51,17 +50,22 @@ LinesComponent = React.createClass @renderedDecorationsByLineId = {} componentDidMount: -> + node = @getDOMNode() + + @cursorsComponent = new CursorsComponent(@props.presenter) + node.appendChild(@cursorsComponent.domNode) + if @props.useShadowDOM insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', '.overlayer') - @getDOMNode().appendChild(insertionPoint) + node.appendChild(insertionPoint) insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', 'atom-overlay') @overlayManager = new OverlayManager(@props.hostElement) - @getDOMNode().appendChild(insertionPoint) + node.appendChild(insertionPoint) else - @overlayManager = new OverlayManager(@getDOMNode()) + @overlayManager = new OverlayManager(node) componentDidUpdate: -> {visible, presenter} = @props @@ -70,6 +74,8 @@ LinesComponent = React.createClass @updateLineNodes() @measureCharactersInNewLines() if visible and not @newState.scrollingVertically + @cursorsComponent.updateSync() + @overlayManager?.render(@props) @oldState.indentGuidesVisible = @newState.indentGuidesVisible From b0a29bdc2fdd19962f9f6b26f63de0220d62586f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 10 Feb 2015 16:50:23 -0700 Subject: [PATCH 073/126] Make HighlightsComponent a plain object instead of using React --- src/highlights-component.coffee | 27 ++++++++------------------- src/lines-component.coffee | 5 ++++- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index e402ccbe2..488a2236f 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -1,34 +1,23 @@ -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' - RegionStyleProperties = ['top', 'left', 'right', 'width', 'height'] module.exports = -HighlightsComponent = React.createClass - displayName: 'HighlightsComponent' +class HighlightsComponent oldState: null - highlightNodesById: null - regionNodesByHighlightId: null - render: -> - div className: 'highlights' - - componentWillMount: -> + constructor: (@presenter) -> @highlightNodesById = {} @regionNodesByHighlightId = {} - componentDidMount: -> + @domNode = document.createElement('div') + @domNode.classList.add('highlights') + if atom.config.get('editor.useShadowDOM') insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', '.underlayer') - @getDOMNode().appendChild(insertionPoint) - - componentDidUpdate: -> - @updateSync() + @domNode.appendChild(insertionPoint) updateSync: -> - node = @getDOMNode() - newState = @props.presenter.state.content.highlights + newState = @presenter.state.content.highlights @oldState ?= {} # remove highlights @@ -46,7 +35,7 @@ HighlightsComponent = React.createClass highlightNode.classList.add('highlight') @highlightNodesById[id] = highlightNode @regionNodesByHighlightId[id] = {} - node.appendChild(highlightNode) + @domNode.appendChild(highlightNode) @updateHighlightNode(id, highlightState) updateHighlightNode: (id, newHighlightState) -> diff --git a/src/lines-component.coffee b/src/lines-component.coffee index d7d41cbdc..33e62851e 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -31,7 +31,6 @@ LinesComponent = React.createClass div {className: 'lines', style}, div className: 'placeholder-text', placeholderText if placeholderText? - HighlightsComponent {presenter} getTransform: -> {scrollTop, scrollLeft} = @newState @@ -55,6 +54,9 @@ LinesComponent = React.createClass @cursorsComponent = new CursorsComponent(@props.presenter) node.appendChild(@cursorsComponent.domNode) + @highlightsComponent = new HighlightsComponent(@props.presenter) + node.appendChild(@highlightsComponent.domNode) + if @props.useShadowDOM insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', '.overlayer') @@ -75,6 +77,7 @@ LinesComponent = React.createClass @measureCharactersInNewLines() if visible and not @newState.scrollingVertically @cursorsComponent.updateSync() + @highlightsComponent.updateSync() @overlayManager?.render(@props) From da793e834a0500d7696d94c5ef8977882ca224c0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 10 Feb 2015 17:06:15 -0700 Subject: [PATCH 074/126] Perform all LinesComponent DOM updates manually --- src/lines-component.coffee | 53 ++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 33e62851e..dba209e07 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -15,25 +15,12 @@ WrapperDiv = document.createElement('div') module.exports = LinesComponent = React.createClass displayName: 'LinesComponent' + placeholderTextDiv: null render: -> - {editor, presenter} = @props - @oldState ?= {lines: {}} - @newState = presenter.state.content + div {className: 'lines'} - {scrollHeight, scrollWidth, backgroundColor, placeholderText} = @newState - - style = - height: scrollHeight - width: scrollWidth - WebkitTransform: @getTransform() - backgroundColor: backgroundColor - - div {className: 'lines', style}, - div className: 'placeholder-text', placeholderText if placeholderText? - - getTransform: -> - {scrollTop, scrollLeft} = @newState + getTransform: (scrollTop, scrollLeft) -> {useHardwareAcceleration} = @props if useHardwareAcceleration @@ -69,8 +56,42 @@ LinesComponent = React.createClass else @overlayManager = new OverlayManager(node) + @updateSync() + componentDidUpdate: -> + @updateSync() + + updateSync: -> {visible, presenter} = @props + @newState = presenter.state.content + @oldState ?= {lines: {}} + + node = @getDOMNode() + + if @newState.scrollHeight isnt @oldState.scrollHeight + node.style.height = @newState.scrollHeight + 'px' + @oldState.scrollHeight = @newState.scrollHeight + + if @newState.scrollWidth isnt @oldState.scrollWidth + node.style.width = @newState.scrollWidth + 'px' + @oldState.scrollWidth = @newState.scrollWidth + + if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft + node.style['-webkit-transform'] = @getTransform(@newState.scrollTop, @newState.scrollLeft) + @oldState.scrollTop = @newState.scrollTop + @oldState.scrollLeft = @newState.scrollLeft + + if @newState.backgroundColor isnt @oldState.backgroundColor + node.style.backgroundColor = @newState.backgroundColor + @oldState.backgroundColor = @newState.backgroundColor + + if @newState.placeholderText isnt @oldState.placeholderText + @placeholderTextDiv?.remove() + if @newState.placeholderText? + @placeholderTextDiv = document.createElement('div') + @placeholderTextDiv.classList.add('placeholder-text') + @placeholderTextDiv.textContent = @newState.placeholderText + node.appendChild(@placeholderTextDiv) @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible @updateLineNodes() From c06e100faeca11e7882e2faeafdfb1ea42b7f02f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 10 Feb 2015 17:33:48 -0700 Subject: [PATCH 075/126] Make LinesComponent a normal object instead of a React component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, remove ability to disable hardware acceleration since there’s no longer a need for it and it complicated this conversion. --- spec/text-editor-component-spec.coffee | 2 +- src/config-schema.coffee | 4 - src/gutter-component.coffee | 7 +- src/lines-component.coffee | 101 ++++++++----------------- src/overlay-manager.coffee | 4 +- src/scrollbar-component.coffee | 4 +- src/text-editor-component.coffee | 37 ++++----- src/text-editor-presenter.coffee | 1 + 8 files changed, 54 insertions(+), 106 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index f576685c3..832ca993d 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2558,7 +2558,7 @@ describe "TextEditorComponent", -> expect(wrapperNode.classList.contains('mini')).toBe true it "does not have an opaque background on lines", -> - expect(component.refs.lines.getDOMNode().getAttribute('style')).not.toContain 'background-color' + expect(component.linesComponent.domNode.getAttribute('style')).not.toContain 'background-color' it "does not render invisible characters", -> atom.config.set('editor.invisibles', eol: 'E') diff --git a/src/config-schema.coffee b/src/config-schema.coffee index f849e4909..1796bb64c 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -162,10 +162,6 @@ module.exports = default: 300 minimum: 0 description: 'Time interval in milliseconds within which operations will be grouped together in the undo history' - useHardwareAcceleration: - type: 'boolean' - default: true - description: 'Disabling will improve editor font rendering but reduce scrolling performance.' useShadowDOM: type: 'boolean' default: true diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 564c611ab..cb443cf26 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -30,13 +30,8 @@ GutterComponent = React.createClass backgroundColor: backgroundColor getTransform: -> - {useHardwareAcceleration} = @props {scrollTop} = @newState - - if useHardwareAcceleration - "translate3d(0px, #{-scrollTop}px, 0px)" - else - "translate(0px, #{-scrollTop}px)" + "translate3d(0px, #{-scrollTop}px, 0px)" componentWillMount: -> @lineNumberNodesById = {} diff --git a/src/lines-component.coffee b/src/lines-component.coffee index dba209e07..e6c0733e1 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -1,7 +1,5 @@ _ = require 'underscore-plus' -React = require 'react-atom-fork' -{div, span} = require 'reactionary-atom-fork' -{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus' +{toArray} = require 'underscore-plus' {$$} = require 'space-pen' CursorsComponent = require './cursors-component' @@ -13,76 +11,58 @@ AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} WrapperDiv = document.createElement('div') module.exports = -LinesComponent = React.createClass - displayName: 'LinesComponent' +class LinesComponent placeholderTextDiv: null - render: -> - div {className: 'lines'} - - getTransform: (scrollTop, scrollLeft) -> - {useHardwareAcceleration} = @props - - if useHardwareAcceleration - "translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)" - else - "translate(#{-scrollLeft}px, #{-scrollTop}px)" - - componentWillMount: -> + constructor: ({@presenter, @hostElement, @useShadowDOM}) -> @measuredLines = new Set @lineNodesByLineId = {} @screenRowsByLineId = {} @lineIdsByScreenRow = {} @renderedDecorationsByLineId = {} - componentDidMount: -> - node = @getDOMNode() + @domNode = document.createElement('div') + @domNode.classList.add('lines') - @cursorsComponent = new CursorsComponent(@props.presenter) - node.appendChild(@cursorsComponent.domNode) + @cursorsComponent = new CursorsComponent(@presenter) + @domNode.appendChild(@cursorsComponent.domNode) - @highlightsComponent = new HighlightsComponent(@props.presenter) - node.appendChild(@highlightsComponent.domNode) + @highlightsComponent = new HighlightsComponent(@presenter) + @domNode.appendChild(@highlightsComponent.domNode) - if @props.useShadowDOM + if @useShadowDOM insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', '.overlayer') - node.appendChild(insertionPoint) + @domNode.appendChild(insertionPoint) insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', 'atom-overlay') - @overlayManager = new OverlayManager(@props.hostElement) - node.appendChild(insertionPoint) + @overlayManager = new OverlayManager(@hostElement) + @domNode.appendChild(insertionPoint) else - @overlayManager = new OverlayManager(node) + @overlayManager = new OverlayManager(@domNode) @updateSync() - componentDidUpdate: -> - @updateSync() - - updateSync: -> - {visible, presenter} = @props - @newState = presenter.state.content + updateSync: (visible) -> + @newState = @presenter.state.content @oldState ?= {lines: {}} - node = @getDOMNode() - if @newState.scrollHeight isnt @oldState.scrollHeight - node.style.height = @newState.scrollHeight + 'px' + @domNode.style.height = @newState.scrollHeight + 'px' @oldState.scrollHeight = @newState.scrollHeight if @newState.scrollWidth isnt @oldState.scrollWidth - node.style.width = @newState.scrollWidth + 'px' + @domNode.style.width = @newState.scrollWidth + 'px' @oldState.scrollWidth = @newState.scrollWidth if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft - node.style['-webkit-transform'] = @getTransform(@newState.scrollTop, @newState.scrollLeft) + @domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, #{-@newState.scrollTop}px, 0px)" @oldState.scrollTop = @newState.scrollTop @oldState.scrollLeft = @newState.scrollLeft if @newState.backgroundColor isnt @oldState.backgroundColor - node.style.backgroundColor = @newState.backgroundColor + @domNode.style.backgroundColor = @newState.backgroundColor @oldState.backgroundColor = @newState.backgroundColor if @newState.placeholderText isnt @oldState.placeholderText @@ -91,7 +71,7 @@ LinesComponent = React.createClass @placeholderTextDiv = document.createElement('div') @placeholderTextDiv.classList.add('placeholder-text') @placeholderTextDiv.textContent = @newState.placeholderText - node.appendChild(@placeholderTextDiv) + @domNode.appendChild(@placeholderTextDiv) @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible @updateLineNodes() @@ -100,15 +80,11 @@ LinesComponent = React.createClass @cursorsComponent.updateSync() @highlightsComponent.updateSync() - @overlayManager?.render(@props) + @overlayManager?.render(@presenter) @oldState.indentGuidesVisible = @newState.indentGuidesVisible @oldState.scrollWidth = @newState.scrollWidth - clearScreenRowCaches: -> - @screenRowsByLineId = {} - @lineIdsByScreenRow = {} - removeLineNodes: -> @removeLineNode(id) for id of @oldState.lines @@ -120,8 +96,6 @@ LinesComponent = React.createClass delete @oldState.lines[id] updateLineNodes: -> - {presenter} = @props - for id of @oldState.lines unless @newState.lines.hasOwnProperty(id) @removeLineNode(id) @@ -144,15 +118,13 @@ LinesComponent = React.createClass return unless newLineIds? WrapperDiv.innerHTML = newLinesHTML - newLineNodes = toArray(WrapperDiv.children) - node = @getDOMNode() + newLineNodes = _.toArray(WrapperDiv.children) for id, i in newLineIds lineNode = newLineNodes[i] @lineNodesByLineId[id] = lineNode - node.appendChild(lineNode) + @domNode.appendChild(lineNode) buildLineHTML: (id) -> - {presenter} = @props {scrollWidth} = @newState {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id] @@ -197,7 +169,6 @@ LinesComponent = React.createClass @buildEndOfLineHTML(id) or ' ' buildLineInnerHTML: (id) -> - {editor} = @props {indentGuidesVisible} = @newState {tokens, text, isOnlyWhitespace} = @newState.lines[id] innerHTML = "" @@ -275,26 +246,22 @@ LinesComponent = React.createClass @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] measureLineHeightAndDefaultCharWidth: -> - node = @getDOMNode() - node.appendChild(DummyLineNode) + @domNode.appendChild(DummyLineNode) lineHeightInPixels = DummyLineNode.getBoundingClientRect().height charWidth = DummyLineNode.firstChild.getBoundingClientRect().width - node.removeChild(DummyLineNode) + @domNode.removeChild(DummyLineNode) - {editor, presenter} = @props - presenter.setLineHeight(lineHeightInPixels) - presenter.setBaseCharacterWidth(charWidth) + @presenter.setLineHeight(lineHeightInPixels) + @presenter.setBaseCharacterWidth(charWidth) remeasureCharacterWidths: -> - return unless @props.presenter.baseCharacterWidth + return unless @presenter.baseCharacterWidth @clearScopedCharWidths() @measureCharactersInNewLines() measureCharactersInNewLines: -> - {presenter} = @props - - presenter.batchCharacterMeasurement => + @presenter.batchCharacterMeasurement => for id, lineState of @oldState.lines unless @measuredLines.has(id) lineNode = @lineNodesByLineId[id] @@ -302,13 +269,12 @@ LinesComponent = React.createClass return measureCharactersInLine: (tokenizedLine, lineNode) -> - {editor} = @props rangeForMeasurement = null iterator = null charIndex = 0 for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens - charWidths = editor.getScopedCharWidths(scopes) + charWidths = @presenter.getScopedCharacterWidths(scopes) valueIndex = 0 while valueIndex < value.length @@ -340,7 +306,7 @@ LinesComponent = React.createClass rangeForMeasurement.setStart(textNode, i) rangeForMeasurement.setEnd(textNode, i + charLength) charWidth = rangeForMeasurement.getBoundingClientRect().width - @props.presenter.setScopedCharacterWidth(scopes, char, charWidth) + @presenter.setScopedCharacterWidth(scopes, char, charWidth) charIndex += charLength @@ -348,5 +314,4 @@ LinesComponent = React.createClass clearScopedCharWidths: -> @measuredLines.clear() - @props.editor.clearScopedCharWidths() - @props.presenter.clearScopedCharacterWidths() + @presenter.clearScopedCharacterWidths() diff --git a/src/overlay-manager.coffee b/src/overlay-manager.coffee index 82387fcf7..ecfbc8667 100644 --- a/src/overlay-manager.coffee +++ b/src/overlay-manager.coffee @@ -3,9 +3,7 @@ class OverlayManager constructor: (@container) -> @overlayNodesById = {} - render: (props) -> - {presenter} = props - + render: (presenter) -> for decorationId, {pixelPosition, item} of presenter.state.content.overlays @renderOverlay(presenter, decorationId, item, pixelPosition) diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index eaf64b8bc..4447acc8d 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -7,7 +7,7 @@ ScrollbarComponent = React.createClass displayName: 'ScrollbarComponent' render: -> - {presenter, orientation, className, useHardwareAcceleration} = @props + {presenter, orientation, className} = @props switch orientation when 'vertical' @@ -18,7 +18,7 @@ ScrollbarComponent = React.createClass style = {} style.display = 'none' unless @newState.visible - style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559 + style.transform = 'translateZ(0)' # See atom/atom#3559 switch orientation when 'vertical' style.width = @newState.width diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 0f4695ca0..833491c30 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -50,10 +50,8 @@ TextEditorComponent = React.createClass @performedInitialMeasurement = false if editor.isDestroyed() if @performedInitialMeasurement - visible = @isVisible() - hiddenInputStyle = @getHiddenInputPosition() - hiddenInputStyle.WebkitTransform = 'translateZ(0)' if @useHardwareAcceleration + hiddenInputStyle.WebkitTransform = 'translateZ(0)' style.height = @presenter.state.height if @presenter.state.height? if useShadowDOM @@ -67,7 +65,7 @@ TextEditorComponent = React.createClass if @gutterVisible GutterComponent { ref: 'gutter', onMouseDown: @onGutterMouseDown, - @presenter, editor, @useHardwareAcceleration + @presenter, editor } div ref: 'scrollView', className: 'scroll-view', @@ -76,17 +74,12 @@ TextEditorComponent = React.createClass className: 'hidden-input' style: hiddenInputStyle - LinesComponent { - ref: 'lines', @presenter, editor, hostElement, @useHardwareAcceleration, useShadowDOM, visible - } - ScrollbarComponent ref: 'horizontalScrollbar' className: 'horizontal-scrollbar' orientation: 'horizontal' presenter: @presenter onScroll: @onHorizontalScroll - useHardwareAcceleration: @useHardwareAcceleration ScrollbarComponent ref: 'verticalScrollbar' @@ -94,7 +87,6 @@ TextEditorComponent = React.createClass orientation: 'vertical' presenter: @presenter onScroll: @onVerticalScroll - useHardwareAcceleration: @useHardwareAcceleration # Also used to measure the height/width of scrollbars after the initial render ScrollbarCornerComponent @@ -126,9 +118,14 @@ TextEditorComponent = React.createClass stoppedScrollingDelay: 200 @presenter.onDidUpdateState(@requestUpdate) - componentDidMount: -> - {editor, stylesElement} = @props + {editor, stylesElement, hostElement, useShadowDOM} = @props + + @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM}) + scrollViewNode = @refs.scrollView.getDOMNode() + horizontalScrollbarNode = @refs.horizontalScrollbar.getDOMNode() + scrollViewNode.insertBefore(@linesComponent.domNode, horizontalScrollbarNode) + @linesComponent.updateSync(@isVisible()) @observeEditor() @listenForDOMEvents() @@ -161,6 +158,8 @@ TextEditorComponent = React.createClass @cursorMoved = false @selectionChanged = false + @linesComponent.updateSync(@isVisible()) + if @props.editor.isAlive() @updateParentViewFocusedClassIfNeeded(prevState) @updateParentViewMiniClass() @@ -289,7 +288,6 @@ TextEditorComponent = React.createClass timeoutId = setTimeout(writeSelectedTextToSelectionClipboard) observeConfig: -> - @subscribe atom.config.observe 'editor.useHardwareAcceleration', @setUseHardwareAcceleration @subscribe atom.config.onDidChange 'editor.fontSize', @sampleFontStyling @subscribe atom.config.onDidChange 'editor.fontFamily', @sampleFontStyling @subscribe atom.config.onDidChange 'editor.lineHeight', @sampleFontStyling @@ -689,14 +687,14 @@ TextEditorComponent = React.createClass measureLineHeightAndDefaultCharWidth: -> if @isVisible() @measureLineHeightAndDefaultCharWidthWhenShown = false - @refs.lines.measureLineHeightAndDefaultCharWidth() + @linesComponent.measureLineHeightAndDefaultCharWidth() else @measureLineHeightAndDefaultCharWidthWhenShown = true remeasureCharacterWidths: -> if @isVisible() @remeasureCharacterWidthsWhenShown = false - @refs.lines.remeasureCharacterWidths() + @linesComponent.remeasureCharacterWidths() else @remeasureCharacterWidthsWhenShown = true @@ -761,7 +759,7 @@ TextEditorComponent = React.createClass consolidateSelections: (e) -> e.abortKeyBinding() unless @props.editor.consolidateSelections() - lineNodeForScreenRow: (screenRow) -> @refs.lines.lineNodeForScreenRow(screenRow) + lineNodeForScreenRow: (screenRow) -> @linesComponent.lineNodeForScreenRow(screenRow) lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow) @@ -816,11 +814,6 @@ TextEditorComponent = React.createClass if scrollSensitivity = parseInt(scrollSensitivity) @scrollSensitivity = Math.abs(scrollSensitivity) / 100 - setUseHardwareAcceleration: (useHardwareAcceleration=true) -> - unless @useHardwareAcceleration is useHardwareAcceleration - @useHardwareAcceleration = useHardwareAcceleration - @requestUpdate() - screenPositionForMouseEvent: (event) -> pixelPosition = @pixelPositionForMouseEvent(event) @props.editor.screenPositionForPixelPosition(pixelPosition) @@ -829,7 +822,7 @@ TextEditorComponent = React.createClass {editor} = @props {clientX, clientY} = event - linesClientRect = @refs.lines.getDOMNode().getBoundingClientRect() + linesClientRect = @linesComponent.domNode.getBoundingClientRect() top = clientY - linesClientRect.top left = clientX - linesClientRect.left {top, left} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 56a2fad98..6e8417e49 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -631,6 +631,7 @@ class TextEditorPresenter clearScopedCharacterWidths: -> @characterWidthsByScope = {} + @model.clearScopedCharWidths() hasPixelPositionRequirements: -> @lineHeight? and @baseCharacterWidth? From 679cadabde945e436646496d2b3158a161c20696 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 09:52:46 -0700 Subject: [PATCH 076/126] Store maxLineNumberDigits in oldState --- src/gutter-component.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index cb443cf26..e70d1a25a 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -12,7 +12,6 @@ GutterComponent = React.createClass displayName: 'GutterComponent' mixins: [SubscriberMixin] - maxLineNumberDigits: null dummyLineNumberNode: null measuredWidth: null @@ -45,13 +44,14 @@ GutterComponent = React.createClass node.addEventListener 'click', @onClick node.addEventListener 'mousedown', @onMouseDown - componentDidUpdate: (oldProps) -> - {maxLineNumberDigits} = @newState - unless maxLineNumberDigits is @maxLineNumberDigits - @maxLineNumberDigits = maxLineNumberDigits + componentDidUpdate: -> + @updateSync() + + updateSync: -> + if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits @updateDummyLineNumber() node.remove() for id, node of @lineNumberNodesById - @oldState = {lineNumbers: {}} + @oldState = {maxLineNumberDigits: @newState.maxLineNumberDigits, lineNumbers: {}} @lineNumberNodesById = {} @updateLineNumbers() From 2d771ab8e4dd609adcb404177c23e42274b4f64d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 10:04:13 -0700 Subject: [PATCH 077/126] Perform GutterComponent DOM updates manually --- src/gutter-component.coffee | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index e70d1a25a..2adfcc652 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -16,29 +16,14 @@ GutterComponent = React.createClass measuredWidth: null render: -> - {presenter} = @props - @newState = presenter.state.gutter - @oldState ?= {lineNumbers: {}} - - {scrollHeight, backgroundColor} = @newState - div className: 'gutter', - div className: 'line-numbers', ref: 'lineNumbers', style: - height: scrollHeight - WebkitTransform: @getTransform() - backgroundColor: backgroundColor - - getTransform: -> - {scrollTop} = @newState - "translate3d(0px, #{-scrollTop}px, 0px)" + div className: 'line-numbers', ref: 'lineNumbers' componentWillMount: -> @lineNumberNodesById = {} componentDidMount: -> - {@maxLineNumberDigits} = @newState - @appendDummyLineNumber() - @updateLineNumbers() + @updateSync() node = @getDOMNode() node.addEventListener 'click', @onClick @@ -48,6 +33,24 @@ GutterComponent = React.createClass @updateSync() updateSync: -> + @newState = @props.presenter.state.gutter + @oldState ?= {lineNumbers: {}} + + @lineNumbersNode ?= @refs.lineNumbers.getDOMNode() + @appendDummyLineNumber() unless @dummyLineNumberNode? + + if @newState.scrollHeight isnt @oldState.scrollHeight + @lineNumbersNode.style.height = @newState.scrollHeight + 'px' + @oldState.scrollHeight = @newState.scrollHeight + + if @newState.scrollTop isnt @oldState.scrollTop + @lineNumbersNode.style['-webkit-transform'] = "translate3d(0px, #{-@newState.scrollTop}px, 0px)" + @oldState.scrollTop = @newState.scrollTop + + if @newState.backgroundColor isnt @oldState.backgroundColor + @lineNumbersNode.style.backgroundColor = @newState.backgroundColor + @oldState.backgroundColor = @newState.backgroundColor + if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits @updateDummyLineNumber() node.remove() for id, node of @lineNumberNodesById From 168df987d744b8c72bc74837ff8abb1469a4bec6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 10:29:05 -0700 Subject: [PATCH 078/126] Make GutterComponent a plain JS object instead of a React component --- spec/text-editor-component-spec.coffee | 10 ++--- src/gutter-component.coffee | 52 ++++++++++---------------- src/text-editor-component.coffee | 28 +++++++++----- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 832ca993d..ed910df5f 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -572,27 +572,27 @@ describe "TextEditorComponent", -> expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' it "hides or shows the gutter based on the '::isGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", -> - expect(component.refs.gutter?).toBe true + expect(component.gutterComponent?).toBe true editor.setGutterVisible(false) nextAnimationFrame() - expect(component.refs.gutter?).toBe false + expect(componentNode.querySelector('.gutter')).toBeNull() atom.config.set("editor.showLineNumbers", false) expect(nextAnimationFrame).toBe noAnimationFrame - expect(component.refs.gutter?).toBe false + expect(componentNode.querySelector('.gutter')).toBeNull() editor.setGutterVisible(true) expect(nextAnimationFrame).toBe noAnimationFrame - expect(component.refs.gutter?).toBe false + expect(componentNode.querySelector('.gutter')).toBeNull() atom.config.set("editor.showLineNumbers", true) nextAnimationFrame() - expect(component.refs.gutter?).toBe true + expect(componentNode.querySelector('.gutter')).toBeDefined() expect(component.lineNumberNodeForScreenRow(3)?).toBe true describe "fold decorations", -> diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 2adfcc652..3209fe806 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -1,42 +1,29 @@ _ = require 'underscore-plus' -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' -{isEqual, isEqualForProperties, multiplyString, toArray} = _ -Decoration = require './decoration' -SubscriberMixin = require './subscriber-mixin' WrapperDiv = document.createElement('div') module.exports = -GutterComponent = React.createClass - displayName: 'GutterComponent' - mixins: [SubscriberMixin] - +class GutterComponent dummyLineNumberNode: null - measuredWidth: null - render: -> - div className: 'gutter', - div className: 'line-numbers', ref: 'lineNumbers' - - componentWillMount: -> + constructor: ({@presenter, @onMouseDown, @editor}) -> @lineNumberNodesById = {} - componentDidMount: -> - @updateSync() + @domNode = document.createElement('div') + @domNode.classList.add('gutter') + @lineNumbersNode = document.createElement('div') + @lineNumbersNode.classList.add('line-numbers') + @domNode.appendChild(@lineNumbersNode) - node = @getDOMNode() - node.addEventListener 'click', @onClick - node.addEventListener 'mousedown', @onMouseDown + @domNode.addEventListener 'click', @onClick + @domNode.addEventListener 'mousedown', @onMouseDown - componentDidUpdate: -> @updateSync() updateSync: -> - @newState = @props.presenter.state.gutter + @newState = @presenter.state.gutter @oldState ?= {lineNumbers: {}} - @lineNumbersNode ?= @refs.lineNumbers.getDOMNode() @appendDummyLineNumber() unless @dummyLineNumberNode? if @newState.scrollHeight isnt @oldState.scrollHeight @@ -64,7 +51,7 @@ GutterComponent = React.createClass appendDummyLineNumber: -> WrapperDiv.innerHTML = @buildLineNumberHTML({bufferRow: -1}) @dummyLineNumberNode = WrapperDiv.children[0] - @refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode) + @lineNumbersNode.appendChild(@dummyLineNumberNode) updateDummyLineNumber: -> @dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false) @@ -85,9 +72,9 @@ GutterComponent = React.createClass if newLineNumberIds? WrapperDiv.innerHTML = newLineNumbersHTML - newLineNumberNodes = toArray(WrapperDiv.children) + newLineNumberNodes = _.toArray(WrapperDiv.children) - node = @refs.lineNumbers.getDOMNode() + node = @lineNumbersNode for id, i in newLineNumberIds lineNumberNode = newLineNumberNodes[i] @lineNumberNodesById[id] = lineNumberNode @@ -118,7 +105,7 @@ GutterComponent = React.createClass else lineNumber = (bufferRow + 1).toString() - padding = multiplyString(' ', maxLineNumberDigits - lineNumber.length) + padding = _.multiplyString(' ', maxLineNumberDigits - lineNumber.length) iconHTML = '
' padding + lineNumber + iconHTML @@ -149,21 +136,20 @@ GutterComponent = React.createClass return @lineNumberNodesById[id] null - onMouseDown: (event) -> + onMouseDown: (event) => {target} = event lineNumber = target.parentNode unless target.classList.contains('icon-right') and lineNumber.classList.contains('foldable') - @props.onMouseDown(event) + @onMouseDown(event) - onClick: (event) -> - {editor} = @props + onClick: (event) => {target} = event lineNumber = target.parentNode if target.classList.contains('icon-right') and lineNumber.classList.contains('foldable') bufferRow = parseInt(lineNumber.getAttribute('data-buffer-row')) if lineNumber.classList.contains('folded') - editor.unfoldBufferRow(bufferRow) + @editor.unfoldBufferRow(bufferRow) else - editor.foldBufferRow(bufferRow) + @editor.foldBufferRow(bufferRow) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 833491c30..2655ce5b0 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -40,6 +40,7 @@ TextEditorComponent = React.createClass measureLineHeightAndDefaultCharWidthWhenShown: true remeasureCharacterWidthsWhenShown: false stylingChangeAnimationFrameRequested: false + gutterComponent: null render: -> {focused, showLineNumbers} = @state @@ -62,12 +63,6 @@ TextEditorComponent = React.createClass className += ' has-selection' if hasSelection div {className, style}, - if @gutterVisible - GutterComponent { - ref: 'gutter', onMouseDown: @onGutterMouseDown, - @presenter, editor - } - div ref: 'scrollView', className: 'scroll-view', InputComponent ref: 'input' @@ -121,6 +116,8 @@ TextEditorComponent = React.createClass componentDidMount: -> {editor, stylesElement, hostElement, useShadowDOM} = @props + @mountGutterComponent() if @gutterVisible + @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM}) scrollViewNode = @refs.scrollView.getDOMNode() horizontalScrollbarNode = @refs.horizontalScrollbar.getDOMNode() @@ -158,6 +155,13 @@ TextEditorComponent = React.createClass @cursorMoved = false @selectionChanged = false + if @gutterVisible + @mountGutterComponent() unless @gutterComponent? + @gutterComponent.updateSync() + else + @gutterComponent?.domNode?.remove() + @gutterComponent = null + @linesComponent.updateSync(@isVisible()) if @props.editor.isAlive() @@ -167,6 +171,12 @@ TextEditorComponent = React.createClass @props.hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged @props.hostElement.__spacePenView.trigger 'editor:display-updated' + mountGutterComponent: -> + {editor} = @props + @gutterComponent = new GutterComponent({@presenter, editor, onMouseDown: @onGutterMouseDown}) + node = @getDOMNode() + node.insertBefore(@gutterComponent.domNode, node.firstChild) + becameVisible: -> @updatesPaused = true @measureScrollbars() if @measureScrollbarsWhenShown @@ -680,8 +690,8 @@ TextEditorComponent = React.createClass @presenter.setBackgroundColor(backgroundColor) - if @refs.gutter? - gutterBackgroundColor = getComputedStyle(@refs.gutter.getDOMNode()).backgroundColor + if @gutterComponent? + gutterBackgroundColor = getComputedStyle(@gutterComponent.domNode).backgroundColor @presenter.setGutterBackgroundColor(gutterBackgroundColor) measureLineHeightAndDefaultCharWidth: -> @@ -761,7 +771,7 @@ TextEditorComponent = React.createClass lineNodeForScreenRow: (screenRow) -> @linesComponent.lineNodeForScreenRow(screenRow) - lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow) + lineNumberNodeForScreenRow: (screenRow) -> @gutterComponent.lineNumberNodeForScreenRow(screenRow) screenRowForNode: (node) -> while node? From 8e27d8215a29e6d214a341cf8772caed66a3766e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 14:13:54 -0700 Subject: [PATCH 079/126] Store hidden input data in TextEditorPresenter::state --- spec/text-editor-presenter-spec.coffee | 63 ++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 33 ++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index b1ee17ef1..8837fd598 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -323,6 +323,69 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.contentHeight - presenter.clientHeight + describe ".hiddenInput", -> + describe ".top/.left", -> + it "is positioned over the last cursor it is in view and the editor is focused", -> + editor.setCursorBufferPosition([3, 6]) + presenter = buildPresenter(focused: false, explicitHeight: 50, contentFrameWidth: 300, horizontalScrollbarHeight: 0, verticalScrollbarWidth: 0) + expectValues presenter.state.hiddenInput, {top: 0, left: 0} + + expectStateUpdate presenter, -> presenter.setFocused(true) + expectValues presenter.state.hiddenInput, {top: 3 * 10, left: 6 * 10} + + expectStateUpdate presenter, -> presenter.setScrollTop(15) + expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: 6 * 10} + + expectStateUpdate presenter, -> presenter.setScrollLeft(35) + expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: (6 * 10) - 35} + + expectStateUpdate presenter, -> presenter.setScrollTop(40) + expectValues presenter.state.hiddenInput, {top: 0, left: (6 * 10) - 35} + + expectStateUpdate presenter, -> presenter.setScrollLeft(70) + expectValues presenter.state.hiddenInput, {top: 0, left: 0} + + expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43]) + expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10} + + newCursor = null + expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10]) + expectValues presenter.state.hiddenInput, {top: (6 * 10) - 40, left: (10 * 10) - 70} + + expectStateUpdate presenter, -> newCursor.destroy() + expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10} + + expectStateUpdate presenter, -> presenter.setFocused(false) + expectValues presenter.state.hiddenInput, {top: 0, left: 0} + + describe ".height", -> + it "is assigned based on the line height", -> + presenter = buildPresenter() + expect(presenter.state.hiddenInput.height).toBe 10 + + expectStateUpdate presenter, -> presenter.setLineHeight(20) + expect(presenter.state.hiddenInput.height).toBe 20 + + describe ".width", -> + it "is assigned based on the width of the character following the cursor", -> + waitsForPromise -> atom.packages.activatePackage('language-javascript') + + runs -> + editor.setCursorBufferPosition([3, 6]) + presenter = buildPresenter() + expect(presenter.state.hiddenInput.width).toBe 10 + + expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) + expect(presenter.state.hiddenInput.width).toBe 15 + + expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20) + expect(presenter.state.hiddenInput.width).toBe 20 + + it "is 2px at the end of lines", -> + presenter = buildPresenter() + editor.setCursorBufferPosition([3, Infinity]) + expect(presenter.state.hiddenInput.width).toBe 2 + describe ".content", -> describe ".scrollingVertically", -> it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 6e8417e49..5939beb73 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -86,6 +86,7 @@ class TextEditorPresenter @state = horizontalScrollbar: {} verticalScrollbar: {} + hiddenInput: {} content: scrollingVertically: false blinkCursorsOff: false @@ -106,6 +107,7 @@ class TextEditorPresenter @updateVerticalScrollState() @updateHorizontalScrollState() @updateScrollbarsState() + @updateHiddenInputState() @updateContentState() @updateDecorations() @updateLinesState() @@ -153,6 +155,25 @@ class TextEditorPresenter @emitter.emit 'did-update-state' + updateHiddenInputState: -> + return unless lastCursor = @model.getLastCursor() + + {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange()) + + if @focused + top -= @scrollTop + left -= @scrollLeft + @state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0) + @state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0) + else + @state.hiddenInput.top = 0 + @state.hiddenInput.left = 0 + + @state.hiddenInput.height = height + @state.hiddenInput.width = Math.max(width, 2) + + @emitter.emit 'did-update-state' + updateContentState: -> @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft @@ -448,6 +469,11 @@ class TextEditorPresenter getCursorBlinkResumeDelay: -> @cursorBlinkResumeDelay + setFocused: (focused) -> + unless @focused is focused + @focused = focused + @updateHiddenInputState() + setScrollTop: (scrollTop) -> scrollTop = @constrainScrollTop(scrollTop) @@ -458,6 +484,7 @@ class TextEditorPresenter @updateEndRow() @didStartScrolling() @updateVerticalScrollState() + @updateHiddenInputState() @updateDecorations() @updateLinesState() @updateCursorsState() @@ -487,6 +514,7 @@ class TextEditorPresenter @scrollLeft = scrollLeft @model.setScrollLeft(scrollLeft) @updateHorizontalScrollState() + @updateHiddenInputState() @updateCursorsState() unless oldScrollLeft? setHorizontalScrollbarHeight: (horizontalScrollbarHeight) -> @@ -576,6 +604,7 @@ class TextEditorPresenter @updateHorizontalScrollState() @updateVerticalScrollState() @updateScrollbarsState() + @updateHiddenInputState() @updateDecorations() @updateLinesState() @updateCursorsState() @@ -623,6 +652,7 @@ class TextEditorPresenter @updateHorizontalScrollState() @updateVerticalScrollState() @updateScrollbarsState() + @updateHiddenInputState() @updateContentState() @updateDecorations() @updateLinesState() @@ -886,6 +916,7 @@ class TextEditorPresenter observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => + @updateHiddenInputState() if cursor.isLastCursor() @pauseCursorBlinking() @updateCursorsState() @@ -895,6 +926,7 @@ class TextEditorPresenter @disposables.remove(didChangePositionDisposable) @disposables.remove(didChangeVisibilityDisposable) @disposables.remove(didDestroyDisposable) + @updateHiddenInputState() @updateCursorsState() @disposables.add(didChangePositionDisposable) @@ -903,6 +935,7 @@ class TextEditorPresenter didAddCursor: (cursor) -> @observeCursor(cursor) + @updateHiddenInputState() @pauseCursorBlinking() @updateCursorsState() From 52a9a76b2a64b4d94775e78896252091d25422e7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 14:57:11 -0700 Subject: [PATCH 080/126] Use presenter state in InputComponent --- spec/text-editor-component-spec.coffee | 3 ++- src/input-component.coffee | 8 +++----- src/text-editor-component.coffee | 19 +++---------------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index ed910df5f..35850c096 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1501,7 +1501,8 @@ describe "TextEditorComponent", -> expect(inputNode.offsetLeft).toBe 0 # In bounds and focused - inputNode.focus() # updates via state change + wrapperNode.focus() # updates via state change + nextAnimationFrame() expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - editor.getScrollTop() expect(inputNode.offsetLeft).toBe (4 * charWidth) - editor.getScrollLeft() diff --git a/src/input-component.coffee b/src/input-component.coffee index 776be6c14..4a0a666a2 100644 --- a/src/input-component.coffee +++ b/src/input-component.coffee @@ -7,8 +7,9 @@ InputComponent = React.createClass displayName: 'InputComponent' render: -> - {className, style} = @props - + {className} = @props + style = @props.presenter.state.hiddenInput + style.WebkitTransform ?= 'translateZ(0)' input {className, style, 'data-react-skip-selection-restoration': true} getInitialState: -> @@ -29,9 +30,6 @@ InputComponent = React.createClass isPressAndHoldCharacter: (char) -> @state.lastChar.match /[aeiouAEIOU]/ - shouldComponentUpdate: (newProps) -> - not isEqual(newProps.style, @props.style) - onPaste: (e) -> e.preventDefault() diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 2655ce5b0..7d38f94fc 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -51,8 +51,6 @@ TextEditorComponent = React.createClass @performedInitialMeasurement = false if editor.isDestroyed() if @performedInitialMeasurement - hiddenInputStyle = @getHiddenInputPosition() - hiddenInputStyle.WebkitTransform = 'translateZ(0)' style.height = @presenter.state.height if @presenter.state.height? if useShadowDOM @@ -67,7 +65,7 @@ TextEditorComponent = React.createClass InputComponent ref: 'input' className: 'hidden-input' - style: hiddenInputStyle + presenter: @presenter ScrollbarComponent ref: 'horizontalScrollbar' @@ -221,19 +219,6 @@ TextEditorComponent = React.createClass getTopmostDOMNode: -> @props.hostElement - getHiddenInputPosition: -> - {editor} = @props - {focused} = @state - return {top: 0, left: 0} unless @isMounted() and focused and editor.getLastCursor()? - - {top, left, height, width} = editor.getLastCursor().getPixelRect() - width = 2 if width is 0 # Prevent autoscroll at the end of longest line - top -= editor.getScrollTop() - left -= editor.getScrollLeft() - top = Math.max(0, Math.min(editor.getHeight() - height, top)) - left = Math.max(0, Math.min(editor.getWidth() - width, left)) - {top, left} - observeEditor: -> {editor} = @props @subscribe editor.onDidChangeGutterVisible(@updateGutterVisible) @@ -317,10 +302,12 @@ TextEditorComponent = React.createClass focused: -> if @isMounted() @setState(focused: true) + @presenter.setFocused(true) @refs.input.focus() blurred: -> if @isMounted() + @presenter.setFocused(false) @setState(focused: false) onTextInput: (event) -> From 0047e3bc2ddea5c4ff8ef6d7fe9a0e947ffcd853 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 15:36:13 -0700 Subject: [PATCH 081/126] Perform InputComponent DOM updates manually --- src/input-component.coffee | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/input-component.coffee b/src/input-component.coffee index 4a0a666a2..d04c5e419 100644 --- a/src/input-component.coffee +++ b/src/input-component.coffee @@ -8,9 +8,7 @@ InputComponent = React.createClass render: -> {className} = @props - style = @props.presenter.state.hiddenInput - style.WebkitTransform ?= 'translateZ(0)' - input {className, style, 'data-react-skip-selection-restoration': true} + input {className} getInitialState: -> {lastChar: ''} @@ -19,11 +17,33 @@ InputComponent = React.createClass node = @getDOMNode() node.addEventListener 'paste', @onPaste node.addEventListener 'compositionupdate', @onCompositionUpdate + node.setAttribute('data-react-skip-selection-restoration', true) + node.style['-webkit-transform'] = 'translateZ(0)' - # Don't let text accumulate in the input forever, but avoid excessive reflows componentDidUpdate: -> + node = @getDOMNode() + @oldState ?= {} + newState = @props.presenter.state.hiddenInput + + if newState.top isnt @oldState.top + node.style.top = newState.top + 'px' + @oldState.top = newState.top + + if newState.left isnt @oldState.left + node.style.left = newState.left + 'px' + @oldState.left = newState.left + + if newState.width isnt @oldState.width + node.style.width = newState.width + 'px' + @oldState.width = newState.width + + if newState.height isnt @oldState.height + node.style.height = newState.height + 'px' + @oldState.height = newState.height + + # Don't let text accumulate in the input forever, but avoid excessive reflows if @lastValueLength > 500 and not @isPressAndHoldCharacter(@state.lastChar) - @getDOMNode().value = '' + node.value = '' @lastValueLength = 0 # This should actually consult the property lists in /System/Library/Input Methods/PressAndHold.app From ea49fc6d5fad42932bc69acb52a1ea191bbf5d66 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 16:14:41 -0700 Subject: [PATCH 082/126] Construct LinesComponent with visibility param to avoid redundant update --- src/lines-component.coffee | 4 ++-- src/text-editor-component.coffee | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index e6c0733e1..961c3b983 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -14,7 +14,7 @@ module.exports = class LinesComponent placeholderTextDiv: null - constructor: ({@presenter, @hostElement, @useShadowDOM}) -> + constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> @measuredLines = new Set @lineNodesByLineId = {} @screenRowsByLineId = {} @@ -42,7 +42,7 @@ class LinesComponent else @overlayManager = new OverlayManager(@domNode) - @updateSync() + @updateSync(visible) updateSync: (visible) -> @newState = @presenter.state.content diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 7d38f94fc..69d6800bc 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -116,11 +116,10 @@ TextEditorComponent = React.createClass @mountGutterComponent() if @gutterVisible - @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM}) scrollViewNode = @refs.scrollView.getDOMNode() horizontalScrollbarNode = @refs.horizontalScrollbar.getDOMNode() + @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM, visible: @isVisible()}) scrollViewNode.insertBefore(@linesComponent.domNode, horizontalScrollbarNode) - @linesComponent.updateSync(@isVisible()) @observeEditor() @listenForDOMEvents() From 8784d48236ce4ad4b329cff1b753063ab9943d84 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Feb 2015 16:16:53 -0700 Subject: [PATCH 083/126] Use plain JS object for InputComponent instead of React --- src/input-component.coffee | 56 ++++++++------------------------ src/text-editor-component.coffee | 12 +++---- 2 files changed, 20 insertions(+), 48 deletions(-) diff --git a/src/input-component.coffee b/src/input-component.coffee index d04c5e419..8a788927a 100644 --- a/src/input-component.coffee +++ b/src/input-component.coffee @@ -1,57 +1,29 @@ -{last, isEqual} = require 'underscore-plus' -React = require 'react-atom-fork' -{input} = require 'reactionary-atom-fork' - module.exports = -InputComponent = React.createClass - displayName: 'InputComponent' +class InputComponent + constructor: (@presenter) -> + @domNode = document.createElement('input') + @domNode.classList.add('hidden-input') + @domNode.setAttribute('data-react-skip-selection-restoration', true) + @domNode.style['-webkit-transform'] = 'translateZ(0)' + @domNode.addEventListener 'paste', (event) => event.preventDefault() + @updateSync() - render: -> - {className} = @props - input {className} - - getInitialState: -> - {lastChar: ''} - - componentDidMount: -> - node = @getDOMNode() - node.addEventListener 'paste', @onPaste - node.addEventListener 'compositionupdate', @onCompositionUpdate - node.setAttribute('data-react-skip-selection-restoration', true) - node.style['-webkit-transform'] = 'translateZ(0)' - - componentDidUpdate: -> - node = @getDOMNode() + updateSync: -> @oldState ?= {} - newState = @props.presenter.state.hiddenInput + newState = @presenter.state.hiddenInput if newState.top isnt @oldState.top - node.style.top = newState.top + 'px' + @domNode.style.top = newState.top + 'px' @oldState.top = newState.top if newState.left isnt @oldState.left - node.style.left = newState.left + 'px' + @domNode.style.left = newState.left + 'px' @oldState.left = newState.left if newState.width isnt @oldState.width - node.style.width = newState.width + 'px' + @domNode.style.width = newState.width + 'px' @oldState.width = newState.width if newState.height isnt @oldState.height - node.style.height = newState.height + 'px' + @domNode.style.height = newState.height + 'px' @oldState.height = newState.height - - # Don't let text accumulate in the input forever, but avoid excessive reflows - if @lastValueLength > 500 and not @isPressAndHoldCharacter(@state.lastChar) - node.value = '' - @lastValueLength = 0 - - # This should actually consult the property lists in /System/Library/Input Methods/PressAndHold.app - isPressAndHoldCharacter: (char) -> - @state.lastChar.match /[aeiouAEIOU]/ - - onPaste: (e) -> - e.preventDefault() - - focus: -> - @getDOMNode().focus() diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 69d6800bc..2f560d67c 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -62,11 +62,6 @@ TextEditorComponent = React.createClass div {className, style}, div ref: 'scrollView', className: 'scroll-view', - InputComponent - ref: 'input' - className: 'hidden-input' - presenter: @presenter - ScrollbarComponent ref: 'horizontalScrollbar' className: 'horizontal-scrollbar' @@ -118,6 +113,10 @@ TextEditorComponent = React.createClass scrollViewNode = @refs.scrollView.getDOMNode() horizontalScrollbarNode = @refs.horizontalScrollbar.getDOMNode() + + @hiddenInputComponent = new InputComponent(@presenter) + scrollViewNode.insertBefore(@hiddenInputComponent.domNode, horizontalScrollbarNode) + @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM, visible: @isVisible()}) scrollViewNode.insertBefore(@linesComponent.domNode, horizontalScrollbarNode) @@ -159,6 +158,7 @@ TextEditorComponent = React.createClass @gutterComponent?.domNode?.remove() @gutterComponent = null + @hiddenInputComponent.updateSync() @linesComponent.updateSync(@isVisible()) if @props.editor.isAlive() @@ -302,7 +302,7 @@ TextEditorComponent = React.createClass if @isMounted() @setState(focused: true) @presenter.setFocused(true) - @refs.input.focus() + @hiddenInputComponent.domNode.focus() blurred: -> if @isMounted() From 8552acaec261b3d24f535f0954ed7c21e366fedf Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 12 Feb 2015 12:24:55 -0700 Subject: [PATCH 084/126] :racehorse: Optimize line node updates --- src/lines-component.coffee | 42 +++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 961c3b983..f15ad9fd0 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -10,6 +10,11 @@ DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibi AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} WrapperDiv = document.createElement('div') +cloneObject = (object) -> + clone = {} + clone[key] = value for key, value of object + clone + module.exports = class LinesComponent placeholderTextDiv: null @@ -52,10 +57,6 @@ class LinesComponent @domNode.style.height = @newState.scrollHeight + 'px' @oldState.scrollHeight = @newState.scrollHeight - if @newState.scrollWidth isnt @oldState.scrollWidth - @domNode.style.width = @newState.scrollWidth + 'px' - @oldState.scrollWidth = @newState.scrollWidth - if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft @domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, #{-@newState.scrollTop}px, 0px)" @oldState.scrollTop = @newState.scrollTop @@ -75,6 +76,11 @@ class LinesComponent @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible @updateLineNodes() + + if @newState.scrollWidth isnt @oldState.scrollWidth + @domNode.style.width = @newState.scrollWidth + 'px' + @oldState.scrollWidth = @newState.scrollWidth + @measureCharactersInNewLines() if visible and not @newState.scrollingVertically @cursorsComponent.updateSync() @@ -113,7 +119,7 @@ class LinesComponent newLinesHTML += @buildLineHTML(id) @screenRowsByLineId[id] = lineState.screenRow @lineIdsByScreenRow[lineState.screenRow] = id - @oldState.lines[id] = _.clone(lineState) + @oldState.lines[id] = cloneObject(lineState) return unless newLineIds? @@ -218,13 +224,16 @@ class LinesComponent "" updateLineNode: (id) -> - {scrollWidth} = @newState - {screenRow, top} = @newState.lines[id] + oldLineState = @oldState.lines[id] + newLineState = @newState.lines[id] lineNode = @lineNodesByLineId[id] - newDecorationClasses = @newState.lines[id].decorationClasses - oldDecorationClasses = @oldState.lines[id].decorationClasses + if @newState.scrollWidth isnt @oldState.scrollWidth + lineNode.style.width = @newState.scrollWidth + 'px' + + newDecorationClasses = newLineState.decorationClasses + oldDecorationClasses = oldLineState.decorationClasses if oldDecorationClasses? for decorationClass in oldDecorationClasses @@ -236,11 +245,16 @@ class LinesComponent unless oldDecorationClasses? and decorationClass in oldDecorationClasses lineNode.classList.add(decorationClass) - lineNode.style.width = scrollWidth + 'px' - lineNode.style.top = top + 'px' - lineNode.dataset.screenRow = screenRow - @screenRowsByLineId[id] = screenRow - @lineIdsByScreenRow[screenRow] = id + oldLineState.decorationClasses = newLineState.decorationClasses + + if newLineState.top isnt oldLineState.top + lineNode.style.top = newLineState.top + 'px' + oldLineState.top = newLineState.cop + + if newLineState.screenRow isnt oldLineState.screenRow + lineNode.dataset.screenRow = newLineState.screenRow + oldLineState.screenRow = newLineState.screenRow + @lineIdsByScreenRow[newLineState.screenRow] = id lineNodeForScreenRow: (screenRow) -> @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] From d9e100cdf808644707b2b6f94a9826372ba8b201 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 12 Feb 2015 12:39:21 -0700 Subject: [PATCH 085/126] Perform ScrollbarComponent DOM updates manually --- src/scrollbar-component.coffee | 81 +++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index 4447acc8d..b65e8b51f 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -7,33 +7,19 @@ ScrollbarComponent = React.createClass displayName: 'ScrollbarComponent' render: -> - {presenter, orientation, className} = @props - - switch orientation - when 'vertical' - @newState = presenter.state.verticalScrollbar - when 'horizontal' - @newState = presenter.state.horizontalScrollbar + {orientation, className} = @props style = {} - style.display = 'none' unless @newState.visible style.transform = 'translateZ(0)' # See atom/atom#3559 - switch orientation - when 'vertical' - style.width = @newState.width - style.bottom = @newState.bottom - when 'horizontal' - style.left = 0 - style.right = @newState.right - style.height = @newState.height + style.left = 0 if orientation is 'horizontal' div {className, style}, switch orientation when 'vertical' - div className: 'scrollbar-content', style: {height: @newState.scrollHeight} + div ref: 'content', className: 'scrollbar-content' when 'horizontal' - div className: 'scrollbar-content', style: {width: @newState.scrollWidth} + div ref: 'content', className: 'scrollbar-content' componentDidMount: -> {orientation} = @props @@ -43,18 +29,71 @@ ScrollbarComponent = React.createClass @getDOMNode().addEventListener 'scroll', @onScroll + @updateSync() + componentWillUnmount: -> @getDOMNode().removeEventListener 'scroll', @onScroll componentDidUpdate: -> - {orientation} = @props + @updateSync() + + updateSync: -> + {presenter, orientation} = @props node = @getDOMNode() + @oldState ?= {} switch orientation when 'vertical' - node.scrollTop = @newState.scrollTop + @newState = presenter.state.verticalScrollbar + @updateVertical() when 'horizontal' - node.scrollLeft = @newState.scrollLeft + @newState = presenter.state.horizontalScrollbar + @updateHorizontal() + + if @newState.visible isnt @oldState.visible + if @newState.visible + node.style.display = '' + else + node.style.display = 'none' + @oldState.visible = @newState.visible + + updateVertical: -> + node = @getDOMNode() + + if @newState.width isnt @oldState.width + node.style.width = @newState.width + 'px' + @oldState.width = @newState.width + + if @newState.bottom isnt @oldState.bottom + node.style.bottom = @newState.bottom + 'px' + @oldState.bottom = @newState.bottom + + if @newState.scrollTop isnt @oldState.scrollTop + node.scrollTop = @newState.scrollTop + @oldState = @newState.scrollTop + + if @newState.scrollHeight isnt @oldState.scrollHeight + @refs.content.getDOMNode().style.height = @newState.scrollHeight + 'px' + @oldState = @newState.scrollHeight + + updateHorizontal: -> + node = @getDOMNode() + + if @newState.height isnt @oldState.height + node.style.height = @newState.height + 'px' + @oldState.height = @newState.height + + if @newState.right isnt @oldState.right + node.style.right = @newState.right + 'px' + @oldState.right = @newState.right + + if @newState.scrollLeft isnt @oldState.scrollLeft + node.scrollLeft = @newState.scrollLeft + @oldState = @newState.scrollLeft + + if @newState.scrollWidth isnt @oldState.scrollWidth + @refs.content.getDOMNode().style.width = @newState.scrollWidth + 'px' + @oldState = @newState.scrollWidth onScroll: -> {orientation, onScroll} = @props From da6bd3664d66462ca454655fb49a463776c0b2d4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 12 Feb 2015 13:54:19 -0700 Subject: [PATCH 086/126] Use plain JS object for ScrollbarComponent instead of React --- src/scrollbar-component.coffee | 89 ++++++++++---------------------- src/text-editor-component.coffee | 36 ++++++------- 2 files changed, 43 insertions(+), 82 deletions(-) diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index b65e8b51f..dbad89cf7 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -1,108 +1,73 @@ -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' -{extend, isEqualForProperties} = require 'underscore-plus' - module.exports = -ScrollbarComponent = React.createClass - displayName: 'ScrollbarComponent' +class ScrollbarComponent + constructor: ({@presenter, @orientation, @onScroll}) -> + @domNode = document.createElement('div') + @domNode.classList.add "#{@orientation}-scrollbar" + @domNode.style['-webkit-transform'] = 'translateZ(0)' # See atom/atom#3559 + @domNode.style.left = 0 if @orientation is 'horizontal' - render: -> - {orientation, className} = @props + @contentNode = document.createElement('div') + @contentNode.classList.add "scrollbar-content" + @domNode.appendChild(@contentNode) - style = {} + @domNode.addEventListener 'scroll', @onScrollCallback - style.transform = 'translateZ(0)' # See atom/atom#3559 - style.left = 0 if orientation is 'horizontal' - - div {className, style}, - switch orientation - when 'vertical' - div ref: 'content', className: 'scrollbar-content' - when 'horizontal' - div ref: 'content', className: 'scrollbar-content' - - componentDidMount: -> - {orientation} = @props - - unless orientation is 'vertical' or orientation is 'horizontal' - throw new Error("Must specify an orientation property of 'vertical' or 'horizontal'") - - @getDOMNode().addEventListener 'scroll', @onScroll - - @updateSync() - - componentWillUnmount: -> - @getDOMNode().removeEventListener 'scroll', @onScroll - - componentDidUpdate: -> @updateSync() updateSync: -> - {presenter, orientation} = @props - node = @getDOMNode() - @oldState ?= {} - switch orientation + switch @orientation when 'vertical' - @newState = presenter.state.verticalScrollbar + @newState = @presenter.state.verticalScrollbar @updateVertical() when 'horizontal' - @newState = presenter.state.horizontalScrollbar + @newState = @presenter.state.horizontalScrollbar @updateHorizontal() if @newState.visible isnt @oldState.visible if @newState.visible - node.style.display = '' + @domNode.style.display = '' else - node.style.display = 'none' + @domNode.style.display = 'none' @oldState.visible = @newState.visible updateVertical: -> - node = @getDOMNode() - if @newState.width isnt @oldState.width - node.style.width = @newState.width + 'px' + @domNode.style.width = @newState.width + 'px' @oldState.width = @newState.width if @newState.bottom isnt @oldState.bottom - node.style.bottom = @newState.bottom + 'px' + @domNode.style.bottom = @newState.bottom + 'px' @oldState.bottom = @newState.bottom if @newState.scrollTop isnt @oldState.scrollTop - node.scrollTop = @newState.scrollTop + @domNode.scrollTop = @newState.scrollTop @oldState = @newState.scrollTop if @newState.scrollHeight isnt @oldState.scrollHeight - @refs.content.getDOMNode().style.height = @newState.scrollHeight + 'px' + @contentNode.style.height = @newState.scrollHeight + 'px' @oldState = @newState.scrollHeight updateHorizontal: -> - node = @getDOMNode() - if @newState.height isnt @oldState.height - node.style.height = @newState.height + 'px' + @domNode.style.height = @newState.height + 'px' @oldState.height = @newState.height if @newState.right isnt @oldState.right - node.style.right = @newState.right + 'px' + @domNode.style.right = @newState.right + 'px' @oldState.right = @newState.right if @newState.scrollLeft isnt @oldState.scrollLeft - node.scrollLeft = @newState.scrollLeft + @domNode.scrollLeft = @newState.scrollLeft @oldState = @newState.scrollLeft if @newState.scrollWidth isnt @oldState.scrollWidth - @refs.content.getDOMNode().style.width = @newState.scrollWidth + 'px' + @contentNode.style.width = @newState.scrollWidth + 'px' @oldState = @newState.scrollWidth - onScroll: -> - {orientation, onScroll} = @props - node = @getDOMNode() - - switch orientation + onScrollCallback: => + switch @orientation when 'vertical' - scrollTop = node.scrollTop - onScroll(scrollTop) + @onScroll(@domNode.scrollTop) when 'horizontal' - scrollLeft = node.scrollLeft - onScroll(scrollLeft) + @onScroll(@domNode.scrollLeft) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 2f560d67c..eb445c6a4 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -61,20 +61,7 @@ TextEditorComponent = React.createClass className += ' has-selection' if hasSelection div {className, style}, - div ref: 'scrollView', className: 'scroll-view', - ScrollbarComponent - ref: 'horizontalScrollbar' - className: 'horizontal-scrollbar' - orientation: 'horizontal' - presenter: @presenter - onScroll: @onHorizontalScroll - - ScrollbarComponent - ref: 'verticalScrollbar' - className: 'vertical-scrollbar' - orientation: 'vertical' - presenter: @presenter - onScroll: @onVerticalScroll + div ref: 'scrollView', className: 'scroll-view' # Also used to measure the height/width of scrollbars after the initial render ScrollbarCornerComponent @@ -111,14 +98,21 @@ TextEditorComponent = React.createClass @mountGutterComponent() if @gutterVisible + node = @getDOMNode() scrollViewNode = @refs.scrollView.getDOMNode() - horizontalScrollbarNode = @refs.horizontalScrollbar.getDOMNode() + scrollbarCornerNode = @refs.scrollbarCorner.getDOMNode() @hiddenInputComponent = new InputComponent(@presenter) - scrollViewNode.insertBefore(@hiddenInputComponent.domNode, horizontalScrollbarNode) + scrollViewNode.appendChild(@hiddenInputComponent.domNode) @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM, visible: @isVisible()}) - scrollViewNode.insertBefore(@linesComponent.domNode, horizontalScrollbarNode) + scrollViewNode.appendChild(@linesComponent.domNode) + + @horizontalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'horizontal', onScroll: @onHorizontalScroll}) + scrollViewNode.appendChild(@horizontalScrollbarComponent.domNode) + + @verticalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'vertical', onScroll: @onVerticalScroll}) + node.insertBefore(@verticalScrollbarComponent.domNode, scrollbarCornerNode) @observeEditor() @listenForDOMEvents() @@ -160,6 +154,8 @@ TextEditorComponent = React.createClass @hiddenInputComponent.updateSync() @linesComponent.updateSync(@isVisible()) + @horizontalScrollbarComponent.updateSync() + @verticalScrollbarComponent.updateSync() if @props.editor.isAlive() @updateParentViewFocusedClassIfNeeded(prevState) @@ -724,10 +720,10 @@ TextEditorComponent = React.createClass @measureScrollbarsWhenShown = true return - {verticalScrollbar, horizontalScrollbar, scrollbarCorner} = @refs + {scrollbarCorner} = @refs - verticalNode = verticalScrollbar.getDOMNode() - horizontalNode = horizontalScrollbar.getDOMNode() + verticalNode = @verticalScrollbarComponent.domNode + horizontalNode = @horizontalScrollbarComponent.domNode cornerNode = scrollbarCorner.getDOMNode() originalVerticalDisplayValue = verticalNode.style.display From 77a4482263f137a76a6a606757ea3dafa8ac87d9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 12 Feb 2015 17:16:49 -0700 Subject: [PATCH 087/126] Fix assignment of oldState values in ScrollbarComponent Signed-off-by: Max Brunsfeld --- src/scrollbar-component.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index dbad89cf7..8eef5c3e6 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -42,11 +42,11 @@ class ScrollbarComponent if @newState.scrollTop isnt @oldState.scrollTop @domNode.scrollTop = @newState.scrollTop - @oldState = @newState.scrollTop + @oldState.scrollTop = @newState.scrollTop if @newState.scrollHeight isnt @oldState.scrollHeight @contentNode.style.height = @newState.scrollHeight + 'px' - @oldState = @newState.scrollHeight + @oldState.scrollHeight = @newState.scrollHeight updateHorizontal: -> if @newState.height isnt @oldState.height @@ -59,11 +59,11 @@ class ScrollbarComponent if @newState.scrollLeft isnt @oldState.scrollLeft @domNode.scrollLeft = @newState.scrollLeft - @oldState = @newState.scrollLeft + @oldState.scrollLeft = @newState.scrollLeft if @newState.scrollWidth isnt @oldState.scrollWidth @contentNode.style.width = @newState.scrollWidth + 'px' - @oldState = @newState.scrollWidth + @oldState.scrollWidth = @newState.scrollWidth onScrollCallback: => switch @orientation From 6b3d29a5e450cf80fe8cda36b90dfbb91fa0de47 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 12 Feb 2015 17:35:38 -0700 Subject: [PATCH 088/126] Manually update DOM in ScrollbarCornerComponent Signed-off-by: Max Brunsfeld --- src/scrollbar-corner-component.coffee | 48 ++++++++++++++++++++------- src/text-editor-component.coffee | 1 - 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/scrollbar-corner-component.coffee b/src/scrollbar-corner-component.coffee index 706373ec2..97788e729 100644 --- a/src/scrollbar-corner-component.coffee +++ b/src/scrollbar-corner-component.coffee @@ -7,19 +7,43 @@ ScrollbarCornerComponent = React.createClass displayName: 'ScrollbarCornerComponent' render: -> - {presenter, measuringScrollbars} = @props + div className: 'scrollbar-corner', + div ref: 'content' - visible = presenter.state.horizontalScrollbar.visible and presenter.state.verticalScrollbar.visible - width = presenter.state.verticalScrollbar.width - height = presenter.state.horizontalScrollbar.height + componentDidMount: -> + @updateSync() - if measuringScrollbars - height = 25 - width = 25 + componentDidUpdate: -> + @updateSync() - display = 'none' unless visible + updateSync: -> + {presenter} = @props - div className: 'scrollbar-corner', style: {display, width, height}, - div style: - height: height + 1 - width: width + 1 + @oldState ?= {} + @newState ?= {} + + newHorizontalState = presenter.state.horizontalScrollbar + newVerticalState = presenter.state.verticalScrollbar + @newState.visible = newHorizontalState.visible and newVerticalState.visible + @newState.height = newHorizontalState.height + @newState.width = newVerticalState.width + + node = @getDOMNode() + contentNode = @refs.content.getDOMNode() + + if @newState.visible isnt @oldState.visible + if @newState.visible + node.style.display = '' + else + node.style.display = 'none' + @oldState.visible = @newState.visible + + if @newState.height isnt @oldState.height + node.style.height = @newState.height + 'px' + contentNode.style.height = @newState.height + 1 + 'px' + @oldState.height = @newState.height + + if @newState.width isnt @oldState.width + node.style.width = @newState.width + 'px' + contentNode.style.width = @newState.width + 1 + 'px' + @oldState.width = @newState.width diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index eb445c6a4..65b5edead 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -67,7 +67,6 @@ TextEditorComponent = React.createClass ScrollbarCornerComponent ref: 'scrollbarCorner' presenter: @presenter - measuringScrollbars: @measuringScrollbars getInitialState: -> {} From 5c7e0c387a16ff1f1f1d50d6f6635b62c7ac7f92 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 12 Feb 2015 17:41:46 -0700 Subject: [PATCH 089/126] Use plain JS object for ScrollbarCornerComponent instead of React Signed-off-by: Max Brunsfeld --- src/scrollbar-corner-component.coffee | 40 ++++++++++----------------- src/text-editor-component.coffee | 18 +++++------- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/src/scrollbar-corner-component.coffee b/src/scrollbar-corner-component.coffee index 97788e729..1a266afc5 100644 --- a/src/scrollbar-corner-component.coffee +++ b/src/scrollbar-corner-component.coffee @@ -1,49 +1,37 @@ -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' -{isEqualForProperties} = require 'underscore-plus' - module.exports = -ScrollbarCornerComponent = React.createClass - displayName: 'ScrollbarCornerComponent' +class ScrollbarCornerComponent + constructor: (@presenter) -> + @domNode = document.createElement('div') + @domNode.classList.add('scrollbar-corner') - render: -> - div className: 'scrollbar-corner', - div ref: 'content' + @contentNode = document.createElement('div') + @domNode.appendChild(@contentNode) - componentDidMount: -> - @updateSync() - - componentDidUpdate: -> @updateSync() updateSync: -> - {presenter} = @props - @oldState ?= {} @newState ?= {} - newHorizontalState = presenter.state.horizontalScrollbar - newVerticalState = presenter.state.verticalScrollbar + newHorizontalState = @presenter.state.horizontalScrollbar + newVerticalState = @presenter.state.verticalScrollbar @newState.visible = newHorizontalState.visible and newVerticalState.visible @newState.height = newHorizontalState.height @newState.width = newVerticalState.width - node = @getDOMNode() - contentNode = @refs.content.getDOMNode() - if @newState.visible isnt @oldState.visible if @newState.visible - node.style.display = '' + @domNode.style.display = '' else - node.style.display = 'none' + @domNode.style.display = 'none' @oldState.visible = @newState.visible if @newState.height isnt @oldState.height - node.style.height = @newState.height + 'px' - contentNode.style.height = @newState.height + 1 + 'px' + @domNode.style.height = @newState.height + 'px' + @contentNode.style.height = @newState.height + 1 + 'px' @oldState.height = @newState.height if @newState.width isnt @oldState.width - node.style.width = @newState.width + 'px' - contentNode.style.width = @newState.width + 1 + 'px' + @domNode.style.width = @newState.width + 'px' + @contentNode.style.width = @newState.width + 1 + 'px' @oldState.width = @newState.width diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 65b5edead..7ac793b7d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -63,11 +63,6 @@ TextEditorComponent = React.createClass div {className, style}, div ref: 'scrollView', className: 'scroll-view' - # Also used to measure the height/width of scrollbars after the initial render - ScrollbarCornerComponent - ref: 'scrollbarCorner' - presenter: @presenter - getInitialState: -> {} getDefaultProps: -> @@ -99,7 +94,6 @@ TextEditorComponent = React.createClass node = @getDOMNode() scrollViewNode = @refs.scrollView.getDOMNode() - scrollbarCornerNode = @refs.scrollbarCorner.getDOMNode() @hiddenInputComponent = new InputComponent(@presenter) scrollViewNode.appendChild(@hiddenInputComponent.domNode) @@ -111,7 +105,10 @@ TextEditorComponent = React.createClass scrollViewNode.appendChild(@horizontalScrollbarComponent.domNode) @verticalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'vertical', onScroll: @onVerticalScroll}) - node.insertBefore(@verticalScrollbarComponent.domNode, scrollbarCornerNode) + node.appendChild(@verticalScrollbarComponent.domNode) + + @scrollbarCornerComponent = new ScrollbarCornerComponent(@presenter) + node.appendChild(@scrollbarCornerComponent.domNode) @observeEditor() @listenForDOMEvents() @@ -155,6 +152,7 @@ TextEditorComponent = React.createClass @linesComponent.updateSync(@isVisible()) @horizontalScrollbarComponent.updateSync() @verticalScrollbarComponent.updateSync() + @scrollbarCornerComponent.updateSync() if @props.editor.isAlive() @updateParentViewFocusedClassIfNeeded(prevState) @@ -693,7 +691,7 @@ TextEditorComponent = React.createClass @measureScrollbarsWhenShown = false {editor} = @props - cornerNode = @refs.scrollbarCorner.getDOMNode() + cornerNode = @scrollbarCornerComponent.domNode originalDisplayValue = cornerNode.style.display cornerNode.style.display = 'block' @@ -719,11 +717,9 @@ TextEditorComponent = React.createClass @measureScrollbarsWhenShown = true return - {scrollbarCorner} = @refs - verticalNode = @verticalScrollbarComponent.domNode horizontalNode = @horizontalScrollbarComponent.domNode - cornerNode = scrollbarCorner.getDOMNode() + cornerNode = @scrollbarCornerComponent.domNode originalVerticalDisplayValue = verticalNode.style.display originalHorizontalDisplayValue = horizontalNode.style.display From 4654bad543d54ddd8844ca7a94731852dc913dce Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 12 Feb 2015 17:48:28 -0700 Subject: [PATCH 090/126] Add .focused to presenter state Signed-off-by: Max Brunsfeld --- spec/text-editor-presenter-spec.coffee | 9 +++++++++ src/text-editor-presenter.coffee | 7 ++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 8837fd598..71f5a2283 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1987,6 +1987,15 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") expect(presenter.state.height).toBe editor.getScreenLineCount() * 20 + describe ".focused", -> + it "tracks the value of ::focused", -> + presenter = buildPresenter(focused: false) + expect(presenter.state.focused).toBe false + expectStateUpdate presenter, -> presenter.setFocused(true) + expect(presenter.state.focused).toBe true + expectStateUpdate presenter, -> presenter.setFocused(false) + expect(presenter.state.focused).toBe false + # disabled until we fix an issue with display buffer markers not updating when # they are moved on screen but not in the buffer xdescribe "when the model and view measurements are mutated randomly", -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5939beb73..606a8d8bf 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -14,7 +14,7 @@ class TextEditorPresenter {@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft} = params {horizontalScrollbarHeight, verticalScrollbarWidth} = params {@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params - {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay} = params + {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @@ -103,6 +103,7 @@ class TextEditorPresenter @updateStartRow() @updateEndRow() + @updateFocusedState() @updateHeightState() @updateVerticalScrollState() @updateHorizontalScrollState() @@ -116,6 +117,9 @@ class TextEditorPresenter @updateGutterState() @updateLineNumbersState() + updateFocusedState: -> + @state.focused = @focused + updateHeightState: -> if @autoHeight @state.height = @contentHeight @@ -472,6 +476,7 @@ class TextEditorPresenter setFocused: (focused) -> unless @focused is focused @focused = focused + @updateFocusedState() @updateHiddenInputState() setScrollTop: (scrollTop) -> From bf29a020e1eef999ca8c8976b370bce4899c3882 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 14 Feb 2015 09:03:57 -0700 Subject: [PATCH 091/126] Use presenter for focused state in EditorComponent --- spec/text-editor-component-spec.coffee | 4 ++++ src/text-editor-component.coffee | 27 ++++++++++++++------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 35850c096..9f221b54b 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1508,6 +1508,7 @@ describe "TextEditorComponent", -> # In bounds, not focused inputNode.blur() # updates via state change + nextAnimationFrame() expect(inputNode.offsetTop).toBe 0 expect(inputNode.offsetLeft).toBe 0 @@ -1519,6 +1520,7 @@ describe "TextEditorComponent", -> # Out of bounds, focused inputNode.focus() # updates via state change + nextAnimationFrame() expect(inputNode.offsetTop).toBe 0 expect(inputNode.offsetLeft).toBe 0 @@ -1840,9 +1842,11 @@ describe "TextEditorComponent", -> it "adds the 'is-focused' class to the editor when the hidden input is focused", -> expect(document.activeElement).toBe document.body inputNode.focus() + nextAnimationFrame() expect(componentNode.classList.contains('is-focused')).toBe true expect(wrapperView.hasClass('is-focused')).toBe true inputNode.blur() + nextAnimationFrame() expect(componentNode.classList.contains('is-focused')).toBe false expect(wrapperView.hasClass('is-focused')).toBe false diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 7ac793b7d..784748f36 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -43,7 +43,10 @@ TextEditorComponent = React.createClass gutterComponent: null render: -> - {focused, showLineNumbers} = @state + @oldState ?= {} + @newState = @presenter.state + + {showLineNumbers} = @state {editor, cursorBlinkPeriod, cursorBlinkResumeDelay, hostElement, useShadowDOM} = @props hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty() style = {} @@ -57,7 +60,7 @@ TextEditorComponent = React.createClass className = 'editor-contents--private' else className = 'editor-contents' - className += ' is-focused' if focused + className += ' is-focused' if @newState.focused className += ' has-selection' if hasSelection div {className, style}, @@ -121,7 +124,8 @@ TextEditorComponent = React.createClass @subscribe scrollbarStyle.changes, @refreshScrollbars @domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval) - @updateParentViewFocusedClassIfNeeded({}) + + @updateParentViewFocusedClassIfNeeded() @updateParentViewMiniClass() @checkForVisibilityChange() @@ -135,7 +139,7 @@ TextEditorComponent = React.createClass clearInterval(@domPollingIntervalId) @domPollingIntervalId = null - componentDidUpdate: (prevProps, prevState) -> + componentDidUpdate: -> cursorMoved = @cursorMoved selectionChanged = @selectionChanged @cursorMoved = false @@ -155,7 +159,7 @@ TextEditorComponent = React.createClass @scrollbarCornerComponent.updateSync() if @props.editor.isAlive() - @updateParentViewFocusedClassIfNeeded(prevState) + @updateParentViewFocusedClassIfNeeded() @updateParentViewMiniClass() @props.hostElement.__spacePenView.trigger 'cursor:moved' if cursorMoved @props.hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged @@ -293,14 +297,12 @@ TextEditorComponent = React.createClass focused: -> if @isMounted() - @setState(focused: true) @presenter.setFocused(true) @hiddenInputComponent.domNode.focus() blurred: -> if @isMounted() @presenter.setFocused(false) - @setState(focused: false) onTextInput: (event) -> event.stopPropagation() @@ -399,7 +401,7 @@ TextEditorComponent = React.createClass return if ctrlKey and process.platform is 'darwin' # Prevent focusout event on hidden input if editor is already focused - event.preventDefault() if @state.focused + event.preventDefault() if @oldState.focused screenPosition = @screenPositionForMouseEvent(event) @@ -821,10 +823,11 @@ TextEditorComponent = React.createClass setInputEnabled: (@inputEnabled) -> @inputEnabled - updateParentViewFocusedClassIfNeeded: (prevState) -> - if prevState.focused isnt @state.focused - @props.hostElement.classList.toggle('is-focused', @state.focused) - @props.rootElement.classList.toggle('is-focused', @state.focused) + updateParentViewFocusedClassIfNeeded: -> + if @oldState.focused isnt @newState.focused + @props.hostElement.classList.toggle('is-focused', @newState.focused) + @props.rootElement.classList.toggle('is-focused', @newState.focused) + @oldState.focused = @newState.focused updateParentViewMiniClass: -> @props.hostElement.classList.toggle('mini', @props.editor.isMini()) From fd603a0cbcbd9dcc599564aae2858391a59f29f1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 14 Feb 2015 09:33:03 -0700 Subject: [PATCH 092/126] Move new character measurement to end of full update to avoid reflow --- src/lines-component.coffee | 4 +--- src/text-editor-component.coffee | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index f15ad9fd0..d6498772d 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -49,7 +49,7 @@ class LinesComponent @updateSync(visible) - updateSync: (visible) -> + updateSync: -> @newState = @presenter.state.content @oldState ?= {lines: {}} @@ -81,8 +81,6 @@ class LinesComponent @domNode.style.width = @newState.scrollWidth + 'px' @oldState.scrollWidth = @newState.scrollWidth - @measureCharactersInNewLines() if visible and not @newState.scrollingVertically - @cursorsComponent.updateSync() @highlightsComponent.updateSync() diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 784748f36..663e08a19 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -101,7 +101,7 @@ TextEditorComponent = React.createClass @hiddenInputComponent = new InputComponent(@presenter) scrollViewNode.appendChild(@hiddenInputComponent.domNode) - @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM, visible: @isVisible()}) + @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM}) scrollViewNode.appendChild(@linesComponent.domNode) @horizontalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'horizontal', onScroll: @onHorizontalScroll}) @@ -153,7 +153,7 @@ TextEditorComponent = React.createClass @gutterComponent = null @hiddenInputComponent.updateSync() - @linesComponent.updateSync(@isVisible()) + @linesComponent.updateSync() @horizontalScrollbarComponent.updateSync() @verticalScrollbarComponent.updateSync() @scrollbarCornerComponent.updateSync() @@ -165,6 +165,8 @@ TextEditorComponent = React.createClass @props.hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged @props.hostElement.__spacePenView.trigger 'editor:display-updated' + @linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically + mountGutterComponent: -> {editor} = @props @gutterComponent = new GutterComponent({@presenter, editor, onMouseDown: @onGutterMouseDown}) From 156569f19e7b5e16d032afa8a0fb856625aa3886 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 11:04:47 -0700 Subject: [PATCH 093/126] Add TextEditorPresenter::state.gutter.visible --- spec/text-editor-presenter-spec.coffee | 22 ++++++++++++++++++++++ src/text-editor-presenter.coffee | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 71f5a2283..143e6f6a5 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1970,6 +1970,28 @@ describe "TextEditorPresenter", -> editor.undo() expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe false + describe ".visible", -> + it "is true iff the editor isn't mini, ::isGutterVisible is true on the editor, and 'editor.showLineNumbers' is enabled in config", -> + presenter = buildPresenter() + + expect(editor.isGutterVisible()).toBe true + expect(presenter.state.gutter.visible).toBe true + + expectStateUpdate presenter, -> editor.setMini(true) + expect(presenter.state.gutter.visible).toBe false + + expectStateUpdate presenter, -> editor.setMini(false) + expect(presenter.state.gutter.visible).toBe true + + expectStateUpdate presenter, -> editor.setGutterVisible(false) + expect(presenter.state.gutter.visible).toBe false + + expectStateUpdate presenter, -> editor.setGutterVisible(true) + expect(presenter.state.gutter.visible).toBe true + + expectStateUpdate presenter, -> atom.config.set('editor.showLineNumbers', false) + expect(presenter.state.gutter.visible).toBe false + describe ".height", -> it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", -> presenter = buildPresenter(explicitHeight: null, autoHeight: true) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 606a8d8bf..b8eee96af 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -64,7 +64,10 @@ class TextEditorPresenter @updateContentState() @updateDecorations() @updateLinesState() + @updateGutterState() @updateLineNumbersState() + @disposables.add @model.onDidChangeGutterVisible => + @updateGutterState() @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this)) @@ -74,6 +77,7 @@ class TextEditorPresenter observeConfig: -> @scrollPastEnd = atom.config.get('editor.scrollPastEnd') + @showLineNumbers = atom.config.get('editor.showLineNumbers') @disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this) @disposables.add atom.config.onDidChange 'editor.scrollPastEnd', scope: @model.getRootScopeDescriptor(), ({newValue}) => @@ -81,6 +85,9 @@ class TextEditorPresenter @updateScrollHeight() @updateVerticalScrollState() @updateScrollbarsState() + @disposables.add atom.config.onDidChange 'editor.showLineNumbers', scope: @model.getRootScopeDescriptor(), ({newValue}) => + @showLineNumbers = newValue + @updateGutterState() buildState: -> @state = @@ -269,6 +276,7 @@ class TextEditorPresenter @emitter.emit "did-update-state" updateGutterState: -> + @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length @state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)" @gutterBackgroundColor From 2fba4979f9e67a992cf09828fe0750ba6cb0909c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 11:07:46 -0700 Subject: [PATCH 094/126] Use presenter to determine gutter visibility --- spec/text-editor-component-spec.coffee | 4 ++-- src/text-editor-component.coffee | 14 ++------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 9f221b54b..d024f7f91 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -580,12 +580,12 @@ describe "TextEditorComponent", -> expect(componentNode.querySelector('.gutter')).toBeNull() atom.config.set("editor.showLineNumbers", false) - expect(nextAnimationFrame).toBe noAnimationFrame + nextAnimationFrame() expect(componentNode.querySelector('.gutter')).toBeNull() editor.setGutterVisible(true) - expect(nextAnimationFrame).toBe noAnimationFrame + nextAnimationFrame() expect(componentNode.querySelector('.gutter')).toBeNull() diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 663e08a19..00698e723 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -46,7 +46,6 @@ TextEditorComponent = React.createClass @oldState ?= {} @newState = @presenter.state - {showLineNumbers} = @state {editor, cursorBlinkPeriod, cursorBlinkResumeDelay, hostElement, useShadowDOM} = @props hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty() style = {} @@ -93,7 +92,7 @@ TextEditorComponent = React.createClass componentDidMount: -> {editor, stylesElement, hostElement, useShadowDOM} = @props - @mountGutterComponent() if @gutterVisible + @mountGutterComponent() if @presenter.state.gutter.visible node = @getDOMNode() scrollViewNode = @refs.scrollView.getDOMNode() @@ -145,7 +144,7 @@ TextEditorComponent = React.createClass @cursorMoved = false @selectionChanged = false - if @gutterVisible + if @presenter.state.gutter.visible @mountGutterComponent() unless @gutterComponent? @gutterComponent.updateSync() else @@ -219,7 +218,6 @@ TextEditorComponent = React.createClass observeEditor: -> {editor} = @props - @subscribe editor.onDidChangeGutterVisible(@updateGutterVisible) @subscribe editor.onDidChangeMini(@setMini) @subscribe editor.observeGrammar(@onGrammarChanged) @subscribe editor.observeCursors(@onCursorAdded) @@ -294,7 +292,6 @@ TextEditorComponent = React.createClass scopeDescriptor = editor.getRootScopeDescriptor() subscriptions.add atom.config.observe 'editor.showIndentGuide', scope: scopeDescriptor, @requestUpdate - subscriptions.add atom.config.observe 'editor.showLineNumbers', scope: scopeDescriptor, @updateGutterVisible subscriptions.add atom.config.observe 'editor.scrollSensitivity', scope: scopeDescriptor, @setScrollSensitivity focused: -> @@ -783,15 +780,8 @@ TextEditorComponent = React.createClass atom.config.set("editor.showIndentGuide", showIndentGuide) setMini: -> - @updateGutterVisible() @requestUpdate() - updateGutterVisible: -> - gutterVisible = not @props.editor.isMini() and @props.editor.isGutterVisible() and atom.config.get('editor.showLineNumbers') - if gutterVisible isnt @gutterVisible - @gutterVisible = gutterVisible - @requestUpdate() - # Deprecated setInvisibles: (invisibles={}) -> grim.deprecate "Use config.set('editor.invisibles', invisibles) instead" From 1845234775244e17b19b318bc086855082223d27 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 11:10:40 -0700 Subject: [PATCH 095/126] Remove unnecessary ::mini subscription on model in TextEditorComponent --- src/text-editor-component.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 00698e723..7102dcb6d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -218,7 +218,6 @@ TextEditorComponent = React.createClass observeEditor: -> {editor} = @props - @subscribe editor.onDidChangeMini(@setMini) @subscribe editor.observeGrammar(@onGrammarChanged) @subscribe editor.observeCursors(@onCursorAdded) @subscribe editor.observeSelections(@onSelectionAdded) @@ -779,9 +778,6 @@ TextEditorComponent = React.createClass setShowIndentGuide: (showIndentGuide) -> atom.config.set("editor.showIndentGuide", showIndentGuide) - setMini: -> - @requestUpdate() - # Deprecated setInvisibles: (invisibles={}) -> grim.deprecate "Use config.set('editor.invisibles', invisibles) instead" From 5ecefe7213f7d6d95a595b2f4c92e13b01951b10 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 11:28:07 -0700 Subject: [PATCH 096/126] Update scoped config values in presenter when grammar changes --- spec/text-editor-presenter-spec.coffee | 14 ++++++++++++++ src/text-editor-presenter.coffee | 26 ++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 143e6f6a5..7691b663f 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1992,6 +1992,20 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> atom.config.set('editor.showLineNumbers', false) expect(presenter.state.gutter.visible).toBe false + it "updates when the editor's grammar changes", -> + presenter = buildPresenter() + + atom.config.set('editor.showLineNumbers', false, scopeSelector: '.source.js') + expect(presenter.state.gutter.visible).toBe true + stateUpdated = false + presenter.onDidUpdateState -> stateUpdated = true + + waitsForPromise -> atom.packages.activatePackage('language-javascript') + + runs -> + expect(stateUpdated).toBe true + expect(presenter.state.gutter.visible).toBe false + describe ".height", -> it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", -> presenter = buildPresenter(explicitHeight: null, autoHeight: true) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index b8eee96af..c694e6546 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -56,7 +56,7 @@ class TextEditorPresenter @updateLinesState() @updateGutterState() @updateLineNumbersState() - @disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this)) + @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) @disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this)) @disposables.add @model.onDidChangeMini => @updateScrollbarDimensions() @@ -76,19 +76,33 @@ class TextEditorPresenter @observeCursor(cursor) for cursor in @model.getCursors() observeConfig: -> - @scrollPastEnd = atom.config.get('editor.scrollPastEnd') - @showLineNumbers = atom.config.get('editor.showLineNumbers') + scope = @model.getRootScopeDescriptor() - @disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this) - @disposables.add atom.config.onDidChange 'editor.scrollPastEnd', scope: @model.getRootScopeDescriptor(), ({newValue}) => + @scrollPastEnd = atom.config.get('editor.scrollPastEnd', {scope}) + @showLineNumbers = atom.config.get('editor.showLineNumbers', {scope}) + + if @configDisposables? + @configDisposables?.dispose() + @disposables.remove(@configDisposables) + + @configDisposables = new CompositeDisposable + @disposables.add(@configDisposables) + + @configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', {scope}, @updateContentState.bind(this) + @configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', {scope}, ({newValue}) => @scrollPastEnd = newValue @updateScrollHeight() @updateVerticalScrollState() @updateScrollbarsState() - @disposables.add atom.config.onDidChange 'editor.showLineNumbers', scope: @model.getRootScopeDescriptor(), ({newValue}) => + @configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', {scope}, ({newValue}) => @showLineNumbers = newValue @updateGutterState() + didChangeGrammar: -> + @observeConfig() + @updateContentState() + @updateGutterState() + buildState: -> @state = horizontalScrollbar: {} From e244aae7c0ba4d95a3f519643a4d9589dbd78f65 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 11:31:30 -0700 Subject: [PATCH 097/126] Cache ::showIndentGuide config setting --- src/text-editor-presenter.coffee | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c694e6546..215398c05 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -76,10 +76,11 @@ class TextEditorPresenter @observeCursor(cursor) for cursor in @model.getCursors() observeConfig: -> - scope = @model.getRootScopeDescriptor() + configParams = {scope: @model.getRootScopeDescriptor()} - @scrollPastEnd = atom.config.get('editor.scrollPastEnd', {scope}) - @showLineNumbers = atom.config.get('editor.showLineNumbers', {scope}) + @scrollPastEnd = atom.config.get('editor.scrollPastEnd', configParams) + @showLineNumbers = atom.config.get('editor.showLineNumbers', configParams) + @showIndentGuide = atom.config.get('editor.showIndentGuide', configParams) if @configDisposables? @configDisposables?.dispose() @@ -88,13 +89,15 @@ class TextEditorPresenter @configDisposables = new CompositeDisposable @disposables.add(@configDisposables) - @configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', {scope}, @updateContentState.bind(this) - @configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', {scope}, ({newValue}) => + @configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) => + @showIndentGuide = newValue + @updateContentState() + @configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) => @scrollPastEnd = newValue @updateScrollHeight() @updateVerticalScrollState() @updateScrollbarsState() - @configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', {scope}, ({newValue}) => + @configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) => @showLineNumbers = newValue @updateGutterState() @@ -202,7 +205,7 @@ class TextEditorPresenter updateContentState: -> @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft - @state.content.indentGuidesVisible = not @model.isMini() and atom.config.get('editor.showIndentGuide', scope: @model.getRootScopeDescriptor()) + @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null @emitter.emit 'did-update-state' From d89ec25b289e8716e446809066deeff603b1e54d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 11:36:54 -0700 Subject: [PATCH 098/126] Remove showIndentGuide subscription from view --- src/text-editor-component.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 7102dcb6d..32fa88c1a 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -290,7 +290,6 @@ TextEditorComponent = React.createClass scopeDescriptor = editor.getRootScopeDescriptor() - subscriptions.add atom.config.observe 'editor.showIndentGuide', scope: scopeDescriptor, @requestUpdate subscriptions.add atom.config.observe 'editor.scrollSensitivity', scope: scopeDescriptor, @setScrollSensitivity focused: -> From ccdc4eb24b0e698426c7ac2198ac80353e4a3f1e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 13:12:23 -0700 Subject: [PATCH 099/126] Update TextEditorComponent DOM node manually --- src/text-editor-component.coffee | 47 ++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 32fa88c1a..aad2ac988 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -43,26 +43,12 @@ TextEditorComponent = React.createClass gutterComponent: null render: -> - @oldState ?= {} - @newState = @presenter.state - - {editor, cursorBlinkPeriod, cursorBlinkResumeDelay, hostElement, useShadowDOM} = @props - hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty() - style = {} - - @performedInitialMeasurement = false if editor.isDestroyed() - - if @performedInitialMeasurement - style.height = @presenter.state.height if @presenter.state.height? - - if useShadowDOM + if @props.useShadowDOM className = 'editor-contents--private' else className = 'editor-contents' - className += ' is-focused' if @newState.focused - className += ' has-selection' if hasSelection - div {className, style}, + div {className}, div ref: 'scrollView', className: 'scroll-view' getInitialState: -> {} @@ -124,8 +110,7 @@ TextEditorComponent = React.createClass @domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval) - @updateParentViewFocusedClassIfNeeded() - @updateParentViewMiniClass() + @updateSync() @checkForVisibilityChange() componentWillUnmount: -> @@ -139,11 +124,37 @@ TextEditorComponent = React.createClass @domPollingIntervalId = null componentDidUpdate: -> + @updateSync() + + updateSync: -> + @oldState ?= {} + @newState = @presenter.state + cursorMoved = @cursorMoved selectionChanged = @selectionChanged @cursorMoved = false @selectionChanged = false + node = @getDOMNode() + {editor} = @props + + if editor.getLastSelection()? and !editor.getLastSelection().isEmpty() + node.classList.add('has-selection') + else + node.classList.remove('has-selection') + + if @newState.focused isnt @oldState.focused + node.classList.toggle('is-focused', @newState.focused) + + @performedInitialMeasurement = false if editor.isDestroyed() + + if @performedInitialMeasurement + if @newState.height isnt @oldState.height + if @newState.height? + node.style.height = @newState.height + 'px' + else + node.style.height = '' + if @presenter.state.gutter.visible @mountGutterComponent() unless @gutterComponent? @gutterComponent.updateSync() From dae15eafc92d4a5472b895098a1bc04b4c822976 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 13:16:04 -0700 Subject: [PATCH 100/126] Remove unused prototype properties --- src/text-editor-component.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index aad2ac988..c9d687a24 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -21,17 +21,15 @@ TextEditorComponent = React.createClass displayName: 'TextEditorComponent' mixins: [SubscriberMixin] - visible: false pendingScrollTop: null pendingScrollLeft: null - selectOnMouseMove: false updateRequested: false updatesPaused: false updateRequestedWhilePaused: false + heightAndWidthMeasurementRequested: false cursorMoved: false selectionChanged: false scrollSensitivity: 0.4 - heightAndWidthMeasurementRequested: false inputEnabled: true domPollingInterval: 100 domPollingIntervalId: null From 0d109d69f0235777b193619c4b4df83156c0ba51 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 13:28:14 -0700 Subject: [PATCH 101/126] Use CompositeDisposable instead of SubscriberMixin in editor view --- src/text-editor-component.coffee | 56 ++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index c9d687a24..e42a4c47d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -14,12 +14,10 @@ InputComponent = require './input-component' LinesComponent = require './lines-component' ScrollbarComponent = require './scrollbar-component' ScrollbarCornerComponent = require './scrollbar-corner-component' -SubscriberMixin = require './subscriber-mixin' module.exports = TextEditorComponent = React.createClass displayName: 'TextEditorComponent' - mixins: [SubscriberMixin] pendingScrollTop: null pendingScrollLeft: null @@ -57,6 +55,9 @@ TextEditorComponent = React.createClass componentWillMount: -> @props.editor.manageScrollPosition = true + + @disposables = new CompositeDisposable + @observeConfig() @setScrollSensitivity(atom.config.get('editor.scrollSensitivity')) @@ -99,12 +100,12 @@ TextEditorComponent = React.createClass @observeEditor() @listenForDOMEvents() - @subscribe stylesElement.onDidAddStyleElement @onStylesheetsChanged - @subscribe stylesElement.onDidUpdateStyleElement @onStylesheetsChanged - @subscribe stylesElement.onDidRemoveStyleElement @onStylesheetsChanged + @disposables.add stylesElement.onDidAddStyleElement @onStylesheetsChanged + @disposables.add stylesElement.onDidUpdateStyleElement @onStylesheetsChanged + @disposables.add stylesElement.onDidRemoveStyleElement @onStylesheetsChanged unless atom.themes.isInitialLoadComplete() - @subscribe atom.themes.onDidChangeActiveThemes @onAllThemesLoaded - @subscribe scrollbarStyle.changes, @refreshScrollbars + @disposables.add atom.themes.onDidChangeActiveThemes @onAllThemesLoaded + @disposables.add scrollbarStyle.changes.onValue @refreshScrollbars @domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval) @@ -114,9 +115,9 @@ TextEditorComponent = React.createClass componentWillUnmount: -> {editor, hostElement} = @props - @unsubscribe() + @disposables.dispose() @presenter.destroy() - @scopedConfigSubscriptions.dispose() + @scopedConfigDisposables.dispose() window.removeEventListener 'resize', @requestHeightAndWidthMeasurement clearInterval(@domPollingIntervalId) @domPollingIntervalId = null @@ -227,9 +228,9 @@ TextEditorComponent = React.createClass observeEditor: -> {editor} = @props - @subscribe editor.observeGrammar(@onGrammarChanged) - @subscribe editor.observeCursors(@onCursorAdded) - @subscribe editor.observeSelections(@onSelectionAdded) + @disposables.add editor.observeGrammar(@onGrammarChanged) + @disposables.add editor.observeCursors(@onCursorAdded) + @disposables.add editor.observeSelections(@onSelectionAdded) listenForDOMEvents: -> node = @getDOMNode() @@ -282,24 +283,27 @@ TextEditorComponent = React.createClass # clipboard.writeText is a sync ipc call on Linux and that # will slow down selections. ipc.send('write-text-to-selection-clipboard', selectedText) - @subscribe editor.onDidChangeSelectionRange -> + @disposables.add editor.onDidChangeSelectionRange -> clearTimeout(timeoutId) timeoutId = setTimeout(writeSelectedTextToSelectionClipboard) observeConfig: -> - @subscribe atom.config.onDidChange 'editor.fontSize', @sampleFontStyling - @subscribe atom.config.onDidChange 'editor.fontFamily', @sampleFontStyling - @subscribe atom.config.onDidChange 'editor.lineHeight', @sampleFontStyling + @disposables.add atom.config.onDidChange 'editor.fontSize', @sampleFontStyling + @disposables.add atom.config.onDidChange 'editor.fontFamily', @sampleFontStyling + @disposables.add atom.config.onDidChange 'editor.lineHeight', @sampleFontStyling onGrammarChanged: -> {editor} = @props - @scopedConfigSubscriptions?.dispose() - @scopedConfigSubscriptions = subscriptions = new CompositeDisposable + if @scopedConfigDisposables? + @scopedConfigDisposables.dispose() + @disposables.remove(@scopedConfigDisposables) - scopeDescriptor = editor.getRootScopeDescriptor() + @scopedConfigDisposables = new CompositeDisposable + @disposables.add(@scopedConfigDisposables) - subscriptions.add atom.config.observe 'editor.scrollSensitivity', scope: scopeDescriptor, @setScrollSensitivity + scope = editor.getRootScopeDescriptor() + @scopedConfigDisposables.add atom.config.observe 'editor.scrollSensitivity', {scope}, @setScrollSensitivity focused: -> if @isMounted() @@ -526,10 +530,14 @@ TextEditorComponent = React.createClass onSelectionAdded: (selection) -> {editor} = @props - @subscribe selection.onDidChangeRange => @onSelectionChanged(selection) - @subscribe selection.onDidDestroy => + selectionDisposables = new CompositeDisposable + selectionDisposables.add selection.onDidChangeRange => @onSelectionChanged(selection) + selectionDisposables.add selection.onDidDestroy => @onSelectionChanged(selection) - @unsubscribe(selection) + selectionDisposables.dispose() + @disposables.remove(selectionDisposables) + + @disposables.add(selectionDisposables) if editor.selectionIntersectsVisibleRowRange(selection) @selectionChanged = true @@ -542,7 +550,7 @@ TextEditorComponent = React.createClass @requestUpdate() onCursorAdded: (cursor) -> - @subscribe cursor.onDidChangePosition @onCursorMoved + @disposables.add cursor.onDidChangePosition @onCursorMoved onCursorMoved: -> @cursorMoved = true From 1e8b0acbd01a27b8fb83185b8a49e8a5d320c764 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 13:29:25 -0700 Subject: [PATCH 102/126] Remove redundant update requests in editor view --- src/text-editor-component.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index e42a4c47d..576fb1059 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -541,20 +541,17 @@ TextEditorComponent = React.createClass if editor.selectionIntersectsVisibleRowRange(selection) @selectionChanged = true - @requestUpdate() onSelectionChanged: (selection) -> {editor} = @props if editor.selectionIntersectsVisibleRowRange(selection) @selectionChanged = true - @requestUpdate() onCursorAdded: (cursor) -> @disposables.add cursor.onDidChangePosition @onCursorMoved onCursorMoved: -> @cursorMoved = true - @requestUpdate() handleDragUntilMouseUp: (event, dragHandler) -> {editor} = @props From c9a6c327526c00c93a9cedb917885220ae725e61 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 13:33:41 -0700 Subject: [PATCH 103/126] Replace cursor blink React props with normal properties --- src/text-editor-component.coffee | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 576fb1059..6b015f1ce 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -19,6 +19,11 @@ module.exports = TextEditorComponent = React.createClass displayName: 'TextEditorComponent' + scrollSensitivity: 0.4 + domPollingInterval: 100 + cursorBlinkPeriod: 800 + cursorBlinkResumeDelay: 100 + pendingScrollTop: null pendingScrollLeft: null updateRequested: false @@ -27,9 +32,7 @@ TextEditorComponent = React.createClass heightAndWidthMeasurementRequested: false cursorMoved: false selectionChanged: false - scrollSensitivity: 0.4 inputEnabled: true - domPollingInterval: 100 domPollingIntervalId: null domPollingPaused: false measureScrollbarsWhenShown: true @@ -49,10 +52,6 @@ TextEditorComponent = React.createClass getInitialState: -> {} - getDefaultProps: -> - cursorBlinkPeriod: 800 - cursorBlinkResumeDelay: 100 - componentWillMount: -> @props.editor.manageScrollPosition = true @@ -61,7 +60,7 @@ TextEditorComponent = React.createClass @observeConfig() @setScrollSensitivity(atom.config.get('editor.scrollSensitivity')) - {editor, lineOverdrawMargin, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props + {editor, lineOverdrawMargin} = @props lineOverdrawMargin ?= 15 @presenter = new TextEditorPresenter @@ -69,8 +68,8 @@ TextEditorComponent = React.createClass scrollTop: editor.getScrollTop() scrollLeft: editor.getScrollLeft() lineOverdrawMargin: lineOverdrawMargin - cursorBlinkPeriod: cursorBlinkPeriod - cursorBlinkResumeDelay: cursorBlinkResumeDelay + cursorBlinkPeriod: @cursorBlinkPeriod + cursorBlinkResumeDelay: @cursorBlinkResumeDelay stoppedScrollingDelay: 200 @presenter.onDidUpdateState(@requestUpdate) From 7033b2720741cb24cd61191956d8d78ad361df60 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 14:53:38 -0700 Subject: [PATCH 104/126] Make EditorComponent a plain JS object rather than a React component --- spec/text-editor-component-spec.coffee | 17 +- spec/text-editor-element-spec.coffee | 10 +- src/text-editor-component.coffee | 403 ++++++++++--------------- src/text-editor-element.coffee | 18 +- src/text-editor-view.coffee | 2 +- 5 files changed, 178 insertions(+), 272 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index d024f7f91..24a24ead6 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -46,7 +46,7 @@ describe "TextEditorComponent", -> lineHeightInPixels = editor.getLineHeightInPixels() charWidth = editor.getDefaultCharWidth() - componentNode = component.getDOMNode() + componentNode = component.domNode verticalScrollbarNode = componentNode.querySelector('.vertical-scrollbar') horizontalScrollbarNode = componentNode.querySelector('.horizontal-scrollbar') @@ -466,11 +466,6 @@ describe "TextEditorComponent", -> expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy() describe "gutter rendering", -> - [gutter] = [] - - beforeEach -> - {gutter} = component.refs - it "renders the currently-visible line numbers", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' component.measureHeightAndWidth() @@ -800,11 +795,11 @@ describe "TextEditorComponent", -> expect(cursorsNode.classList.contains('blink-off')).toBe false - advanceClock(component.props.cursorBlinkPeriod / 2) + advanceClock(component.cursorBlinkPeriod / 2) nextAnimationFrame() expect(cursorsNode.classList.contains('blink-off')).toBe true - advanceClock(component.props.cursorBlinkPeriod / 2) + advanceClock(component.cursorBlinkPeriod / 2) nextAnimationFrame() expect(cursorsNode.classList.contains('blink-off')).toBe false @@ -813,8 +808,8 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(cursorsNode.classList.contains('blink-off')).toBe false - advanceClock(component.props.cursorBlinkResumeDelay) - advanceClock(component.props.cursorBlinkPeriod / 2) + advanceClock(component.cursorBlinkResumeDelay) + advanceClock(component.cursorBlinkPeriod / 2) nextAnimationFrame() expect(cursorsNode.classList.contains('blink-off')).toBe true @@ -2356,7 +2351,7 @@ describe "TextEditorComponent", -> wrapperView.appendTo(hiddenParent) {component} = wrapperView - componentNode = component.getDOMNode() + componentNode = component.domNode expect(componentNode.querySelectorAll('.line').length).toBe 0 hiddenParent.style.display = 'block' diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee index cbcc5fc16..f6082b571 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -46,12 +46,12 @@ describe "TextEditorElement", -> jasmine.attachToDOM(element) component = element.component - expect(component.isMounted()).toBe true + expect(component.mounted).toBe true element.remove() - expect(component.isMounted()).toBe false + expect(component.mounted).toBe false jasmine.attachToDOM(element) - expect(element.component.isMounted()).toBe true + expect(element.component.mounted).toBe true describe "when the editor.useShadowDOM config option is false", -> it "mounts the react component and unmounts when removed from the dom", -> @@ -61,9 +61,9 @@ describe "TextEditorElement", -> jasmine.attachToDOM(element) component = element.component - expect(component.isMounted()).toBe true + expect(component.mounted).toBe true element.getModel().destroy() - expect(component.isMounted()).toBe false + expect(component.mounted).toBe false describe "focus and blur handling", -> describe "when the editor.useShadowDOM config option is true", -> diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 6b015f1ce..eb0a70293 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -1,7 +1,4 @@ _ = require 'underscore-plus' -React = require 'react-atom-fork' -{div, span} = require 'reactionary-atom-fork' -{debounce, defaults, isEqualForProperties} = require 'underscore-plus' scrollbarStyle = require 'scrollbar-style' {Range, Point} = require 'text-buffer' grim = require 'grim' @@ -16,13 +13,12 @@ ScrollbarComponent = require './scrollbar-component' ScrollbarCornerComponent = require './scrollbar-corner-component' module.exports = -TextEditorComponent = React.createClass - displayName: 'TextEditorComponent' - +class TextEditorComponent scrollSensitivity: 0.4 domPollingInterval: 100 cursorBlinkPeriod: 800 cursorBlinkResumeDelay: 100 + lineOverdrawMargin: 15 pendingScrollTop: null pendingScrollLeft: null @@ -40,68 +36,60 @@ TextEditorComponent = React.createClass remeasureCharacterWidthsWhenShown: false stylingChangeAnimationFrameRequested: false gutterComponent: null + mounted: true - render: -> - if @props.useShadowDOM - className = 'editor-contents--private' - else - className = 'editor-contents' - - div {className}, - div ref: 'scrollView', className: 'scroll-view' - - getInitialState: -> {} - - componentWillMount: -> - @props.editor.manageScrollPosition = true - + constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, lineOverdrawMargin}) -> + @lineOverdrawMargin = lineOverdrawMargin if lineOverdrawMargin? @disposables = new CompositeDisposable + @editor.manageScrollPosition = true @observeConfig() @setScrollSensitivity(atom.config.get('editor.scrollSensitivity')) - {editor, lineOverdrawMargin} = @props - lineOverdrawMargin ?= 15 - @presenter = new TextEditorPresenter - model: editor - scrollTop: editor.getScrollTop() - scrollLeft: editor.getScrollLeft() + model: @editor + scrollTop: @editor.getScrollTop() + scrollLeft: @editor.getScrollLeft() lineOverdrawMargin: lineOverdrawMargin cursorBlinkPeriod: @cursorBlinkPeriod cursorBlinkResumeDelay: @cursorBlinkResumeDelay stoppedScrollingDelay: 200 + @presenter.onDidUpdateState(@requestUpdate) - componentDidMount: -> - {editor, stylesElement, hostElement, useShadowDOM} = @props + @domNode = document.createElement('div') + if @useShadowDOM + @domNode.classList.add('editor-contents--private') + else + @domNode.classList.add('editor-contents') + + @scrollViewNode = document.createElement('div') + @scrollViewNode.classList.add('scroll-view') + @domNode.appendChild(@scrollViewNode) @mountGutterComponent() if @presenter.state.gutter.visible - node = @getDOMNode() - scrollViewNode = @refs.scrollView.getDOMNode() - @hiddenInputComponent = new InputComponent(@presenter) - scrollViewNode.appendChild(@hiddenInputComponent.domNode) + @scrollViewNode.appendChild(@hiddenInputComponent.domNode) - @linesComponent = new LinesComponent({@presenter, hostElement, useShadowDOM}) - scrollViewNode.appendChild(@linesComponent.domNode) + @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM}) + @scrollViewNode.appendChild(@linesComponent.domNode) @horizontalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'horizontal', onScroll: @onHorizontalScroll}) - scrollViewNode.appendChild(@horizontalScrollbarComponent.domNode) + @scrollViewNode.appendChild(@horizontalScrollbarComponent.domNode) @verticalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'vertical', onScroll: @onVerticalScroll}) - node.appendChild(@verticalScrollbarComponent.domNode) + @domNode.appendChild(@verticalScrollbarComponent.domNode) @scrollbarCornerComponent = new ScrollbarCornerComponent(@presenter) - node.appendChild(@scrollbarCornerComponent.domNode) + @domNode.appendChild(@scrollbarCornerComponent.domNode) @observeEditor() @listenForDOMEvents() - @disposables.add stylesElement.onDidAddStyleElement @onStylesheetsChanged - @disposables.add stylesElement.onDidUpdateStyleElement @onStylesheetsChanged - @disposables.add stylesElement.onDidRemoveStyleElement @onStylesheetsChanged + @disposables.add @stylesElement.onDidAddStyleElement @onStylesheetsChanged + @disposables.add @stylesElement.onDidUpdateStyleElement @onStylesheetsChanged + @disposables.add @stylesElement.onDidRemoveStyleElement @onStylesheetsChanged unless atom.themes.isInitialLoadComplete() @disposables.add atom.themes.onDidChangeActiveThemes @onAllThemesLoaded @disposables.add scrollbarStyle.changes.onValue @refreshScrollbars @@ -111,19 +99,14 @@ TextEditorComponent = React.createClass @updateSync() @checkForVisibilityChange() - componentWillUnmount: -> - {editor, hostElement} = @props - + destroy: -> + @mounted = false @disposables.dispose() @presenter.destroy() - @scopedConfigDisposables.dispose() window.removeEventListener 'resize', @requestHeightAndWidthMeasurement clearInterval(@domPollingIntervalId) @domPollingIntervalId = null - componentDidUpdate: -> - @updateSync() - updateSync: -> @oldState ?= {} @newState = @presenter.state @@ -133,25 +116,22 @@ TextEditorComponent = React.createClass @cursorMoved = false @selectionChanged = false - node = @getDOMNode() - {editor} = @props - - if editor.getLastSelection()? and !editor.getLastSelection().isEmpty() - node.classList.add('has-selection') + if @editor.getLastSelection()? and !@editor.getLastSelection().isEmpty() + @domNode.classList.add('has-selection') else - node.classList.remove('has-selection') + @domNode.classList.remove('has-selection') if @newState.focused isnt @oldState.focused - node.classList.toggle('is-focused', @newState.focused) + @domNode.classList.toggle('is-focused', @newState.focused) - @performedInitialMeasurement = false if editor.isDestroyed() + @performedInitialMeasurement = false if @editor.isDestroyed() if @performedInitialMeasurement if @newState.height isnt @oldState.height if @newState.height? - node.style.height = @newState.height + 'px' + @domNode.style.height = @newState.height + 'px' else - node.style.height = '' + @domNode.style.height = '' if @presenter.state.gutter.visible @mountGutterComponent() unless @gutterComponent? @@ -166,20 +146,18 @@ TextEditorComponent = React.createClass @verticalScrollbarComponent.updateSync() @scrollbarCornerComponent.updateSync() - if @props.editor.isAlive() + if @editor.isAlive() @updateParentViewFocusedClassIfNeeded() @updateParentViewMiniClass() - @props.hostElement.__spacePenView.trigger 'cursor:moved' if cursorMoved - @props.hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged - @props.hostElement.__spacePenView.trigger 'editor:display-updated' + @hostElement.__spacePenView.trigger 'cursor:moved' if cursorMoved + @hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged + @hostElement.__spacePenView.trigger 'editor:display-updated' @linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically mountGutterComponent: -> - {editor} = @props - @gutterComponent = new GutterComponent({@presenter, editor, onMouseDown: @onGutterMouseDown}) - node = @getDOMNode() - node.insertBefore(@gutterComponent.domNode, node.firstChild) + @gutterComponent = new GutterComponent({@presenter, @editor, onMouseDown: @onGutterMouseDown}) + @domNode.insertBefore(@gutterComponent.domNode, @domNode.firstChild) becameVisible: -> @updatesPaused = true @@ -189,28 +167,28 @@ TextEditorComponent = React.createClass @measureHeightAndWidth() @measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown @remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown - @props.editor.setVisible(true) + @editor.setVisible(true) @performedInitialMeasurement = true @updatesPaused = false - @forceUpdate() if @canUpdate() + @updateSync() if @canUpdate() - requestUpdate: -> + requestUpdate: => return unless @canUpdate() if @updatesPaused @updateRequestedWhilePaused = true return - if @props.hostElement.isUpdatedSynchronously() - @forceUpdate() + if @hostElement.isUpdatedSynchronously() + @updateSync() else unless @updateRequested @updateRequested = true requestAnimationFrame => @updateRequested = false - @forceUpdate() if @canUpdate() + @updateSync() if @editor.isAlive() canUpdate: -> - @isMounted() and @props.editor.isAlive() + @mounted and @editor.isAlive() requestAnimationFrame: (fn) -> @updatesPaused = true @@ -220,34 +198,27 @@ TextEditorComponent = React.createClass @updatesPaused = false if @updateRequestedWhilePaused and @canUpdate() @updateRequestedWhilePaused = false - @forceUpdate() + @updateSync() getTopmostDOMNode: -> - @props.hostElement + @hostElement observeEditor: -> - {editor} = @props - @disposables.add editor.observeGrammar(@onGrammarChanged) - @disposables.add editor.observeCursors(@onCursorAdded) - @disposables.add editor.observeSelections(@onSelectionAdded) + @disposables.add @editor.observeGrammar(@onGrammarChanged) + @disposables.add @editor.observeCursors(@onCursorAdded) + @disposables.add @editor.observeSelections(@onSelectionAdded) listenForDOMEvents: -> - node = @getDOMNode() - node.addEventListener 'mousewheel', @onMouseWheel - node.addEventListener 'textInput', @onTextInput - @refs.scrollView.getDOMNode().addEventListener 'mousedown', @onMouseDown - - scrollViewNode = @refs.scrollView.getDOMNode() - scrollViewNode.addEventListener 'scroll', @onScrollViewScroll + @domNode.addEventListener 'mousewheel', @onMouseWheel + @domNode.addEventListener 'textInput', @onTextInput + @scrollViewNode.addEventListener 'mousedown', @onMouseDown + @scrollViewNode.addEventListener 'scroll', @onScrollViewScroll window.addEventListener 'resize', @requestHeightAndWidthMeasurement @listenForIMEEvents() @trackSelectionClipboard() if process.platform is 'linux' listenForIMEEvents: -> - node = @getDOMNode() - {editor} = @props - # The IME composition events work like this: # # User types 's', chromium pops up the completion helper @@ -262,27 +233,26 @@ TextEditorComponent = React.createClass # 5. textInput fired; event.data == the completion string selectedText = null - node.addEventListener 'compositionstart', -> - selectedText = editor.getSelectedText() - node.addEventListener 'compositionupdate', (event) -> - editor.insertText(event.data, select: true, undo: 'skip') - node.addEventListener 'compositionend', (event) -> - editor.insertText(selectedText, select: true, undo: 'skip') + @domNode.addEventListener 'compositionstart', => + selectedText = @editor.getSelectedText() + @domNode.addEventListener 'compositionupdate', (event) => + @editor.insertText(event.data, select: true, undo: 'skip') + @domNode.addEventListener 'compositionend', (event) => + @editor.insertText(selectedText, select: true, undo: 'skip') event.target.value = '' # Listen for selection changes and store the currently selected text # in the selection clipboard. This is only applicable on Linux. trackSelectionClipboard: -> timeoutId = null - {editor} = @props - writeSelectedTextToSelectionClipboard = -> - return if editor.isDestroyed() - if selectedText = editor.getSelectedText() + writeSelectedTextToSelectionClipboard = => + return if @editor.isDestroyed() + if selectedText = @editor.getSelectedText() # This uses ipc.send instead of clipboard.writeText because # clipboard.writeText is a sync ipc call on Linux and that # will slow down selections. ipc.send('write-text-to-selection-clipboard', selectedText) - @disposables.add editor.onDidChangeSelectionRange -> + @disposables.add @editor.onDidChangeSelectionRange -> clearTimeout(timeoutId) timeoutId = setTimeout(writeSelectedTextToSelectionClipboard) @@ -291,9 +261,7 @@ TextEditorComponent = React.createClass @disposables.add atom.config.onDidChange 'editor.fontFamily', @sampleFontStyling @disposables.add atom.config.onDidChange 'editor.lineHeight', @sampleFontStyling - onGrammarChanged: -> - {editor} = @props - + onGrammarChanged: => if @scopedConfigDisposables? @scopedConfigDisposables.dispose() @disposables.remove(@scopedConfigDisposables) @@ -301,19 +269,19 @@ TextEditorComponent = React.createClass @scopedConfigDisposables = new CompositeDisposable @disposables.add(@scopedConfigDisposables) - scope = editor.getRootScopeDescriptor() + scope = @editor.getRootScopeDescriptor() @scopedConfigDisposables.add atom.config.observe 'editor.scrollSensitivity', {scope}, @setScrollSensitivity focused: -> - if @isMounted() + if @mounted @presenter.setFocused(true) @hiddenInputComponent.domNode.focus() blurred: -> - if @isMounted() + if @mounted @presenter.setFocused(false) - onTextInput: (event) -> + onTextInput: (event) => event.stopPropagation() # If we prevent the insertion of a space character, then the browser @@ -322,7 +290,6 @@ TextEditorComponent = React.createClass return unless @isInputEnabled() - {editor} = @props inputNode = event.target # Work around of the accented character suggestion feature in OS X. @@ -330,16 +297,14 @@ TextEditorComponent = React.createClass # replacing the previous un-accented character with an accented variant, it # will select backward over it. selectedLength = inputNode.selectionEnd - inputNode.selectionStart - editor.selectLeft() if selectedLength is 1 + @editor.selectLeft() if selectedLength is 1 - insertedRange = editor.transact atom.config.get('editor.undoGroupingInterval'), -> - editor.insertText(event.data) + insertedRange = @editor.transact atom.config.get('editor.undoGroupingInterval'), => + @editor.insertText(event.data) inputNode.value = event.data if insertedRange - onVerticalScroll: (scrollTop) -> - {editor} = @props - - return if @updateRequested or scrollTop is editor.getScrollTop() + onVerticalScroll: (scrollTop) => + return if @updateRequested or scrollTop is @editor.getScrollTop() animationFramePending = @pendingScrollTop? @pendingScrollTop = scrollTop @@ -349,10 +314,8 @@ TextEditorComponent = React.createClass @pendingScrollTop = null @presenter.setScrollTop(pendingScrollTop) - onHorizontalScroll: (scrollLeft) -> - {editor} = @props - - return if @updateRequested or scrollLeft is editor.getScrollLeft() + onHorizontalScroll: (scrollLeft) => + return if @updateRequested or scrollLeft is @editor.getScrollLeft() animationFramePending = @pendingScrollLeft? @pendingScrollLeft = scrollLeft @@ -361,9 +324,7 @@ TextEditorComponent = React.createClass @presenter.setScrollLeft(@pendingScrollLeft) @pendingScrollLeft = null - onMouseWheel: (event) -> - {editor} = @props - + onMouseWheel: (event) => # Only scroll in one direction at a time {wheelDeltaX, wheelDeltaY} = event @@ -378,24 +339,23 @@ TextEditorComponent = React.createClass if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY) # Scrolling horizontally - previousScrollLeft = editor.getScrollLeft() + previousScrollLeft = @editor.getScrollLeft() @presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)) - event.preventDefault() unless previousScrollLeft is editor.getScrollLeft() + event.preventDefault() unless previousScrollLeft is @editor.getScrollLeft() else # Scrolling vertically @presenter.setMouseWheelScreenRow(@screenRowForNode(event.target)) previousScrollTop = @presenter.scrollTop @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) - event.preventDefault() unless previousScrollTop is editor.getScrollTop() + event.preventDefault() unless previousScrollTop is @editor.getScrollTop() - onScrollViewScroll: -> - if @isMounted() + onScrollViewScroll: => + if @mounted console.warn "TextEditorScrollView scrolled when it shouldn't have." - scrollViewNode = @refs.scrollView.getDOMNode() - scrollViewNode.scrollTop = 0 - scrollViewNode.scrollLeft = 0 + @scrollViewNode.scrollTop = 0 + @scrollViewNode.scrollLeft = 0 - onMouseDown: (event) -> + onMouseDown: (event) => unless event.button is 0 or (event.button is 1 and process.platform is 'linux') # Only handle mouse down events for left mouse button on all platforms # and middle mouse button on Linux since it pastes the selection clipboard @@ -403,7 +363,6 @@ TextEditorComponent = React.createClass return if event.target?.classList.contains('horizontal-scrollbar') - {editor} = @props {detail, shiftKey, metaKey, ctrlKey} = event # CTRL+click brings up the context menu on OSX, so don't handle those either @@ -415,27 +374,27 @@ TextEditorComponent = React.createClass screenPosition = @screenPositionForMouseEvent(event) if event.target?.classList.contains('fold-marker') - bufferRow = editor.bufferRowForScreenRow(screenPosition.row) - editor.unfoldBufferRow(bufferRow) + bufferRow = @editor.bufferRowForScreenRow(screenPosition.row) + @editor.unfoldBufferRow(bufferRow) return switch detail when 1 if shiftKey - editor.selectToScreenPosition(screenPosition) + @editor.selectToScreenPosition(screenPosition) else if metaKey or (ctrlKey and process.platform isnt 'darwin') - editor.addCursorAtScreenPosition(screenPosition) + @editor.addCursorAtScreenPosition(screenPosition) else - editor.setCursorScreenPosition(screenPosition) + @editor.setCursorScreenPosition(screenPosition) when 2 - editor.getLastSelection().selectWord() + @editor.getLastSelection().selectWord() when 3 - editor.getLastSelection().selectLine() + @editor.getLastSelection().selectLine() - @handleDragUntilMouseUp event, (screenPosition) -> - editor.selectToScreenPosition(screenPosition) + @handleDragUntilMouseUp event, (screenPosition) => + @editor.selectToScreenPosition(screenPosition) - onGutterMouseDown: (event) -> + onGutterMouseDown: (event) => return unless event.button is 0 # only handle the left mouse button {shiftKey, metaKey, ctrlKey} = event @@ -447,27 +406,25 @@ TextEditorComponent = React.createClass else @onGutterClick(event) - onGutterClick: (event) -> - {editor} = @props + onGutterClick: (event) => clickedRow = @screenPositionForMouseEvent(event).row - editor.setSelectedScreenRange([[clickedRow, 0], [clickedRow + 1, 0]], preserveFolds: true) + @editor.setSelectedScreenRange([[clickedRow, 0], [clickedRow + 1, 0]], preserveFolds: true) - @handleDragUntilMouseUp event, (screenPosition) -> + @handleDragUntilMouseUp event, (screenPosition) => dragRow = screenPosition.row if dragRow < clickedRow # dragging up - editor.setSelectedScreenRange([[dragRow, 0], [clickedRow + 1, 0]], preserveFolds: true) + @editor.setSelectedScreenRange([[dragRow, 0], [clickedRow + 1, 0]], preserveFolds: true) else - editor.setSelectedScreenRange([[clickedRow, 0], [dragRow + 1, 0]], preserveFolds: true) + @editor.setSelectedScreenRange([[clickedRow, 0], [dragRow + 1, 0]], preserveFolds: true) - onGutterMetaClick: (event) -> - {editor} = @props + onGutterMetaClick: (event) => clickedRow = @screenPositionForMouseEvent(event).row - bufferRange = editor.bufferRangeForScreenRange([[clickedRow, 0], [clickedRow + 1, 0]]) - rowSelection = editor.addSelectionForBufferRange(bufferRange, preserveFolds: true) + bufferRange = @editor.bufferRangeForScreenRange([[clickedRow, 0], [clickedRow + 1, 0]]) + rowSelection = @editor.addSelectionForBufferRange(bufferRange, preserveFolds: true) - @handleDragUntilMouseUp event, (screenPosition) -> + @handleDragUntilMouseUp event, (screenPosition) => dragRow = screenPosition.row if dragRow < clickedRow # dragging up @@ -476,32 +433,31 @@ TextEditorComponent = React.createClass rowSelection.setScreenRange([[clickedRow, 0], [dragRow + 1, 0]], preserveFolds: true) # After updating the selected screen range, merge overlapping selections - editor.mergeIntersectingSelections(preserveFolds: true) + @editor.mergeIntersectingSelections(preserveFolds: true) # The merge process will possibly destroy the current selection because # it will be merged into another one. Therefore, we need to obtain a # reference to the new selection that contains the originally selected row - rowSelection = _.find editor.getSelections(), (selection) -> + rowSelection = _.find @editor.getSelections(), (selection) -> selection.intersectsBufferRange(bufferRange) - onGutterShiftClick: (event) -> - {editor} = @props + onGutterShiftClick: (event) => clickedRow = @screenPositionForMouseEvent(event).row - tailPosition = editor.getLastSelection().getTailScreenPosition() + tailPosition = @editor.getLastSelection().getTailScreenPosition() if clickedRow < tailPosition.row - editor.selectToScreenPosition([clickedRow, 0]) + @editor.selectToScreenPosition([clickedRow, 0]) else - editor.selectToScreenPosition([clickedRow + 1, 0]) + @editor.selectToScreenPosition([clickedRow + 1, 0]) - @handleDragUntilMouseUp event, (screenPosition) -> + @handleDragUntilMouseUp event, (screenPosition) => dragRow = screenPosition.row if dragRow < tailPosition.row # dragging up - editor.setSelectedScreenRange([[dragRow, 0], tailPosition], preserveFolds: true) + @editor.setSelectedScreenRange([[dragRow, 0], tailPosition], preserveFolds: true) else - editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]], preserveFolds: true) + @editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]], preserveFolds: true) - onStylesheetsChanged: (styleElement) -> + onStylesheetsChanged: (styleElement) => return unless @performedInitialMeasurement return unless atom.themes.isInitialLoadComplete() @@ -513,22 +469,20 @@ TextEditorComponent = React.createClass @stylingChangeAnimationFrameRequested = true requestAnimationFrame => @stylingChangeAnimationFrameRequested = false - if @isMounted() + if @mounted @refreshScrollbars() if not styleElement.sheet? or @containsScrollbarSelector(styleElement.sheet) @handleStylingChange() - onAllThemesLoaded: -> + onAllThemesLoaded: => @refreshScrollbars() @handleStylingChange() - handleStylingChange: -> + handleStylingChange: => @sampleFontStyling() @sampleBackgroundColors() @remeasureCharacterWidths() - onSelectionAdded: (selection) -> - {editor} = @props - + onSelectionAdded: (selection) => selectionDisposables = new CompositeDisposable selectionDisposables.add selection.onDidChangeRange => @onSelectionChanged(selection) selectionDisposables.add selection.onDidDestroy => @@ -538,31 +492,29 @@ TextEditorComponent = React.createClass @disposables.add(selectionDisposables) - if editor.selectionIntersectsVisibleRowRange(selection) + if @editor.selectionIntersectsVisibleRowRange(selection) @selectionChanged = true - onSelectionChanged: (selection) -> - {editor} = @props - if editor.selectionIntersectsVisibleRowRange(selection) + onSelectionChanged: (selection) => + if @editor.selectionIntersectsVisibleRowRange(selection) @selectionChanged = true - onCursorAdded: (cursor) -> + onCursorAdded: (cursor) => @disposables.add cursor.onDidChangePosition @onCursorMoved - onCursorMoved: -> + onCursorMoved: => @cursorMoved = true - handleDragUntilMouseUp: (event, dragHandler) -> - {editor} = @props + handleDragUntilMouseUp: (event, dragHandler) => dragging = false lastMousePosition = {} animationLoop = => @requestAnimationFrame => - if dragging and @isMounted() + if dragging and @mounted screenPosition = @screenPositionForMouseEvent(lastMousePosition) dragHandler(screenPosition) animationLoop() - else if not @isMounted() + else if not @mounted stopDragging() onMouseMove = (event) -> @@ -577,9 +529,9 @@ TextEditorComponent = React.createClass # Stop dragging when cursor enters dev tools because we can't detect mouseup onMouseUp() if event.which is 0 - onMouseUp = (event) -> + onMouseUp = (event) => stopDragging() - editor.finalizeSelections() + @editor.finalizeSelections() pasteSelectionClipboard(event) stopDragging = -> @@ -587,21 +539,20 @@ TextEditorComponent = React.createClass window.removeEventListener('mousemove', onMouseMove) window.removeEventListener('mouseup', onMouseUp) - pasteSelectionClipboard = (event) -> + pasteSelectionClipboard = (event) => if event?.which is 2 and process.platform is 'linux' if selection = require('clipboard').readText('selection') - editor.insertText(selection) + @editor.insertText(selection) window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) isVisible: -> - node = @getDOMNode() - node.offsetHeight > 0 or node.offsetWidth > 0 + @domNode.offsetHeight > 0 or @domNode.offsetWidth > 0 pauseDOMPolling: -> @domPollingPaused = true - @resumeDOMPollingAfterDelay ?= debounce(@resumeDOMPolling, 100) + @resumeDOMPollingAfterDelay ?= _.debounce(@resumeDOMPolling, 100) @resumeDOMPollingAfterDelay() resumeDOMPolling: -> @@ -609,8 +560,8 @@ TextEditorComponent = React.createClass resumeDOMPollingAfterDelay: null # created lazily - pollDOM: -> - return if @domPollingPaused or @updateRequested or not @isMounted() + pollDOM: => + return if @domPollingPaused or @updateRequested or not @mounted unless @checkForVisibilityChange() @sampleBackgroundColors() @@ -627,7 +578,7 @@ TextEditorComponent = React.createClass else @wasVisible = false - requestHeightAndWidthMeasurement: -> + requestHeightAndWidthMeasurement: => return if @heightAndWidthMeasurementRequested @heightAndWidthMeasurementRequested = true @@ -640,29 +591,27 @@ TextEditorComponent = React.createClass # and use the scrollHeight / scrollWidth as its height and width in # calculations. measureHeightAndWidth: -> - return unless @isMounted() + return unless @mounted - {editor, hostElement} = @props - scrollViewNode = @refs.scrollView.getDOMNode() - {position} = getComputedStyle(hostElement) - {height} = hostElement.style + {position} = getComputedStyle(@hostElement) + {height} = @hostElement.style if position is 'absolute' or height @presenter.setAutoHeight(false) - height = hostElement.offsetHeight + height = @hostElement.offsetHeight if height > 0 @presenter.setExplicitHeight(height) else @presenter.setAutoHeight(true) @presenter.setExplicitHeight(null) - clientWidth = scrollViewNode.clientWidth - paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft) + clientWidth = @scrollViewNode.clientWidth + paddingLeft = parseInt(getComputedStyle(@scrollViewNode).paddingLeft) clientWidth -= paddingLeft if clientWidth > 0 @presenter.setContentFrameWidth(clientWidth) - sampleFontStyling: -> + sampleFontStyling: => oldFontSize = @fontSize oldFontFamily = @fontFamily oldLineHeight = @lineHeight @@ -676,8 +625,7 @@ TextEditorComponent = React.createClass @remeasureCharacterWidths() sampleBackgroundColors: (suppressUpdate) -> - {hostElement} = @props - {backgroundColor} = getComputedStyle(hostElement) + {backgroundColor} = getComputedStyle(@hostElement) @presenter.setBackgroundColor(backgroundColor) @@ -702,7 +650,6 @@ TextEditorComponent = React.createClass measureScrollbars: -> @measureScrollbarsWhenShown = false - {editor} = @props cornerNode = @scrollbarCornerComponent.domNode originalDisplayValue = cornerNode.style.display @@ -722,7 +669,7 @@ TextEditorComponent = React.createClass return true false - refreshScrollbars: -> + refreshScrollbars: => if @isVisible() @measureScrollbarsWhenShown = false else @@ -756,7 +703,7 @@ TextEditorComponent = React.createClass cornerNode.style.display = originalCornerDisplayValue consolidateSelections: (e) -> - e.abortKeyBinding() unless @props.editor.consolidateSelections() + e.abortKeyBinding() unless @editor.consolidateSelections() lineNodeForScreenRow: (screenRow) -> @linesComponent.lineNodeForScreenRow(screenRow) @@ -799,16 +746,15 @@ TextEditorComponent = React.createClass setShowInvisibles: (showInvisibles) -> atom.config.set('editor.showInvisibles', showInvisibles) - setScrollSensitivity: (scrollSensitivity) -> + setScrollSensitivity: (scrollSensitivity) => if scrollSensitivity = parseInt(scrollSensitivity) @scrollSensitivity = Math.abs(scrollSensitivity) / 100 screenPositionForMouseEvent: (event) -> pixelPosition = @pixelPositionForMouseEvent(event) - @props.editor.screenPositionForPixelPosition(pixelPosition) + @editor.screenPositionForPixelPosition(pixelPosition) pixelPositionForMouseEvent: (event) -> - {editor} = @props {clientX, clientY} = event linesClientRect = @linesComponent.domNode.getBoundingClientRect() @@ -817,7 +763,7 @@ TextEditorComponent = React.createClass {top, left} getModel: -> - @props.editor + @editor isInputEnabled: -> @inputEnabled @@ -825,45 +771,10 @@ TextEditorComponent = React.createClass updateParentViewFocusedClassIfNeeded: -> if @oldState.focused isnt @newState.focused - @props.hostElement.classList.toggle('is-focused', @newState.focused) - @props.rootElement.classList.toggle('is-focused', @newState.focused) + @hostElement.classList.toggle('is-focused', @newState.focused) + @rootElement.classList.toggle('is-focused', @newState.focused) @oldState.focused = @newState.focused updateParentViewMiniClass: -> - @props.hostElement.classList.toggle('mini', @props.editor.isMini()) - @props.rootElement.classList.toggle('mini', @props.editor.isMini()) - - runScrollBenchmark: -> - unless process.env.NODE_ENV is 'production' - ReactPerf = require 'react-atom-fork/lib/ReactDefaultPerf' - ReactPerf.start() - - node = @getDOMNode() - - scroll = (delta, done) -> - dispatchMouseWheelEvent = -> - node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -0, wheelDeltaY: -delta)) - - stopScrolling = -> - clearInterval(interval) - done?() - - interval = setInterval(dispatchMouseWheelEvent, 10) - setTimeout(stopScrolling, 500) - - console.timeline('scroll') - scroll 50, -> - scroll 100, -> - scroll 200, -> - scroll 400, -> - scroll 800, -> - scroll 1600, -> - console.timelineEnd('scroll') - unless process.env.NODE_ENV is 'production' - ReactPerf.stop() - console.log "Inclusive" - ReactPerf.printInclusive() - console.log "Exclusive" - ReactPerf.printExclusive() - console.log "Wasted" - ReactPerf.printWasted() + @hostElement.classList.toggle('mini', @editor.isMini()) + @rootElement.classList.toggle('mini', @editor.isMini()) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 67e62b408..443786793 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -1,6 +1,5 @@ {Emitter} = require 'event-kit' {View, $, callRemoveHooks} = require 'space-pen' -React = require 'react-atom-fork' Path = require 'path' {defaults} = require 'underscore-plus' TextBuffer = require 'text-buffer' @@ -61,7 +60,7 @@ class TextEditorElement extends HTMLElement attachedCallback: -> @buildModel() unless @getModel()? - @mountComponent() unless @component?.isMounted() + @mountComponent() unless @component? @component.checkForVisibilityChange() if this is document.activeElement @focused() @@ -105,7 +104,7 @@ class TextEditorElement extends HTMLElement )) mountComponent: -> - @componentDescriptor ?= TextEditorComponent( + @component = new TextEditorComponent( hostElement: this rootElement: @rootElement stylesElement: @stylesElement @@ -113,27 +112,28 @@ class TextEditorElement extends HTMLElement lineOverdrawMargin: @lineOverdrawMargin useShadowDOM: @useShadowDOM ) - @component = React.renderComponent(@componentDescriptor, @rootElement) + @rootElement.appendChild(@component.domNode) if @useShadowDOM @shadowRoot.addEventListener('blur', @shadowRootBlurred.bind(this), true) else - inputNode = @component.refs.input.getDOMNode() + inputNode = @component.hiddenInputComponent.domNode inputNode.addEventListener 'focus', @focused.bind(this) inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false)) unmountComponent: -> - return unless @component?.isMounted() callRemoveHooks(this) - React.unmountComponentAtNode(@rootElement) - @component = null + if @component? + @component.destroy() + @component.domNode.remove() + @component = null focused: -> @component?.focused() blurred: (event) -> unless @useShadowDOM - if event.relatedTarget is @component?.refs.input?.getDOMNode() + if event.relatedTarget is @component.hiddenInputComponent.domNode event.stopImmediatePropagation() return diff --git a/src/text-editor-view.coffee b/src/text-editor-view.coffee index 7fceb42a8..5db4e41ce 100644 --- a/src/text-editor-view.coffee +++ b/src/text-editor-view.coffee @@ -126,7 +126,7 @@ class TextEditorView extends View Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0] Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1] Object.defineProperty @::, 'active', get: -> @is(@getPaneView()?.activeView) - Object.defineProperty @::, 'isFocused', get: -> document.activeElement is @element or document.activeElement is @element.component?.refs.input.getDOMNode() + Object.defineProperty @::, 'isFocused', get: -> document.activeElement is @element or document.activeElement is @element.component?.hiddenInputComponent?.domNode Object.defineProperty @::, 'mini', get: -> @model?.isMini() Object.defineProperty @::, 'component', get: -> @element?.component From d337c88aea5e5e8bf6e46cc9d5cd9dd5ae7e794f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 18 Feb 2015 15:59:48 -0700 Subject: [PATCH 105/126] Delete overlay node from hash before removing --- src/overlay-manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overlay-manager.coffee b/src/overlay-manager.coffee index ecfbc8667..7d9c9d95d 100644 --- a/src/overlay-manager.coffee +++ b/src/overlay-manager.coffee @@ -9,8 +9,8 @@ class OverlayManager for id, overlayNode of @overlayNodesById unless presenter.state.content.overlays.hasOwnProperty(id) - overlayNode.remove() delete @overlayNodesById[id] + overlayNode.remove() return From de4d9951908a8ab95af0ee7bf403ca7feb55eb90 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 19 Feb 2015 14:26:39 -0700 Subject: [PATCH 106/126] Add document coordination methods to ViewRegistry These will assist in updating and reading the DOM in a non-blocking manner across components. --- spec/view-registry-spec.coffee | 74 ++++++++++++++++++++++++++++++++++ src/view-registry.coffee | 45 +++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee index 851faf109..90691578f 100644 --- a/spec/view-registry-spec.coffee +++ b/spec/view-registry-spec.coffee @@ -85,3 +85,77 @@ describe "ViewRegistry", -> expect(registry.getView(new TestModel) instanceof TestView).toBe true disposable.dispose() expect(-> registry.getView(new TestModel)).toThrow() + + describe "::updateDocument(fn) and ::readDocument(fn)", -> + frameRequests = null + + beforeEach -> + frameRequests = [] + spyOn(window, 'requestAnimationFrame').andCallFake (fn) -> frameRequests.push(fn) + + it "performs all pending writes before all pending reads on the next animation frame", -> + events = [] + + registry.updateDocument -> events.push('write 1') + registry.readDocument -> events.push('read 1') + registry.readDocument -> events.push('read 2') + registry.updateDocument -> events.push('write 2') + + expect(events).toEqual [] + + expect(frameRequests.length).toBe 1 + frameRequests[0]() + expect(events).toEqual ['write 1', 'write 2', 'read 1', 'read 2'] + + frameRequests = [] + events = [] + disposable = registry.updateDocument -> events.push('write 3') + registry.updateDocument -> events.push('write 4') + registry.readDocument -> events.push('read 3') + + disposable.dispose() + + expect(frameRequests.length).toBe 1 + frameRequests[0]() + expect(events).toEqual ['write 4', 'read 3'] + + it "pauses DOM polling when reads or writes are pending", -> + spyOn(window, 'setInterval').andCallFake(fakeSetInterval) + spyOn(window, 'clearInterval').andCallFake(fakeClearInterval) + events = [] + + registry.pollDocument -> events.push('poll') + registry.updateDocument -> events.push('write') + registry.readDocument -> events.push('read') + + advanceClock(registry.documentPollingInterval) + + frameRequests[0]() + expect(events).toEqual ['write', 'read'] + + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['write', 'read', 'poll'] + + describe "::pollDocument(fn)", -> + it "calls all registered reader functions on an interval until they are disabled via a returned disposable", -> + spyOn(window, 'setInterval').andCallFake(fakeSetInterval) + + events = [] + disposable1 = registry.pollDocument -> events.push('poll 1') + disposable2 = registry.pollDocument -> events.push('poll 2') + + expect(events).toEqual [] + + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2'] + + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2'] + + disposable1.dispose() + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2'] + + disposable2.dispose() + advanceClock(registry.documentPollingInterval) + expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2'] diff --git a/src/view-registry.coffee b/src/view-registry.coffee index a9104af5f..f3750171d 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -42,9 +42,14 @@ Grim = require 'grim' # ``` module.exports = class ViewRegistry + documentPollingInterval: 200 + constructor: -> @views = new WeakMap @providers = [] + @documentWriters = [] + @documentReaders = [] + @documentPollers = [] # Essential: Add a provider that will be used to construct views in the # workspace's view layer based on model objects in its model layer. @@ -150,3 +155,43 @@ class ViewRegistry findProvider: (object) -> find @providers, ({modelConstructor}) -> object instanceof modelConstructor + + updateDocument: (fn) -> + @documentWriters.push(fn) + @requestDocumentUpdate() + new Disposable => + @documentWriters = @documentWriters.filter (writer) -> writer isnt fn + + readDocument: (fn) -> + @documentReaders.push(fn) + @requestDocumentUpdate() + new Disposable => + @documentReaders = @documentReaders.filter (reader) -> reader isnt fn + + pollDocument: (fn) -> + @startPollingDocument() if @documentPollers.length is 0 + @documentPollers.push(fn) + new Disposable => + @documentPollers = @documentPollers.filter (poller) -> poller isnt fn + @stopPollingDocument() if @documentPollers.length is 0 + + requestDocumentUpdate: -> + unless @documentUpdateRequested + @documentUpdateRequested = true + @stopPollingDocument() + requestAnimationFrame(@performDocumentUpdate) + + performDocumentUpdate: => + @documentUpdateRequested = false + @startPollingDocument() + writer() while writer = @documentWriters.shift() + reader() while reader = @documentReaders.shift() + + startPollingDocument: -> + @pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval) + + stopPollingDocument: -> + window.clearInterval(@pollIntervalHandle) + + performDocumentPoll: => + poller() for poller in @documentPollers From 1d84d74e50c86d05d2e101516378422aa7aeb403 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 19 Feb 2015 14:57:30 -0700 Subject: [PATCH 107/126] Centralize text editor DOM interaction through atom.views This ensures that DOM writing, reading, and polling properly interleaves with DOM interactions from other text editors and any other code that coordinates via atom.views. Not sure about the location of it though. --- spec/spec-helper.coffee | 1 + spec/text-editor-component-spec.coffee | 13 +++++++------ src/text-editor-component.coffee | 24 ++++-------------------- src/view-registry.coffee | 8 ++++++++ 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index a8b7478c1..d34c72b1a 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -82,6 +82,7 @@ beforeEach -> atom.keymaps.keyBindings = _.clone(keyBindingsToRestore) atom.commands.restoreSnapshot(commandsToRestore) atom.styles.restoreSnapshot(styleElementsToRestore) + atom.views.clearDocumentRequests() atom.workspaceViewParentSelector = '#jasmine-content' diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 24a24ead6..2ebe9b373 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -193,7 +193,8 @@ describe "TextEditorComponent", -> expect(linesNode.style.backgroundColor).toBe backgroundColor wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)' - advanceClock(component.domPollingInterval) + + advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' @@ -562,7 +563,7 @@ describe "TextEditorComponent", -> # favor gutter color if it's assigned gutterNode.style.backgroundColor = 'rgb(255, 0, 0)' - advanceClock(component.domPollingInterval) + advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' @@ -2355,7 +2356,7 @@ describe "TextEditorComponent", -> expect(componentNode.querySelectorAll('.line').length).toBe 0 hiddenParent.style.display = 'block' - advanceClock(component.domPollingInterval) + advanceClock(atom.views.documentPollingInterval) expect(componentNode.querySelectorAll('.line').length).toBeGreaterThan 0 @@ -2465,13 +2466,13 @@ describe "TextEditorComponent", -> expect(parseInt(newHeight)).toBeLessThan wrapperNode.offsetHeight wrapperNode.style.height = newHeight - advanceClock(component.domPollingInterval) + advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(componentNode.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1) gutterWidth = componentNode.querySelector('.gutter').offsetWidth componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px' - advanceClock(component.domPollingInterval) + advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(componentNode.querySelector('.line').textContent).toBe "var quicksort " @@ -2480,7 +2481,7 @@ describe "TextEditorComponent", -> scrollViewNode.style.paddingLeft = 20 + 'px' componentNode.style.width = 30 * charWidth + 'px' - advanceClock(component.domPollingInterval) + advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(component.lineNodeForScreenRow(0).textContent).toBe "var quicksort = " diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index eb0a70293..cea4cae3e 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -15,7 +15,6 @@ ScrollbarCornerComponent = require './scrollbar-corner-component' module.exports = class TextEditorComponent scrollSensitivity: 0.4 - domPollingInterval: 100 cursorBlinkPeriod: 800 cursorBlinkResumeDelay: 100 lineOverdrawMargin: 15 @@ -29,8 +28,6 @@ class TextEditorComponent cursorMoved: false selectionChanged: false inputEnabled: true - domPollingIntervalId: null - domPollingPaused: false measureScrollbarsWhenShown: true measureLineHeightAndDefaultCharWidthWhenShown: true remeasureCharacterWidthsWhenShown: false @@ -94,7 +91,7 @@ class TextEditorComponent @disposables.add atom.themes.onDidChangeActiveThemes @onAllThemesLoaded @disposables.add scrollbarStyle.changes.onValue @refreshScrollbars - @domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval) + @disposables.add atom.views.pollDocument(@pollDOM) @updateSync() @checkForVisibilityChange() @@ -104,8 +101,6 @@ class TextEditorComponent @disposables.dispose() @presenter.destroy() window.removeEventListener 'resize', @requestHeightAndWidthMeasurement - clearInterval(@domPollingIntervalId) - @domPollingIntervalId = null updateSync: -> @oldState ?= {} @@ -153,6 +148,7 @@ class TextEditorComponent @hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged @hostElement.__spacePenView.trigger 'editor:display-updated' + readAfterUpdateSync: => @linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically mountGutterComponent: -> @@ -183,16 +179,16 @@ class TextEditorComponent @updateSync() else unless @updateRequested @updateRequested = true - requestAnimationFrame => + atom.views.updateDocument => @updateRequested = false @updateSync() if @editor.isAlive() + atom.views.readDocument(@readAfterUpdateSync) canUpdate: -> @mounted and @editor.isAlive() requestAnimationFrame: (fn) -> @updatesPaused = true - @pauseDOMPolling() requestAnimationFrame => fn() @updatesPaused = false @@ -550,19 +546,7 @@ class TextEditorComponent isVisible: -> @domNode.offsetHeight > 0 or @domNode.offsetWidth > 0 - pauseDOMPolling: -> - @domPollingPaused = true - @resumeDOMPollingAfterDelay ?= _.debounce(@resumeDOMPolling, 100) - @resumeDOMPollingAfterDelay() - - resumeDOMPolling: -> - @domPollingPaused = false - - resumeDOMPollingAfterDelay: null # created lazily - pollDOM: => - return if @domPollingPaused or @updateRequested or not @mounted - unless @checkForVisibilityChange() @sampleBackgroundColors() @measureHeightAndWidth() diff --git a/src/view-registry.coffee b/src/view-registry.coffee index f3750171d..47b3658a3 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -43,6 +43,8 @@ Grim = require 'grim' module.exports = class ViewRegistry documentPollingInterval: 200 + documentUpdateRequested: false + pollIntervalHandle: null constructor: -> @views = new WeakMap @@ -175,6 +177,12 @@ class ViewRegistry @documentPollers = @documentPollers.filter (poller) -> poller isnt fn @stopPollingDocument() if @documentPollers.length is 0 + clearDocumentRequests: -> + @documentReaders = [] + @documentWriters = [] + @documentPollers = [] + @documentUpdateRequested = false + requestDocumentUpdate: -> unless @documentUpdateRequested @documentUpdateRequested = true From dc752eda200725b49e1d04c51355ed0db49a952e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 19 Feb 2015 15:19:49 -0700 Subject: [PATCH 108/126] Update scrollHeight/Width before scrollTop/Left in dummy scrollbars This avoids jerky scroll behavior when auto-scrolling at the margins. --- src/scrollbar-component.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index 8eef5c3e6..3e94a0708 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -40,14 +40,14 @@ class ScrollbarComponent @domNode.style.bottom = @newState.bottom + 'px' @oldState.bottom = @newState.bottom - if @newState.scrollTop isnt @oldState.scrollTop - @domNode.scrollTop = @newState.scrollTop - @oldState.scrollTop = @newState.scrollTop - if @newState.scrollHeight isnt @oldState.scrollHeight @contentNode.style.height = @newState.scrollHeight + 'px' @oldState.scrollHeight = @newState.scrollHeight + if @newState.scrollTop isnt @oldState.scrollTop + @domNode.scrollTop = @newState.scrollTop + @oldState.scrollTop = @newState.scrollTop + updateHorizontal: -> if @newState.height isnt @oldState.height @domNode.style.height = @newState.height + 'px' @@ -57,13 +57,14 @@ class ScrollbarComponent @domNode.style.right = @newState.right + 'px' @oldState.right = @newState.right + if @newState.scrollWidth isnt @oldState.scrollWidth + @contentNode.style.width = @newState.scrollWidth + 'px' + @oldState.scrollWidth = @newState.scrollWidth + if @newState.scrollLeft isnt @oldState.scrollLeft @domNode.scrollLeft = @newState.scrollLeft @oldState.scrollLeft = @newState.scrollLeft - if @newState.scrollWidth isnt @oldState.scrollWidth - @contentNode.style.width = @newState.scrollWidth + 'px' - @oldState.scrollWidth = @newState.scrollWidth onScrollCallback: => switch @orientation From c728ad6d57337ee858800a29d7b94f2035fb0295 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 12 Feb 2015 15:47:09 -0800 Subject: [PATCH 109/126] Introduce atom.directory-provider service. A `Project` will always have a `DefaultDirectoryProvider` that will be used if there are no other `DirectoryProvider` objects that can produce a `Directory` for a path. --- spec/default-directory-provider-spec.coffee | 21 +++++++++++++ src/default-directory-provider.coffee | 33 ++++++++++++++++++++ src/project.coffee | 34 +++++++++++++-------- 3 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 spec/default-directory-provider-spec.coffee create mode 100644 src/default-directory-provider.coffee diff --git a/spec/default-directory-provider-spec.coffee b/spec/default-directory-provider-spec.coffee new file mode 100644 index 000000000..63da18523 --- /dev/null +++ b/spec/default-directory-provider-spec.coffee @@ -0,0 +1,21 @@ +DefaultDirectoryProvider = require "../src/default-directory-provider" +path = require "path" +temp = require "temp" + +describe "DefaultDirectoryProvider", -> + describe ".directoryForURISync(uri)", -> + it "returns a Directory with a path that matches the uri", -> + provider = new DefaultDirectoryProvider() + tmp = temp.mkdirSync() + + directory = provider.directoryForURISync(tmp) + expect(directory.getPath()).toEqual tmp + + describe ".directoryForURI(uri)", -> + it "returns a Promise that resolves to a Directory with a path that matches the uri", -> + provider = new DefaultDirectoryProvider() + tmp = temp.mkdirSync() + + waitsForPromise -> + provider.directoryForURI(tmp).then (directory) -> + expect(directory.getPath()).toEqual tmp diff --git a/src/default-directory-provider.coffee b/src/default-directory-provider.coffee new file mode 100644 index 000000000..a05e006f8 --- /dev/null +++ b/src/default-directory-provider.coffee @@ -0,0 +1,33 @@ +{Directory} = require 'pathwatcher' +fs = require 'fs-plus' +path = require 'path' + +module.exports = +class DefaultDirectoryProvider + + # Public: Create a Directory that corresponds to the specified URI. + # + # * `uri` {String} The path to the directory to add. This is guaranteed not to + # be contained by a {Directory} in `atom.project`. + # Returns: + # * {Directory} if the given URI is compatible with this provider. + # * `null` if the given URI is not compatibile with this provider. + directoryForURISync: (uri) -> + projectPath = path.normalize(uri) + + directoryPath = if fs.isDirectorySync(projectPath) + projectPath + else + path.dirname(projectPath) + + new Directory(directoryPath) + + # Public: Create a Directory that corresponds to the specified URI. + # + # * `uri` {String} The path to the directory to add. This is guaranteed not to + # be contained by a {Directory} in `atom.project`. + # Returns a Promise that resolves to: + # * {Directory} if the given URI is compatible with this provider. + # * `null` if the given URI is not compatibile with this provider. + directoryForURI: (uri) -> + Promise.resolve(@directoryForURISync(uri)) diff --git a/src/project.coffee b/src/project.coffee index 50bda3ed8..1f17e6610 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -8,9 +8,9 @@ Q = require 'q' {Model} = require 'theorist' {Subscriber} = require 'emissary' {Emitter} = require 'event-kit' +DefaultDirectoryProvider = require './default-directory-provider' Serializable = require 'serializable' TextBuffer = require 'text-buffer' -{Directory} = require 'pathwatcher' Grim = require 'grim' TextEditor = require './text-editor' @@ -41,6 +41,14 @@ class Project extends Model @rootDirectories = [] @repositories = [] + @directoryProviders = [new DefaultDirectoryProvider()] + atom.packages.serviceHub.consume( + 'atom.directory-provider', + '^0.1.0', + # New providers are added to the front of @directoryProviders because + # DefaultDirectoryProvider is a catch-all that will always provide a Directory. + (provider) => @directoryProviders.unshift(provider)) + # Mapping from the real path of a {Directory} to a {Promise} that resolves # to either a {Repository} or null. Ideally, the {Directory} would be used # as the key; however, there can be multiple {Directory} objects created for @@ -182,22 +190,22 @@ class Project extends Model Grim.deprecate("Use ::setPaths instead") @setPaths([path]) - # Public: Add a path the project's list of root paths + # Public: Add a path to the project's list of root paths # # * `projectPath` {String} The path to the directory to add. addPath: (projectPath, options) -> - projectPath = path.normalize(projectPath) + for directory in @getDirectories() + # Apparently a Directory does not believe it can contain itself, so we + # must also check whether the paths match. + return if directory.contains(projectPath) or directory.getPath() is projectPath - directoryPath = if fs.isDirectorySync(projectPath) - projectPath - else - path.dirname(projectPath) - - return if @getPaths().some (existingPath) -> - (directoryPath is existingPath) or - (directoryPath.indexOf(path.join(existingPath, path.sep)) is 0) - - directory = new Directory(directoryPath) + directory = null + for provider in @directoryProviders + break if directory = provider.directoryForURISync?(projectPath) + if directory is null + # This should never happen because DefaultDirectoryProvider should always + # return a Directory. + throw new Error(projectPath + ' could not be resolved to a directory') @rootDirectories.push(directory) repo = null From fad7b4b5954bf2dfa532fe3c6fc2797331ff1e83 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 19 Feb 2015 20:17:09 -0800 Subject: [PATCH 110/126] :arrow_up: metrics@0.45.0 Fix events!! --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fb5a14629..8d3eb08d7 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "keybinding-resolver": "0.29.0", "link": "0.30.0", "markdown-preview": "0.137.0", - "metrics": "0.44.0", + "metrics": "0.45.0", "notifications": "0.28.0", "open-on-github": "0.32.0", "package-generator": "0.38.0", From 461cd8c5fe887ca42a215fade7cfa7dc855c2e16 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 12 Feb 2015 15:47:09 -0800 Subject: [PATCH 111/126] Introduce atom.directory-provider service. A `Project` will always have a `DefaultDirectoryProvider` that will be used if there are no other `DirectoryProvider` objects that can produce a `Directory` for a path. --- spec/default-directory-provider-spec.coffee | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/default-directory-provider-spec.coffee b/spec/default-directory-provider-spec.coffee index 63da18523..e0726a282 100644 --- a/spec/default-directory-provider-spec.coffee +++ b/spec/default-directory-provider-spec.coffee @@ -11,6 +11,25 @@ describe "DefaultDirectoryProvider", -> directory = provider.directoryForURISync(tmp) expect(directory.getPath()).toEqual tmp + it "normalizes its input before creating a Directory for it", -> + provider = new DefaultDirectoryProvider() + tmp = temp.mkdirSync() + nonNormalizedPath = tmp + path.sep + ".." + path.sep + path.basename(tmp) + expect(tmp.contains("..")).toBe false + expect(nonNormalizedPath.contains("..")).toBe true + + directory = provider.directoryForURISync(nonNormalizedPath) + expect(directory.getPath()).toEqual tmp + + it "creates a Directory for its parent dir when passed a file", -> + provider = new DefaultDirectoryProvider() + tmp = temp.mkdirSync() + file = path.join(tmp, 'example.txt') + fs.writeFileSync(file, 'data') + + directory = provider.directoryForURISync(file) + expect(directory.getPath()).toEqual tmp + describe ".directoryForURI(uri)", -> it "returns a Promise that resolves to a Directory with a path that matches the uri", -> provider = new DefaultDirectoryProvider() From ede049554c04693188432051c4aa85b217d70a66 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 19 Feb 2015 20:28:36 -0800 Subject: [PATCH 112/126] formatting fixes --- src/default-directory-provider.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/default-directory-provider.coffee b/src/default-directory-provider.coffee index a05e006f8..7ecdbd354 100644 --- a/src/default-directory-provider.coffee +++ b/src/default-directory-provider.coffee @@ -8,7 +8,8 @@ class DefaultDirectoryProvider # Public: Create a Directory that corresponds to the specified URI. # # * `uri` {String} The path to the directory to add. This is guaranteed not to - # be contained by a {Directory} in `atom.project`. + # be contained by a {Directory} in `atom.project`. + # # Returns: # * {Directory} if the given URI is compatible with this provider. # * `null` if the given URI is not compatibile with this provider. @@ -25,7 +26,8 @@ class DefaultDirectoryProvider # Public: Create a Directory that corresponds to the specified URI. # # * `uri` {String} The path to the directory to add. This is guaranteed not to - # be contained by a {Directory} in `atom.project`. + # be contained by a {Directory} in `atom.project`. + # # Returns a Promise that resolves to: # * {Directory} if the given URI is compatible with this provider. # * `null` if the given URI is not compatibile with this provider. From bf9c4132b217d75a7d7c6275d88a14ff04444c48 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 19 Feb 2015 21:02:31 -0800 Subject: [PATCH 113/126] Create a comprehensive test for the new behavior in Project. --- spec/default-directory-provider-spec.coffee | 4 +- spec/project-spec.coffee | 70 +++++++++++++++++++++ src/project.coffee | 2 +- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/spec/default-directory-provider-spec.coffee b/spec/default-directory-provider-spec.coffee index e0726a282..780f2afd5 100644 --- a/spec/default-directory-provider-spec.coffee +++ b/spec/default-directory-provider-spec.coffee @@ -24,8 +24,8 @@ describe "DefaultDirectoryProvider", -> it "creates a Directory for its parent dir when passed a file", -> provider = new DefaultDirectoryProvider() tmp = temp.mkdirSync() - file = path.join(tmp, 'example.txt') - fs.writeFileSync(file, 'data') + file = path.join(tmp, "example.txt") + fs.writeFileSync(file, "data") directory = provider.directoryForURISync(file) expect(directory.getPath()).toEqual tmp diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 956895952..6944ac168 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -14,6 +14,76 @@ describe "Project", -> atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')]) describe "constructor", -> + it "enables a custom DirectoryProvider to supersede the DefaultDirectoryProvider", -> + remotePath = "ssh://foreign-directory:8080/" + class DummyDirectory + constructor: (@path) -> + getPath: -> @path + getFile: -> existsSync: -> false + getSubdirectory: -> existsSync: -> false + isRoot: -> true + off: -> + contains: (filePath) -> filePath.startsWith(remotePath) + + directoryProvider = + directoryForURISync: (uri) -> + if uri.startsWith("ssh://") + new DummyDirectory(uri) + else + null + directoryForURI: (uri) -> throw new Error("This should not be called.") + atom.packages.serviceHub.provide( + "atom.directory-provider", "0.1.0", directoryProvider) + + expect(atom.project.directoryProviders.length).toBe 2 + expect(atom.project.directoryProviders[0]).toBe directoryProvider + + tmp = temp.mkdirSync() + atom.project.setPaths([tmp, remotePath]) + directories = atom.project.getDirectories() + expect(directories.length).toBe 2 + + localDirectory = directories[0] + expect(localDirectory.getPath()).toBe tmp + expect(localDirectory instanceof Directory).toBe true + + dummyDirectory = directories[1] + expect(dummyDirectory.getPath()).toBe remotePath + expect(dummyDirectory instanceof DummyDirectory).toBe true + + expect(atom.project.getPaths()).toEqual([tmp, remotePath]) + + # Make sure that DummyDirectory.contains() is honored. + remotePathSubdirectory = remotePath + "a/subdirectory" + atom.project.addPath(remotePathSubdirectory) + expect(atom.project.getDirectories().length).toBe 2 + + # Make sure that a new DummyDirectory that is not contained by the first + # DummyDirectory can be added. + otherRemotePath = "ssh://other-foreign-directory:8080/" + atom.project.addPath(otherRemotePath) + newDirectories = atom.project.getDirectories() + expect(newDirectories.length).toBe 3 + otherDummyDirectory = newDirectories[2] + expect(otherDummyDirectory.getPath()).toBe otherRemotePath + expect(otherDummyDirectory instanceof DummyDirectory).toBe true + + it "a custom DirectoryProvider that returns null defaults to the DefaultDirectoryProvider", -> + directoryProvider = + directoryForURISync: (uri) -> null + directoryForURI: (uri) -> throw new Error("This should not be called.") + atom.packages.serviceHub.provide( + "atom.directory-provider", "0.1.0", directoryProvider) + + expect(atom.project.directoryProviders.length).toBe 2 + expect(atom.project.directoryProviders[0]).toBe directoryProvider + + tmp = temp.mkdirSync() + atom.project.setPaths([tmp]) + directories = atom.project.getDirectories() + expect(directories.length).toBe 1 + expect(directories[0].getPath()).toBe tmp + it "tries to update repositories when a new RepositoryProvider is registered", -> tmp = temp.mkdirSync('atom-project') atom.project.setPaths([tmp]) diff --git a/src/project.coffee b/src/project.coffee index 1f17e6610..d3fbd88eb 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -167,7 +167,7 @@ class Project extends Model # Public: Get an {Array} of {String}s containing the paths of the project's # directories. - getPaths: -> rootDirectory.path for rootDirectory in @rootDirectories + getPaths: -> rootDirectory.getPath() for rootDirectory in @rootDirectories getPath: -> Grim.deprecate("Use ::getPaths instead") @getPaths()[0] From 54c70706480a2f67a3a7ddb2c16fd0f9506cc362 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 19 Feb 2015 21:16:30 -0800 Subject: [PATCH 114/126] kill assertions about directoryProviders --- spec/project-spec.coffee | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 6944ac168..29329dfee 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -35,9 +35,6 @@ describe "Project", -> atom.packages.serviceHub.provide( "atom.directory-provider", "0.1.0", directoryProvider) - expect(atom.project.directoryProviders.length).toBe 2 - expect(atom.project.directoryProviders[0]).toBe directoryProvider - tmp = temp.mkdirSync() atom.project.setPaths([tmp, remotePath]) directories = atom.project.getDirectories() @@ -75,9 +72,6 @@ describe "Project", -> atom.packages.serviceHub.provide( "atom.directory-provider", "0.1.0", directoryProvider) - expect(atom.project.directoryProviders.length).toBe 2 - expect(atom.project.directoryProviders[0]).toBe directoryProvider - tmp = temp.mkdirSync() atom.project.setPaths([tmp]) directories = atom.project.getDirectories() From d5abd8764352f1ad62c40003f05638d6bc06e5c8 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 19 Feb 2015 21:35:07 -0800 Subject: [PATCH 115/126] reword it() message --- spec/project-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 29329dfee..466431faf 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -65,7 +65,7 @@ describe "Project", -> expect(otherDummyDirectory.getPath()).toBe otherRemotePath expect(otherDummyDirectory instanceof DummyDirectory).toBe true - it "a custom DirectoryProvider that returns null defaults to the DefaultDirectoryProvider", -> + it "uses the default directory provider if no custom provider can handle the URI", -> directoryProvider = directoryForURISync: (uri) -> null directoryForURI: (uri) -> throw new Error("This should not be called.") From d1e6dba2c9abb71b2767531d45679a99bf6ea24b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Feb 2015 22:19:08 -0800 Subject: [PATCH 116/126] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d3eb08d7..c12864b92 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "symbols-view": "0.83.0", "tabs": "0.67.0", "timecop": "0.30.0", - "tree-view": "0.159.0", + "tree-view": "0.160.0", "update-package-dependencies": "0.8.0", "welcome": "0.24.0", "whitespace": "0.29.0", From 32d393d26f1f2b1537fbdac4370d753fe84b1011 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 19 Feb 2015 17:14:35 -0700 Subject: [PATCH 117/126] =?UTF-8?q?Pause=20polling=20when=20updates=20are?= =?UTF-8?q?=20requested,=20but=20don=E2=80=99t=20start=20polling=20over?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blinking cursor was ensuring that we never polled in certain cases. We need to allow the interval to continue polling at a normal pace, but just avoid doing any work that could delay the next animation frame. --- spec/text-editor-component-spec.coffee | 3 ++- spec/view-registry-spec.coffee | 5 +++-- src/view-registry.coffee | 10 +++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 2ebe9b373..5d6e7ed85 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2473,7 +2473,8 @@ describe "TextEditorComponent", -> gutterWidth = componentNode.querySelector('.gutter').offsetWidth componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px' advanceClock(atom.views.documentPollingInterval) - nextAnimationFrame() + nextAnimationFrame() # won't poll until cursor blinks + nextAnimationFrame() # handle update requested by poll expect(componentNode.querySelector('.line').textContent).toBe "var quicksort " it "accounts for the scroll view's padding when determining the wrap location", -> diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee index 90691578f..4d0d72abc 100644 --- a/spec/view-registry-spec.coffee +++ b/spec/view-registry-spec.coffee @@ -129,12 +129,13 @@ describe "ViewRegistry", -> registry.readDocument -> events.push('read') advanceClock(registry.documentPollingInterval) + expect(events).toEqual [] frameRequests[0]() - expect(events).toEqual ['write', 'read'] + expect(events).toEqual ['write', 'read', 'poll'] advanceClock(registry.documentPollingInterval) - expect(events).toEqual ['write', 'read', 'poll'] + expect(events).toEqual ['write', 'read', 'poll', 'poll'] describe "::pollDocument(fn)", -> it "calls all registered reader functions on an interval until they are disabled via a returned disposable", -> diff --git a/src/view-registry.coffee b/src/view-registry.coffee index 47b3658a3..d17de4c3b 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -44,6 +44,7 @@ module.exports = class ViewRegistry documentPollingInterval: 200 documentUpdateRequested: false + performDocumentPollAfterUpdate: false pollIntervalHandle: null constructor: -> @@ -186,14 +187,13 @@ class ViewRegistry requestDocumentUpdate: -> unless @documentUpdateRequested @documentUpdateRequested = true - @stopPollingDocument() requestAnimationFrame(@performDocumentUpdate) performDocumentUpdate: => @documentUpdateRequested = false - @startPollingDocument() writer() while writer = @documentWriters.shift() reader() while reader = @documentReaders.shift() + @performDocumentPoll() if @performDocumentPollAfterUpdate startPollingDocument: -> @pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval) @@ -202,4 +202,8 @@ class ViewRegistry window.clearInterval(@pollIntervalHandle) performDocumentPoll: => - poller() for poller in @documentPollers + if @documentUpdateRequested + @performDocumentPollAfterUpdate = true + else + @performDocumentPollAfterUpdate = false + poller() for poller in @documentPollers From 64e2c70fc4589b55037fb2ec0a0c01bcaa28144a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 20 Feb 2015 09:53:06 -0800 Subject: [PATCH 118/126] Prepare 0.183 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c12864b92..accad249a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "0.182.0", + "version": "0.183.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From eb868fdec62a168b52458bdc4fc51b95799a4370 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 20 Feb 2015 10:17:59 -0800 Subject: [PATCH 119/126] :arrow_up: snippets@0.76.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index accad249a..f951c2593 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "package-generator": "0.38.0", "release-notes": "0.51.0", "settings-view": "0.183.0", - "snippets": "0.75.0", + "snippets": "0.76.0", "spell-check": "0.54.0", "status-bar": "0.60.0", "styleguide": "0.44.0", From 70089b9c827d89dac48ca1dbfb56006b6a670f8b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Feb 2015 13:19:51 -0800 Subject: [PATCH 120/126] :arrow_up: fuzzy-finder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f951c2593..3192691e9 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "encoding-selector": "0.18.0", "exception-reporting": "0.24.0", "find-and-replace": "0.157.0", - "fuzzy-finder": "0.67.0", + "fuzzy-finder": "0.68.0", "git-diff": "0.52.0", "go-to-line": "0.30.0", "grammar-selector": "0.45.0", From 55f80f33e6d418f6992fa3a0920ddd7971d12d06 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 20 Feb 2015 15:28:02 -0700 Subject: [PATCH 121/126] Avoid allocating an array on every poll @jashkenas Love CoffeeScript but I wish I could opt out of loops being list comprehensions. --- src/view-registry.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view-registry.coffee b/src/view-registry.coffee index d17de4c3b..4dbb5594e 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -207,3 +207,4 @@ class ViewRegistry else @performDocumentPollAfterUpdate = false poller() for poller in @documentPollers + return From c1c09624598d40f2b9b1aeff28992c06e1e28189 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Feb 2015 14:58:36 -0800 Subject: [PATCH 122/126] :arrow_up: git-diff --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3192691e9..7f69731af 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "exception-reporting": "0.24.0", "find-and-replace": "0.157.0", "fuzzy-finder": "0.68.0", - "git-diff": "0.52.0", + "git-diff": "0.53.0", "go-to-line": "0.30.0", "grammar-selector": "0.45.0", "image-view": "0.49.0", From 7ca9e5614c097a2ceb561c3f578ad6ad374fc8d0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 20 Feb 2015 15:11:46 -0800 Subject: [PATCH 123/126] Go back to using or check instead of pattern --- src/babel.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/babel.coffee b/src/babel.coffee index 98b6de07e..766324ce0 100644 --- a/src/babel.coffee +++ b/src/babel.coffee @@ -132,8 +132,10 @@ transpile = (sourceCode, filePath, cachePath) -> # either generated on the fly or pulled from cache. loadFile = (module, filePath) -> sourceCode = fs.readFileSync(filePath, 'utf8') - unless /^("use 6to5"|'use 6to5'|"use babel"|'use babel')/.test(sourceCode) - return module._compile(sourceCode, filePath) + return module._compile(sourceCode, filePath) unless sourceCode.startsWith('"use 6to5"') or + sourceCode.startsWith("'use 6to5'") or + sourceCode.startsWith('"use babel"') or + sourceCode.startsWith("'use babel'") cachePath = getCachePath(sourceCode) js = getCachedJavaScript(cachePath) ? transpile(sourceCode, filePath, cachePath) From 631c95643dfed29f605236397c09bda60345a2ab Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 20 Feb 2015 15:24:21 -0800 Subject: [PATCH 124/126] to5 -> babel --- spec/compile-cache-spec.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee index c01948f73..164b8caef 100644 --- a/spec/compile-cache-spec.coffee +++ b/spec/compile-cache-spec.coffee @@ -10,19 +10,19 @@ describe "Compile Cache", -> it "adds the path to the correct CSON, CoffeeScript, or babel cache", -> spyOn(CSON, 'readFileSync').andCallThrough() spyOn(CoffeeCache, 'addPathToCache').andCallThrough() - spyOn(to5, 'addPathToCache').andCallThrough() + spyOn(babel, 'addPathToCache').andCallThrough() CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'cson.cson')) expect(CSON.readFileSync.callCount).toBe 1 expect(CoffeeCache.addPathToCache.callCount).toBe 0 - expect(to5.addPathToCache.callCount).toBe 0 + expect(babel.addPathToCache.callCount).toBe 0 CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'coffee.coffee')) expect(CSON.readFileSync.callCount).toBe 1 expect(CoffeeCache.addPathToCache.callCount).toBe 1 - expect(to5.addPathToCache.callCount).toBe 0 + expect(babel.addPathToCache.callCount).toBe 0 CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'babel', 'double-quotes.js')) expect(CSON.readFileSync.callCount).toBe 1 expect(CoffeeCache.addPathToCache.callCount).toBe 1 - expect(to5.addPathToCache.callCount).toBe 1 + expect(babel.addPathToCache.callCount).toBe 1 From 34a75d1c32199b2ff7ba0dc32b083a13310855cf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 20 Feb 2015 15:32:44 -0800 Subject: [PATCH 125/126] :arrow_up: spell-check@0.55 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a36f661be..39629d4a1 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "release-notes": "0.51.0", "settings-view": "0.183.0", "snippets": "0.76.0", - "spell-check": "0.54.0", + "spell-check": "0.55.0", "status-bar": "0.60.0", "styleguide": "0.44.0", "symbols-view": "0.83.0", From 25befa736831301626e31000cc64d7053f844a6b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 20 Feb 2015 15:55:41 -0800 Subject: [PATCH 126/126] Update fixture path in compile cache spec --- spec/compile-cache-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee index 164b8caef..534457157 100644 --- a/spec/compile-cache-spec.coffee +++ b/spec/compile-cache-spec.coffee @@ -22,7 +22,7 @@ describe "Compile Cache", -> expect(CoffeeCache.addPathToCache.callCount).toBe 1 expect(babel.addPathToCache.callCount).toBe 0 - CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'babel', 'double-quotes.js')) + CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'babel', 'babel-double-quotes.js')) expect(CSON.readFileSync.callCount).toBe 1 expect(CoffeeCache.addPathToCache.callCount).toBe 1 expect(babel.addPathToCache.callCount).toBe 1