From c9b51a4f5aac7b22525eeb7915b3ca3e7aa3d721 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Fri, 24 Jul 2015 18:10:52 +0300 Subject: [PATCH 001/198] :memo: Update docs for marker callback --- src/marker.coffee | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/marker.coffee b/src/marker.coffee index 536edfb63..4e44d2bf1 100644 --- a/src/marker.coffee +++ b/src/marker.coffee @@ -82,10 +82,14 @@ class Marker # # * `callback` {Function} to be called when the marker changes. # * `event` {Object} with the following keys: - # * `oldHeadPosition` {Point} representing the former head position - # * `newHeadPosition` {Point} representing the new head position - # * `oldTailPosition` {Point} representing the former tail position - # * `newTailPosition` {Point} representing the new tail position + # * `oldHeadBufferPosition` {Point} representing the former head buffer position + # * `newHeadBufferPosition` {Point} representing the new head buffer position + # * `oldTailBufferPosition` {Point} representing the former tail buffer position + # * `newTailBufferPosition` {Point} representing the new tail buffer position + # * `oldHeadScreenPosition` {Point} representing the former head screen position + # * `newHeadScreenPosition` {Point} representing the new head screen position + # * `oldTailScreenPosition` {Point} representing the former tail screen position + # * `newTailScreenPosition` {Point} representing the new tail screen position # * `wasValid` {Boolean} indicating whether the marker was valid before the change # * `isValid` {Boolean} indicating whether the marker is now valid # * `hadTail` {Boolean} indicating whether the marker had a tail before the change From 989bb861992f4bf5383842239a46897588e3e195 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 29 Jul 2015 11:27:24 -0700 Subject: [PATCH 002/198] Start work on custom scope -> filetypes configuration --- spec/grammars-spec.coffee | 5 +++ src/config-schema.coffee | 3 ++ src/grammar-registry.coffee | 87 ++++++++++++++++++++++++++++++++++++- src/tokenized-buffer.coffee | 4 +- 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee index cc975468f..ef48ec15b 100644 --- a/spec/grammars-spec.coffee +++ b/spec/grammars-spec.coffee @@ -110,6 +110,11 @@ describe "the `grammars` global", -> expect(-> atom.grammars.selectGrammar(null, '')).not.toThrow() expect(-> atom.grammars.selectGrammar(null, null)).not.toThrow() + describe "when the user has custom grammar filetypes", -> + it "considers the custom filetypes as well as those defined in the grammar", -> + atom.config.set('core.fileTypesByScope', 'source.ruby': ['Cheffile']) + expect(atom.grammars.selectGrammar('build/Cheffile', 'cookbook "postgres"').scopeName).toBe 'source.ruby' + describe ".removeGrammar(grammar)", -> it "removes the grammar, so it won't be returned by selectGrammar", -> grammar = atom.grammars.selectGrammar('foo.js') diff --git a/src/config-schema.coffee b/src/config-schema.coffee index d46cdf589..6ffcfa632 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -26,6 +26,9 @@ module.exports = default: [] items: type: 'string' + fileTypesByScope: + type: 'object' + default: {} themes: type: 'array' default: ['one-dark-ui', 'one-dark-syntax'] diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee index 7b1ef823f..6a84b8ddd 100644 --- a/src/grammar-registry.coffee +++ b/src/grammar-registry.coffee @@ -1,7 +1,11 @@ +_ = require 'underscore-plus' {Emitter} = require 'event-kit' {includeDeprecatedAPIs, deprecate} = require 'grim' FirstMate = require 'first-mate' Token = require './token' +fs = require 'fs-plus' + +PathSplitRegex = new RegExp("[/.]") # Extended: Syntax class holding the grammars used for tokenizing. # @@ -39,7 +43,7 @@ class GrammarRegistry extends FirstMate.GrammarRegistry bestMatch = null highestScore = -Infinity for grammar in @grammars - score = grammar.getScore(filePath, fileContents) + score = @getGrammarScore(grammar, filePath, fileContents) if score > highestScore or not bestMatch? bestMatch = grammar highestScore = score @@ -47,6 +51,87 @@ class GrammarRegistry extends FirstMate.GrammarRegistry bestMatch = grammar unless grammar.bundledPackage bestMatch + # Extended: Returns a {Number} representing how well the grammar matches the + # `filePath` and `contents`. + getGrammarScore: (grammar, filePath, contents) -> + contents = fs.readFileSync(filePath, 'utf8') if not contents? and fs.isFileSync(filePath) + + if @grammarOverrideForPath(filePath) is grammar.scopeName + 2 + (filePath?.length ? 0) + else if @grammarMatchesContents(grammar, contents) + 1 + (filePath?.length ? 0) + else + @getGrammarPathScore(grammar, filePath) + + getGrammarPathScore: (grammar, filePath) -> + return -1 unless filePath + filePath = filePath.replace(/\\/g, '/') if process.platform is 'win32' + + pathComponents = filePath.toLowerCase().split(PathSplitRegex) + pathScore = -1 + + fileTypes = grammar.fileTypes + if customFileTypes = atom.config.get('core.fileTypesByScope')?[grammar.scopeName] + fileTypes = fileTypes.concat(customFileTypes) + + for fileType in fileTypes + fileTypeComponents = fileType.toLowerCase().split(PathSplitRegex) + pathSuffix = pathComponents[-fileTypeComponents.length..-1] + if _.isEqual(pathSuffix, fileTypeComponents) + pathScore = Math.max(pathScore, fileType.length) + pathScore + + grammarMatchesContents: (grammar, contents) -> + return false unless contents? and grammar.firstLineRegex? + + escaped = false + numberOfNewlinesInRegex = 0 + for character in grammar.firstLineRegex.source + switch character + when '\\' + escaped = !escaped + when 'n' + numberOfNewlinesInRegex++ if escaped + escaped = false + else + escaped = false + lines = contents.split('\n') + grammar.firstLineRegex.testSync(lines[0..numberOfNewlinesInRegex].join('\n')) + + # Public: Get the grammar override for the given file path. + # + # * `filePath` A {String} file path. + # + # Returns a {Grammar} or undefined. + grammarOverrideForPath: (filePath) -> + @grammarOverridesByPath[filePath] + + # Public: Set the grammar override for the given file path. + # + # * `filePath` A non-empty {String} file path. + # * `scopeName` A {String} such as `"source.js"`. + # + # Returns a {Grammar} or undefined. + setGrammarOverrideForPath: (filePath, scopeName) -> + if filePath + @grammarOverridesByPath[filePath] = scopeName + + # Public: Remove the grammar override for the given file path. + # + # * `filePath` A {String} file path. + # + # Returns undefined. + clearGrammarOverrideForPath: (filePath) -> + delete @grammarOverridesByPath[filePath] + undefined + + # Public: Remove all grammar overrides. + # + # Returns undefined. + clearGrammarOverrides: -> + @grammarOverridesByPath = {} + undefined + clearObservers: -> @off() if includeDeprecatedAPIs @emitter = new Emitter diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index b4b381c63..ca525c34c 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -68,7 +68,7 @@ class TokenizedBuffer extends Model if grammar.injectionSelector? @retokenizeLines() if @hasTokenForSelector(grammar.injectionSelector) else - newScore = grammar.getScore(@buffer.getPath(), @getGrammarSelectionContent()) + newScore = atom.grammars.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent()) @setGrammar(grammar, newScore) if newScore > @currentGrammarScore setGrammar: (grammar, score) -> @@ -76,7 +76,7 @@ class TokenizedBuffer extends Model @grammar = grammar @rootScopeDescriptor = new ScopeDescriptor(scopes: [@grammar.scopeName]) - @currentGrammarScore = score ? grammar.getScore(@buffer.getPath(), @getGrammarSelectionContent()) + @currentGrammarScore = score ? atom.grammars.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent()) @grammarUpdateDisposable?.dispose() @grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines() From 1ee382a4cb979d121b75ec68eaf0fe074aab9d65 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 29 Jul 2015 13:18:46 -0700 Subject: [PATCH 003/198] Add GrammarRegistry tests removed from first-mate --- spec/grammars-spec.coffee | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee index ef48ec15b..cc2f6bf6c 100644 --- a/spec/grammars-spec.coffee +++ b/spec/grammars-spec.coffee @@ -1,6 +1,7 @@ path = require 'path' fs = require 'fs-plus' temp = require 'temp' +GrammarRegistry = require '../src/grammar-registry' describe "the `grammars` global", -> beforeEach -> @@ -16,6 +17,9 @@ describe "the `grammars` global", -> waitsForPromise -> atom.packages.activatePackage('language-ruby') + waitsForPromise -> + atom.packages.activatePackage('language-git') + afterEach -> atom.packages.deactivatePackages() atom.packages.unloadPackages() @@ -30,6 +34,30 @@ describe "the `grammars` global", -> expect(grammars2.selectGrammar(filePath).name).toBe 'Ruby' describe ".selectGrammar(filePath)", -> + it "always returns a grammar", -> + registry = new GrammarRegistry() + expect(registry.selectGrammar().scopeName).toBe 'text.plain.null-grammar' + + it "selects the text.plain grammar over the null grammar", -> + expect(atom.grammars.selectGrammar('test.txt').scopeName).toBe 'text.plain' + + it "selects a grammar based on the file path case insensitively", -> + expect(atom.grammars.selectGrammar('/tmp/source.coffee').scopeName).toBe 'source.coffee' + expect(atom.grammars.selectGrammar('/tmp/source.COFFEE').scopeName).toBe 'source.coffee' + + describe "on Windows", -> + originalPlatform = null + + beforeEach -> + originalPlatform = process.platform + Object.defineProperty process, 'platform', value: 'win32' + + afterEach -> + Object.defineProperty process, 'platform', value: originalPlatform + + it "normalizes back slashes to forward slashes when matching the fileTypes", -> + expect(atom.grammars.selectGrammar('something\\.git\\config').scopeName).toBe 'source.git-config' + it "can use the filePath to load the correct grammar based on the grammar's filetype", -> waitsForPromise -> atom.packages.activatePackage('language-git') From aa8fb391aa681ab171224cf0fe71f8d3aac1781f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 29 Jul 2015 13:58:54 -0700 Subject: [PATCH 004/198] Handle ties between custom and built-in grammar file types --- spec/grammars-spec.coffee | 16 ++++++++++++++-- src/grammar-registry.coffee | 5 ++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee index cc2f6bf6c..e2f8df568 100644 --- a/spec/grammars-spec.coffee +++ b/spec/grammars-spec.coffee @@ -138,11 +138,23 @@ describe "the `grammars` global", -> expect(-> atom.grammars.selectGrammar(null, '')).not.toThrow() expect(-> atom.grammars.selectGrammar(null, null)).not.toThrow() - describe "when the user has custom grammar filetypes", -> - it "considers the custom filetypes as well as those defined in the grammar", -> + describe "when the user has custom grammar file types", -> + it "considers the custom file types as well as those defined in the grammar", -> atom.config.set('core.fileTypesByScope', 'source.ruby': ['Cheffile']) expect(atom.grammars.selectGrammar('build/Cheffile', 'cookbook "postgres"').scopeName).toBe 'source.ruby' + it "favors user-defined file types over built-in ones of equal length", -> + atom.config.set('core.fileTypesByScope', + 'source.coffee': ['Rakefile'], + 'source.ruby': ['Cakefile'] + ) + expect(atom.grammars.selectGrammar('Rakefile', '').scopeName).toBe 'source.coffee' + expect(atom.grammars.selectGrammar('Cakefile', '').scopeName).toBe 'source.ruby' + + it "favors grammars with matching first-line-regexps even if custom file types match the file", -> + atom.config.set('core.fileTypesByScope', 'source.ruby': ['bootstrap']) + expect(atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node').scopeName).toBe 'source.js' + describe ".removeGrammar(grammar)", -> it "removes the grammar, so it won't be returned by selectGrammar", -> grammar = atom.grammars.selectGrammar('foo.js') diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee index 6a84b8ddd..4558dee8b 100644 --- a/src/grammar-registry.coffee +++ b/src/grammar-registry.coffee @@ -74,11 +74,14 @@ class GrammarRegistry extends FirstMate.GrammarRegistry if customFileTypes = atom.config.get('core.fileTypesByScope')?[grammar.scopeName] fileTypes = fileTypes.concat(customFileTypes) - for fileType in fileTypes + for fileType, i in fileTypes fileTypeComponents = fileType.toLowerCase().split(PathSplitRegex) pathSuffix = pathComponents[-fileTypeComponents.length..-1] if _.isEqual(pathSuffix, fileTypeComponents) pathScore = Math.max(pathScore, fileType.length) + if i >= grammar.fileTypes.length + pathScore += 0.5 + pathScore grammarMatchesContents: (grammar, contents) -> From 9e9f670ac37e22d1da7c45a29e9cfa0be2546c6e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 29 Jul 2015 14:10:19 -0700 Subject: [PATCH 005/198] :shirt: Replace ! -> 'not' --- src/grammar-registry.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee index 4558dee8b..c7924843e 100644 --- a/src/grammar-registry.coffee +++ b/src/grammar-registry.coffee @@ -92,7 +92,7 @@ class GrammarRegistry extends FirstMate.GrammarRegistry for character in grammar.firstLineRegex.source switch character when '\\' - escaped = !escaped + escaped = not escaped when 'n' numberOfNewlinesInRegex++ if escaped escaped = false From 84f72c880a8f7976d03c8048f145cedf74d8da9f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 30 Jul 2015 18:28:06 -0700 Subject: [PATCH 006/198] Config - don't allow setting key-paths inside of strings, arrays, etc --- spec/config-spec.coffee | 13 +++++++++++-- src/config.coffee | 14 +++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index b75a121a1..cd6e3651d 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -1142,8 +1142,8 @@ describe "Config", -> type: 'integer' default: 12 - expect(atom.config.getSchema('foo.baz')).toBeUndefined() - expect(atom.config.getSchema('foo.bar.anInt.baz')).toBeUndefined() + expect(atom.config.getSchema('foo.baz')).toBe(null) + expect(atom.config.getSchema('foo.bar.anInt.baz')).toBe(false) it "respects the schema for scoped settings", -> schema = @@ -1380,6 +1380,10 @@ describe "Config", -> expect(atom.config.set('foo.bar.aString', nope: 'nope')).toBe false expect(atom.config.get('foo.bar.aString')).toBe 'ok' + it 'does not allow setting children of that key-path', -> + expect(atom.config.set('foo.bar.aString.something', 123)).toBe false + expect(atom.config.get('foo.bar.aString')).toBe 'ok' + describe 'when the schema has a "maximumLength" key', -> it "trims the string to be no longer than the specified maximum", -> schema = @@ -1438,6 +1442,11 @@ describe "Config", -> atom.config.set 'foo.bar', ['2', '3', '4'] expect(atom.config.get('foo.bar')).toEqual [2, 3, 4] + it 'does not allow setting children of that key-path', -> + expect(atom.config.set('foo.bar.child', 123)).toBe false + expect(atom.config.set('foo.bar.child.grandchild', 123)).toBe false + expect(atom.config.get('foo.bar')).toEqual [1, 2, 3] + describe 'when the value has a "color" type', -> beforeEach -> schema = diff --git a/src/config.coffee b/src/config.coffee index b713bd024..c3983fcbe 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -665,12 +665,18 @@ class Config # # Returns an {Object} eg. `{type: 'integer', default: 23, minimum: 1}`. # Returns `null` when the keyPath has no schema specified. + # Returns `false` when the key-path is not accessible from the root schema. getSchema: (keyPath) -> keys = splitKeyPath(keyPath) schema = @schema for key in keys - break unless schema? - schema = schema.properties?[key] + if schema.type is 'object' + childSchema = schema.properties?[key] + unless childSchema? + return null + else + return false + schema = childSchema schema # Extended: Get the {String} path to the config file being used. @@ -948,7 +954,9 @@ class Config catch e undefined else - value = @constructor.executeSchemaEnforcers(keyPath, value, schema) if schema = @getSchema(keyPath) + if (schema = @getSchema(keyPath))? + throw new Error("Illegal key path #{keyPath}") if schema is false + value = @constructor.executeSchemaEnforcers(keyPath, value, schema) value # When the schema is changed / added, there may be values set in the config From fbd684926eecaa349ad4a7c2790cc391ee98281b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 30 Jul 2015 18:28:41 -0700 Subject: [PATCH 007/198] Config - Support additionalProperties field of object schemas --- spec/config-spec.coffee | 41 +++++++++++++++++++++++++++++++++++++++++ src/config.coffee | 20 +++++++++++++++++--- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index cd6e3651d..1821b7e01 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -1429,6 +1429,47 @@ describe "Config", -> expect(atom.config.get('foo.bar.anInt')).toEqual 12 expect(atom.config.get('foo.bar.nestedObject.nestedBool')).toEqual true + describe "when the value has additionalProperties set to false", -> + it 'does not allow other properties to be set on the object', -> + atom.config.setSchema('foo.bar', + type: 'object' + properties: + anInt: + type: 'integer' + default: 12 + additionalProperties: false + ) + + expect(atom.config.set('foo.bar', {anInt: 5, somethingElse: 'ok'})).toBe true + expect(atom.config.get('foo.bar.anInt')).toBe 5 + expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined() + + expect(atom.config.set('foo.bar.somethingElse', {anInt: 5})).toBe false + expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined() + + describe 'when the value has an additionalProperties schema', -> + it 'validates properties of the object against that schema', -> + atom.config.setSchema('foo.bar', + type: 'object' + properties: + anInt: + type: 'integer' + default: 12 + additionalProperties: + type: 'string' + ) + + expect(atom.config.set('foo.bar', {anInt: 5, somethingElse: 'ok'})).toBe true + expect(atom.config.get('foo.bar.anInt')).toBe 5 + expect(atom.config.get('foo.bar.somethingElse')).toBe 'ok' + + expect(atom.config.set('foo.bar.somethingElse', 7)).toBe false + expect(atom.config.get('foo.bar.somethingElse')).toBe 'ok' + + expect(atom.config.set('foo.bar', {anInt: 6, somethingElse: 7})).toBe true + expect(atom.config.get('foo.bar.anInt')).toBe 6 + expect(atom.config.get('foo.bar.somethingElse')).toBe undefined + describe 'when the value has an "array" type', -> beforeEach -> schema = diff --git a/src/config.coffee b/src/config.coffee index c3983fcbe..f26599a0b 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -673,7 +673,12 @@ class Config if schema.type is 'object' childSchema = schema.properties?[key] unless childSchema? - return null + if isPlainObject(schema.additionalProperties) + childSchema = schema.additionalProperties + else if schema.additionalProperties is false + return false + else + return null else return false schema = childSchema @@ -1085,17 +1090,26 @@ Config.addSchemaEnforcers throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be an object") unless isPlainObject(value) return value unless schema.properties? + defaultChildSchema = null + allowsAdditionalProperties = true + if isPlainObject(schema.additionalProperties) + defaultChildSchema = schema.additionalProperties + if schema.additionalProperties is false + allowsAdditionalProperties = false + newValue = {} for prop, propValue of value - childSchema = schema.properties[prop] + childSchema = schema.properties[prop] ? defaultChildSchema if childSchema? try newValue[prop] = @executeSchemaEnforcers("#{keyPath}.#{prop}", propValue, childSchema) catch error console.warn "Error setting item in object: #{error.message}" - else + else if allowsAdditionalProperties # Just pass through un-schema'd values newValue[prop] = propValue + else + console.warn "Illegal object key: #{keyPath}.#{prop}" newValue From 506dfc863cab3b4b84321d193f9c72f86441c897 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 30 Jul 2015 18:29:01 -0700 Subject: [PATCH 008/198] Require fileTypesByScope entries to be arrays of strings --- src/config-schema.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 6ffcfa632..93d0dcdc2 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -29,6 +29,10 @@ module.exports = fileTypesByScope: type: 'object' default: {} + additionalProperties: + type: 'array' + items: + type: 'string' themes: type: 'array' default: ['one-dark-ui', 'one-dark-syntax'] From f5c8b04337f47c353f8b2277369797183d5fed06 Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Wed, 22 Jul 2015 11:16:27 +0200 Subject: [PATCH 009/198] Add function that only copies selected text unless the cursor is at the beginning of the line. --- src/text-editor-element.coffee | 1 + src/text-editor.coffee | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 85bcfd145..fc027b0d8 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -302,6 +302,7 @@ atom.commands.add 'atom-text-editor', stopEventPropagationAndGroupUndo( 'editor:transpose': -> @transpose() 'editor:upper-case': -> @upperCase() 'editor:lower-case': -> @lowerCase() + 'editor:copy-selection': -> @copyOnlySelectedText() ) atom.commands.add 'atom-text-editor:not([mini])', stopEventPropagation( diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8a4e40a11..220e99f2b 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2587,6 +2587,18 @@ class TextEditor extends Model maintainClipboard = true return + + # Essential: For each selection, only copy hightlighted text. Line is coptied if no selected text and cursor is at the beginning of the line + copyOnlySelectedText: -> + maintainClipboard = false + for selection in @getSelectionsOrderedByBufferPosition() + if not selection.isEmpty() + selection.copy(maintainClipboard, true) + else if selection.isEmpty() and selection.cursor.isAtBeginningOfLine() + @copySelectedText() + maintainClipboard = true + return + # Essential: For each selection, cut the selected text. cutSelectedText: -> maintainClipboard = false From 146363bba6688a593a2c5649a9844f29df3396db Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Wed, 22 Jul 2015 11:17:35 +0200 Subject: [PATCH 010/198] :green_heart: tests for copying only selected text unless cursor is at the beginning of the line --- spec/text-editor-spec.coffee | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 8a7af3aa7..a657840b2 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -2994,6 +2994,31 @@ describe "TextEditor", -> sort items """ + describe ".copyOnlySelectedText()", -> + it "copies selected text onto the clipboard", -> + editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]], [[2, 8], [2, 13]]]) + + editor.copyOnlySelectedText() + expect(buffer.lineForRow(0)).toBe "var quicksort = function () {" + expect(buffer.lineForRow(1)).toBe " var sort = function(items) {" + expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;" + expect(clipboard.readText()).toBe 'quicksort\nsort\nitems' + expect(atom.clipboard.read()).toEqual """ + quicksort + sort + items + """ + + describe "when no text is selected", -> + it "copies the line on which the cursor is at the begginning of the line", -> + editor.setCursorBufferPosition([1, 0]) + editor.copyOnlySelectedText() + expect(atom.clipboard.read()).toEqual " var sort = function(items) {\n" + + it "does not copy anything if the cursor is not at the beginning of the line", -> + editor.setCursorBufferPosition([1, 5]) + editor.copyOnlySelectedText() + expect(atom.clipboard.read()).toEqual "initial clipboard content" describe ".pasteText()", -> copyText = (text, {startColumn, textEditor}={}) -> From 2655513673762e292248e1d8ad2f5bc683a2942a Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Mon, 27 Jul 2015 13:02:39 +0200 Subject: [PATCH 011/198] CR: copyOnlySelectedText only works on selected text, no matter of cursor position --- spec/text-editor-spec.coffee | 7 +------ src/text-editor.coffee | 2 -- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index a657840b2..fa5d1c423 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3010,12 +3010,7 @@ describe "TextEditor", -> """ describe "when no text is selected", -> - it "copies the line on which the cursor is at the begginning of the line", -> - editor.setCursorBufferPosition([1, 0]) - editor.copyOnlySelectedText() - expect(atom.clipboard.read()).toEqual " var sort = function(items) {\n" - - it "does not copy anything if the cursor is not at the beginning of the line", -> + it "does not copy anything", -> editor.setCursorBufferPosition([1, 5]) editor.copyOnlySelectedText() expect(atom.clipboard.read()).toEqual "initial clipboard content" diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 220e99f2b..d1e4913ed 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2594,8 +2594,6 @@ class TextEditor extends Model for selection in @getSelectionsOrderedByBufferPosition() if not selection.isEmpty() selection.copy(maintainClipboard, true) - else if selection.isEmpty() and selection.cursor.isAtBeginningOfLine() - @copySelectedText() maintainClipboard = true return From 0441fa1f10a35b582914505bb653cc56975e5233 Mon Sep 17 00:00:00 2001 From: Hernawan Fa'iz Abdillah Date: Sat, 1 Aug 2015 10:11:34 +0700 Subject: [PATCH 012/198] Add tabType config item --- src/config-schema.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index d46cdf589..680886ece 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -143,6 +143,10 @@ module.exports = softTabs: type: 'boolean' default: true + tabType: + type: 'string' + default: 'auto' + enum: ['auto', 'soft', 'hard'] softWrapAtPreferredLineLength: type: 'boolean' default: false From 8eff9d493dddd3bf8f7bfe4e7ef05a5366bf03f0 Mon Sep 17 00:00:00 2001 From: Hernawan Fa'iz Abdillah Date: Fri, 7 Aug 2015 09:11:03 +0700 Subject: [PATCH 013/198] Add description to tabType config --- src/config-schema.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 680886ece..52209aef5 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -147,6 +147,7 @@ module.exports = type: 'string' default: 'auto' enum: ['auto', 'soft', 'hard'] + description: 'Determine character inserted during Tab keypress.' softWrapAtPreferredLineLength: type: 'boolean' default: false From d9d442b723278e128990db14324f7aa2f04d7960 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 16:30:14 -0700 Subject: [PATCH 014/198] Consolidate tab specs --- spec/text-editor-spec.coffee | 65 ++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 5175f91c4..256d1cde0 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3694,6 +3694,38 @@ describe "TextEditor", -> atom.workspace.open(null, softTabs: false).then (editor) -> expect(editor.getSoftTabs()).toBeFalsy() + afterEach -> + atom.packages.deactivatePackages() + atom.packages.unloadPackages() + + it "resets the tab style when tokenization is complete", -> + editor.destroy() + + waitsForPromise -> + atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o + + runs -> + expect(editor.softTabs).toBe true + + waitsForPromise -> + atom.packages.activatePackage('language-coffee-script') + + runs -> + expect(editor.softTabs).toBe false + + it "uses hard tabs in Makefile files", -> + # FIXME remove once this is handled by a scoped setting in the + # language-make package + + waitsForPromise -> + atom.packages.activatePackage('language-make') + + waitsForPromise -> + atom.project.open('Makefile').then (o) -> editor = o + + runs -> + expect(editor.softTabs).toBe false + describe '.getTabLength()', -> describe 'when scoped settings are used', -> coffeeEditor = null @@ -3923,39 +3955,6 @@ describe "TextEditor", -> coffeeEditor.insertText("\n") expect(coffeeEditor.lineTextForBufferRow(2)).toBe "" - describe "soft and hard tabs", -> - afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() - - it "resets the tab style when tokenization is complete", -> - editor.destroy() - - waitsForPromise -> - atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o - - runs -> - expect(editor.softTabs).toBe true - - waitsForPromise -> - atom.packages.activatePackage('language-coffee-script') - - runs -> - expect(editor.softTabs).toBe false - - it "uses hard tabs in Makefile files", -> - # FIXME remove once this is handled by a scoped setting in the - # language-make package - - waitsForPromise -> - atom.packages.activatePackage('language-make') - - waitsForPromise -> - atom.project.open('Makefile').then (o) -> editor = o - - runs -> - expect(editor.softTabs).toBe false - describe ".destroy()", -> it "destroys all markers associated with the edit session", -> editor.foldAll() From 71f6cd0a0fa84a126e6402c25868386d5e09a260 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 16:43:53 -0700 Subject: [PATCH 015/198] Get some base tests setup --- spec/text-editor-spec.coffee | 87 ++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 256d1cde0..81f75d96e 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -2246,7 +2246,6 @@ describe "TextEditor", -> expect(editor.indentationForBufferRow(1)).toBe 1 expect(editor.indentationForBufferRow(2)).toBe 0 - describe ".backspace()", -> describe "when there is a single cursor", -> changeScreenRangeHandler = null @@ -3676,55 +3675,67 @@ describe "TextEditor", -> expect(editor.lineTextForBufferRow(0)).toBe 'abC' expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 2]] - describe "soft-tabs detection", -> - it "assigns soft / hard tabs based on the contents of the buffer, or uses the default if unknown", -> - waitsForPromise -> - atom.workspace.open('sample.js', softTabs: false).then (editor) -> - expect(editor.getSoftTabs()).toBeTruthy() - - waitsForPromise -> - atom.workspace.open('sample-with-tabs.coffee', softTabs: true).then (editor) -> - expect(editor.getSoftTabs()).toBeFalsy() - - waitsForPromise -> - atom.workspace.open('sample-with-tabs-and-initial-comment.js', softTabs: true).then (editor) -> - expect(editor.getSoftTabs()).toBeFalsy() - - waitsForPromise -> - atom.workspace.open(null, softTabs: false).then (editor) -> - expect(editor.getSoftTabs()).toBeFalsy() - + describe "soft and hard tabs", -> afterEach -> atom.packages.deactivatePackages() atom.packages.unloadPackages() - it "resets the tab style when tokenization is complete", -> - editor.destroy() + describe "when editor.tabType is 'auto'", -> + beforeEach -> + atom.config.set('editor.tabType', 'auto') - waitsForPromise -> - atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o + it "auto-detects soft / hard tabs based on the contents of the buffer, or uses the default if unknown, and setSoftTabs() overrides", -> + waitsForPromise -> + atom.workspace.open('sample.js', softTabs: false).then (editor) -> + expect(editor.getSoftTabs()).toBe true + editor.setSoftTabs(false) + expect(editor.getSoftTabs()).toBe false - runs -> - expect(editor.softTabs).toBe true + waitsForPromise -> + atom.workspace.open('sample-with-tabs.coffee', softTabs: true).then (editor) -> + expect(editor.getSoftTabs()).toBe false + editor.setSoftTabs(true) + expect(editor.getSoftTabs()).toBe true - waitsForPromise -> - atom.packages.activatePackage('language-coffee-script') + waitsForPromise -> + atom.workspace.open('sample-with-tabs-and-initial-comment.js', softTabs: true).then (editor) -> + expect(editor.getSoftTabs()).toBe false + editor.setSoftTabs(true) + expect(editor.getSoftTabs()).toBe true - runs -> - expect(editor.softTabs).toBe false + waitsForPromise -> + atom.workspace.open(null, softTabs: false).then (editor) -> + expect(editor.getSoftTabs()).toBe false + editor.setSoftTabs(true) + expect(editor.getSoftTabs()).toBe true - it "uses hard tabs in Makefile files", -> - # FIXME remove once this is handled by a scoped setting in the - # language-make package + it "resets the tab style when tokenization is complete", -> + editor.destroy() - waitsForPromise -> - atom.packages.activatePackage('language-make') + waitsForPromise -> + atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o - waitsForPromise -> - atom.project.open('Makefile').then (o) -> editor = o + runs -> + expect(editor.softTabs).toBe true - runs -> - expect(editor.softTabs).toBe false + waitsForPromise -> + atom.packages.activatePackage('language-coffee-script') + + runs -> + expect(editor.softTabs).toBe false + + it "uses hard tabs in Makefile files", -> + # FIXME remove once this is handled by a scoped setting in the + # language-make package + + waitsForPromise -> + atom.packages.activatePackage('language-make') + + waitsForPromise -> + atom.project.open('Makefile').then (o) -> editor = o + + runs -> + expect(editor.softTabs).toBe false describe '.getTabLength()', -> describe 'when scoped settings are used', -> From 5f620fb73919a5ac94b2f401e03e7ca42c19e02e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Aug 2015 16:52:25 -0700 Subject: [PATCH 016/198] Config::getSchema - use null to represent inaccessible key path --- spec/config-spec.coffee | 4 ++-- src/config.coffee | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 1821b7e01..3b7bd6061 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -1142,8 +1142,8 @@ describe "Config", -> type: 'integer' default: 12 - expect(atom.config.getSchema('foo.baz')).toBe(null) - expect(atom.config.getSchema('foo.bar.anInt.baz')).toBe(false) + expect(atom.config.getSchema('foo.baz')).toEqual {type: 'any'} + expect(atom.config.getSchema('foo.bar.anInt.baz')).toBe(null) it "respects the schema for scoped settings", -> schema = diff --git a/src/config.coffee b/src/config.coffee index f26599a0b..7c6dc8286 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -664,8 +664,8 @@ class Config # * `keyPath` The {String} name of the key. # # Returns an {Object} eg. `{type: 'integer', default: 23, minimum: 1}`. - # Returns `null` when the keyPath has no schema specified. - # Returns `false` when the key-path is not accessible from the root schema. + # Returns `null` when the keyPath has no schema specified, but is accessible + # from the root schema. getSchema: (keyPath) -> keys = splitKeyPath(keyPath) schema = @schema @@ -676,11 +676,11 @@ class Config if isPlainObject(schema.additionalProperties) childSchema = schema.additionalProperties else if schema.additionalProperties is false - return false - else return null + else + return {type: 'any'} else - return false + return null schema = childSchema schema @@ -959,10 +959,9 @@ class Config catch e undefined else - if (schema = @getSchema(keyPath))? + unless (schema = @getSchema(keyPath))? throw new Error("Illegal key path #{keyPath}") if schema is false - value = @constructor.executeSchemaEnforcers(keyPath, value, schema) - value + @constructor.executeSchemaEnforcers(keyPath, value, schema) # When the schema is changed / added, there may be values set in the config # that do not conform to the schema. This will reset make them conform. @@ -1040,6 +1039,10 @@ class Config # order of specification. Then the `*` enforcers will be run, in order of # specification. Config.addSchemaEnforcers + 'any': + coerce: (keyPath, value, schema) -> + value + 'integer': coerce: (keyPath, value, schema) -> value = parseInt(value) From b3ea638de3b16b402b4e984501bae00e405c4a3f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 17:10:43 -0700 Subject: [PATCH 017/198] Add a bunch of specs for tabType setting --- spec/text-editor-spec.coffee | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 81f75d96e..6a46992ff 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3737,6 +3737,79 @@ describe "TextEditor", -> runs -> expect(editor.softTabs).toBe false + describe "when editor.tabType is 'hard'", -> + beforeEach -> + atom.config.set('editor.tabType', 'hard') + + it "always chooses hard tabs and setSoftTabs() overrides the setting", -> + waitsForPromise -> + atom.workspace.open('sample.js').then (editor) -> + expect(editor.getSoftTabs()).toBe false + editor.setSoftTabs(true) + expect(editor.getSoftTabs()).toBe true + + waitsForPromise -> + atom.workspace.open('sample-with-tabs.coffee').then (editor) -> + expect(editor.getSoftTabs()).toBe false + editor.setSoftTabs(true) + expect(editor.getSoftTabs()).toBe true + + waitsForPromise -> + atom.workspace.open('sample-with-tabs-and-initial-comment.js').then (editor) -> + expect(editor.getSoftTabs()).toBe false + editor.setSoftTabs(true) + expect(editor.getSoftTabs()).toBe true + + waitsForPromise -> + atom.workspace.open(null).then (editor) -> + expect(editor.getSoftTabs()).toBe false + editor.setSoftTabs(true) + expect(editor.getSoftTabs()).toBe true + + describe "when editor.tabType is 'soft'", -> + beforeEach -> + atom.config.set('editor.tabType', 'soft') + + it "always chooses soft tabs and setSoftTabs() overrides the setting", -> + waitsForPromise -> + atom.workspace.open('sample.js').then (editor) -> + expect(editor.getSoftTabs()).toBe true + editor.setSoftTabs(false) + expect(editor.getSoftTabs()).toBe false + + waitsForPromise -> + atom.workspace.open('sample-with-tabs.coffee').then (editor) -> + expect(editor.getSoftTabs()).toBe true + editor.setSoftTabs(false) + expect(editor.getSoftTabs()).toBe false + + waitsForPromise -> + atom.workspace.open('sample-with-tabs-and-initial-comment.js').then (editor) -> + expect(editor.getSoftTabs()).toBe true + editor.setSoftTabs(false) + expect(editor.getSoftTabs()).toBe false + + waitsForPromise -> + atom.workspace.open(null).then (editor) -> + expect(editor.getSoftTabs()).toBe true + editor.setSoftTabs(false) + expect(editor.getSoftTabs()).toBe false + + it "keeps the tabType when tokenization is complete", -> + editor.destroy() + + waitsForPromise -> + atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o + + runs -> + expect(editor.softTabs).toBe true + + waitsForPromise -> + atom.packages.activatePackage('language-coffee-script') + + runs -> + expect(editor.softTabs).toBe true + describe '.getTabLength()', -> describe 'when scoped settings are used', -> coffeeEditor = null From 1544c117a22c40d17b0032a503186bb58eb82270 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 17:11:07 -0700 Subject: [PATCH 018/198] Add logic that uses the editor.tabType setting --- src/text-editor.coffee | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 07fe26588..293ed2b70 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -75,7 +75,7 @@ class TextEditor extends Model 'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows', toProperty: 'languageMode' - constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) -> + constructor: ({softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) -> super @emitter = new Emitter @@ -86,7 +86,7 @@ class TextEditor extends Model buffer ?= new TextBuffer @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode}) @buffer = @displayBuffer.buffer - @softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true + @softTabs = @shouldUseSoftTabs(defaultValue: softTabs) for marker in @findMarkers(@getSelectionMarkerAttributes()) marker.setProperties(preserveFolds: true) @@ -2395,6 +2395,20 @@ class TextEditor extends Model return unless @getSoftTabs() @scanInBufferRange /\t/g, bufferRange, ({replace}) => replace(@getTabText()) + # Private: Computes whether or not this editor should use softTabs based on + # the `editor.tabType` setting. + # + # Returns a {Boolean} + shouldUseSoftTabs: ({defaultValue}) -> + tabType = atom.config.get('editor.tabType', scope: @getRootScopeDescriptor()) + switch tabType + when 'auto' + @usesSoftTabs() ? defaultValue ? atom.config.get('editor.softTabs') ? true + when 'hard' + false + when 'soft' + true + ### Section: Soft Wrap Behavior ### @@ -2875,7 +2889,7 @@ class TextEditor extends Model ### handleTokenization: -> - @softTabs = @usesSoftTabs() ? @softTabs + @softTabs = @shouldUseSoftTabs(defaultValue: @softTabs) handleGrammarChange: -> @unfoldAll() From 119200847923492660f23fffdbd93d4af5303aba Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 17:21:53 -0700 Subject: [PATCH 019/198] Update the tab type when the setting changes --- spec/text-editor-spec.coffee | 21 +++++++++++++++++++++ src/text-editor.coffee | 6 ++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 6a46992ff..1d9a4b3fb 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3810,6 +3810,27 @@ describe "TextEditor", -> runs -> expect(editor.softTabs).toBe true + describe "when editor.tabType changes", -> + beforeEach -> + atom.config.set('editor.tabType', 'auto') + + it "updates based on the value chosen", -> + waitsForPromise -> + atom.workspace.open('sample.js').then (editor) -> + expect(editor.getSoftTabs()).toBe true + atom.config.set('editor.tabType', 'hard') + expect(editor.getSoftTabs()).toBe false + atom.config.set('editor.tabType', 'auto') + expect(editor.getSoftTabs()).toBe true + + waitsForPromise -> + atom.workspace.open('sample-with-tabs.coffee').then (editor) -> + expect(editor.getSoftTabs()).toBe false + atom.config.set('editor.tabType', 'soft') + expect(editor.getSoftTabs()).toBe true + atom.config.set('editor.tabType', 'auto') + expect(editor.getSoftTabs()).toBe false + describe '.getTabLength()', -> describe 'when scoped settings are used', -> coffeeEditor = null diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 293ed2b70..8d0d9b2d6 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -75,7 +75,7 @@ class TextEditor extends Model 'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows', toProperty: 'languageMode' - constructor: ({softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) -> + constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) -> super @emitter = new Emitter @@ -86,7 +86,9 @@ class TextEditor extends Model buffer ?= new TextBuffer @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode}) @buffer = @displayBuffer.buffer - @softTabs = @shouldUseSoftTabs(defaultValue: softTabs) + + @disposables.add atom.config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), => + @softTabs = @shouldUseSoftTabs(defaultValue: @softTabs) for marker in @findMarkers(@getSelectionMarkerAttributes()) marker.setProperties(preserveFolds: true) From b5d253b9c58cdace6acc2392ca18ead032233dfe Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 17:23:15 -0700 Subject: [PATCH 020/198] Add a spec for the scoped setting --- spec/text-editor-spec.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 1d9a4b3fb..8e483c34f 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3822,6 +3822,8 @@ describe "TextEditor", -> expect(editor.getSoftTabs()).toBe false atom.config.set('editor.tabType', 'auto') expect(editor.getSoftTabs()).toBe true + atom.config.set('editor.tabType', 'hard', scopeSelector: '.source.js') + expect(editor.getSoftTabs()).toBe false waitsForPromise -> atom.workspace.open('sample-with-tabs.coffee').then (editor) -> From 62aaadbfb681d3c8ea7a2ca6a88c7f2df88aaa92 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Aug 2015 17:26:44 -0700 Subject: [PATCH 021/198] :arrow_up: settings-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac6a00a20..825f335a7 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "open-on-github": "0.37.0", "package-generator": "0.40.0", "release-notes": "0.53.0", - "settings-view": "0.211.2", + "settings-view": "0.213.1", "snippets": "0.95.0", "spell-check": "0.59.0", "status-bar": "0.75.1", From 1b727c1862add54d016e59a98d5fc5bf164e2436 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 17:32:05 -0700 Subject: [PATCH 022/198] Handle a grammar change for @softTabs --- spec/text-editor-spec.coffee | 17 +++++++++++++++++ src/text-editor.coffee | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 8e483c34f..dbc1bb4d4 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3833,6 +3833,23 @@ describe "TextEditor", -> atom.config.set('editor.tabType', 'auto') expect(editor.getSoftTabs()).toBe false + describe "when the grammar changes", -> + coffeeEditor = null + beforeEach -> + atom.config.set('editor.tabType', 'hard', scopeSelector: '.source.js') + atom.config.set('editor.tabType', 'soft', scopeSelector: '.source.coffee') + + waitsForPromise -> + Promise.all [ + atom.packages.activatePackage('language-coffee-script') + atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o + ] + + it "updates based on the value chosen", -> + expect(editor.getSoftTabs()).toBe false + editor.setGrammar(coffeeEditor.getGrammar()) + expect(editor.getSoftTabs()).toBe true + describe '.getTabLength()', -> describe 'when scoped settings are used', -> coffeeEditor = null diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8d0d9b2d6..9e2516019 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -94,6 +94,7 @@ class TextEditor extends Model marker.setProperties(preserveFolds: true) @addSelection(marker) + @subscribeToTabTypeConfig() @subscribeToBuffer() @subscribeToDisplayBuffer() @@ -178,9 +179,15 @@ class TextEditor extends Model @subscribe @displayBuffer.onDidAddDecoration (decoration) => @emit 'decoration-added', decoration @subscribe @displayBuffer.onDidRemoveDecoration (decoration) => @emit 'decoration-removed', decoration + subscribeToTabTypeConfig: -> + @tabTypeSubscription?.dispose() + @tabTypeSubscription = atom.config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), => + @softTabs = @shouldUseSoftTabs(defaultValue: @softTabs) + destroyed: -> @unsubscribe() if includeDeprecatedAPIs @disposables.dispose() + @tabTypeSubscription.dispose() selection.destroy() for selection in @getSelections() @buffer.release() @displayBuffer.destroy() @@ -2895,6 +2902,7 @@ class TextEditor extends Model handleGrammarChange: -> @unfoldAll() + @subscribeToTabTypeConfig() @emitter.emit 'did-change-grammar', @getGrammar() handleMarkerCreated: (marker) => From 4598e000f42f996e7cb98cc65af6fb91c81a5bd0 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 17:33:02 -0700 Subject: [PATCH 023/198] Remove duplicated bit --- src/text-editor.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 9e2516019..ee1b5df59 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -87,9 +87,6 @@ class TextEditor extends Model @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode}) @buffer = @displayBuffer.buffer - @disposables.add atom.config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), => - @softTabs = @shouldUseSoftTabs(defaultValue: @softTabs) - for marker in @findMarkers(@getSelectionMarkerAttributes()) marker.setProperties(preserveFolds: true) @addSelection(marker) From 51538b0c67fd69cd88fe8914b1eca4772ec58c84 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 12 Aug 2015 17:48:52 -0700 Subject: [PATCH 024/198] =?UTF-8?q?Don=E2=80=99t=20need=20this=20coffeeEdi?= =?UTF-8?q?tor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/text-editor-spec.coffee | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index dbc1bb4d4..e6ce94e3f 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3840,14 +3840,11 @@ describe "TextEditor", -> atom.config.set('editor.tabType', 'soft', scopeSelector: '.source.coffee') waitsForPromise -> - Promise.all [ - atom.packages.activatePackage('language-coffee-script') - atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o - ] + atom.packages.activatePackage('language-coffee-script') it "updates based on the value chosen", -> expect(editor.getSoftTabs()).toBe false - editor.setGrammar(coffeeEditor.getGrammar()) + editor.setGrammar(atom.grammars.grammarForScopeName('source.coffee')) expect(editor.getSoftTabs()).toBe true describe '.getTabLength()', -> From 2493da0a8411f7b954fdbaffb1f919488e2a681b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Aug 2015 17:55:19 -0700 Subject: [PATCH 025/198] Rename fileTypesByScope -> customFileTypes --- spec/grammars-spec.coffee | 6 +++--- src/config-schema.coffee | 2 +- src/grammar-registry.coffee | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee index e2f8df568..960ce7d52 100644 --- a/spec/grammars-spec.coffee +++ b/spec/grammars-spec.coffee @@ -140,11 +140,11 @@ describe "the `grammars` global", -> describe "when the user has custom grammar file types", -> it "considers the custom file types as well as those defined in the grammar", -> - atom.config.set('core.fileTypesByScope', 'source.ruby': ['Cheffile']) + atom.config.set('core.customFileTypes', 'source.ruby': ['Cheffile']) expect(atom.grammars.selectGrammar('build/Cheffile', 'cookbook "postgres"').scopeName).toBe 'source.ruby' it "favors user-defined file types over built-in ones of equal length", -> - atom.config.set('core.fileTypesByScope', + atom.config.set('core.customFileTypes', 'source.coffee': ['Rakefile'], 'source.ruby': ['Cakefile'] ) @@ -152,7 +152,7 @@ describe "the `grammars` global", -> expect(atom.grammars.selectGrammar('Cakefile', '').scopeName).toBe 'source.ruby' it "favors grammars with matching first-line-regexps even if custom file types match the file", -> - atom.config.set('core.fileTypesByScope', 'source.ruby': ['bootstrap']) + atom.config.set('core.customFileTypes', 'source.ruby': ['bootstrap']) expect(atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node').scopeName).toBe 'source.js' describe ".removeGrammar(grammar)", -> diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 93d0dcdc2..334437618 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -26,7 +26,7 @@ module.exports = default: [] items: type: 'string' - fileTypesByScope: + customFileTypes: type: 'object' default: {} additionalProperties: diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee index c7924843e..033595dad 100644 --- a/src/grammar-registry.coffee +++ b/src/grammar-registry.coffee @@ -71,7 +71,7 @@ class GrammarRegistry extends FirstMate.GrammarRegistry pathScore = -1 fileTypes = grammar.fileTypes - if customFileTypes = atom.config.get('core.fileTypesByScope')?[grammar.scopeName] + if customFileTypes = atom.config.get('core.customFileTypes')?[grammar.scopeName] fileTypes = fileTypes.concat(customFileTypes) for fileType, i in fileTypes From 0203438669e5ef10a3d048d72e2a005ca84189eb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Aug 2015 18:04:52 -0700 Subject: [PATCH 026/198] Add description for customFileTypes in schema --- src/config-schema.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 334437618..27f436e09 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -29,6 +29,7 @@ module.exports = customFileTypes: type: 'object' default: {} + description: 'Associates scope names (e.g. "source.js") with arrays of file extensions and file names (e.g. ["Somefile", ".js2"])' additionalProperties: type: 'array' items: From 4609be791252e14ca7191062724e24a51e463a46 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 13 Aug 2015 10:34:44 -0700 Subject: [PATCH 027/198] Be defensive with grammar in usesSoftTabs() --- src/text-editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index ee1b5df59..c7a0b0c88 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2376,7 +2376,7 @@ class TextEditor extends Model usesSoftTabs: -> # FIXME Remove once this can be specified as a scoped setting in the # language-make package - return false if @getGrammar().scopeName is 'source.makefile' + return false if @getGrammar()?.scopeName is 'source.makefile' for bufferRow in [0..@buffer.getLastRow()] continue if @displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() From 948d86f6fe3373c15e767b7e5f7194757eedfc64 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 13 Aug 2015 10:45:28 -0700 Subject: [PATCH 028/198] :arrow_up: status-bar@0.78 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68d694fcf..279b65793 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "settings-view": "0.213.1", "snippets": "0.95.0", "spell-check": "0.59.0", - "status-bar": "0.77.0", + "status-bar": "0.78.0", "styleguide": "0.44.0", "symbols-view": "0.100.0", "tabs": "0.82.0", From 216e98140d80c92bfc31ca83cb5cec47863b0db1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 13 Aug 2015 12:30:28 -0600 Subject: [PATCH 029/198] Eliminate unused argument --- src/text-editor-component.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 8d3bfc579..a2ed0ff8d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -403,7 +403,7 @@ class TextEditorComponent when 3 @editor.getLastSelection().selectLine() - @handleDragUntilMouseUp event, (screenPosition) => + @handleDragUntilMouseUp (screenPosition) => @editor.selectToScreenPosition(screenPosition, true) onLineNumberGutterMouseDown: (event) => @@ -424,7 +424,7 @@ class TextEditorComponent @editor.setSelectedBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]], preserveFolds: true) - @handleDragUntilMouseUp event, (screenPosition) => + @handleDragUntilMouseUp (screenPosition) => dragRow = screenPosition.row dragBufferRow = @editor.bufferRowForScreenRow(dragRow) if dragBufferRow < clickedBufferRow # dragging up @@ -440,7 +440,7 @@ class TextEditorComponent bufferRange = new Range([clickedBufferRow, 0], [clickedBufferRow + 1, 0]) rowSelection = @editor.addSelectionForBufferRange(bufferRange, preserveFolds: true) - @handleDragUntilMouseUp event, (screenPosition) => + @handleDragUntilMouseUp (screenPosition) => dragRow = screenPosition.row dragBufferRow = @editor.bufferRowForScreenRow(dragRow) @@ -466,7 +466,7 @@ class TextEditorComponent else @editor.selectToBufferPosition([clickedBufferRow + 1, 0]) - @handleDragUntilMouseUp event, (screenPosition) => + @handleDragUntilMouseUp (screenPosition) => dragRow = screenPosition.row dragBufferRow = @editor.bufferRowForScreenRow(dragRow) if dragRow < tailPosition.row # dragging up @@ -523,7 +523,7 @@ class TextEditorComponent onCursorMoved: => @cursorMoved = true - handleDragUntilMouseUp: (event, dragHandler) => + handleDragUntilMouseUp: (dragHandler) => dragging = false lastMousePosition = {} animationLoop = => From a88b648144c179d3baa0a4958b0af3c35bf864d5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 13 Aug 2015 12:53:02 -0600 Subject: [PATCH 030/198] Unify gutter drag logic This ensures the same approach is used to drag the gutter for all modes of selection. --- src/text-editor-component.coffee | 65 ++++++++++---------------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index a2ed0ff8d..a46b352a3 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -419,61 +419,36 @@ class TextEditorComponent @onGutterClick(event) onGutterClick: (event) => - clickedRow = @screenPositionForMouseEvent(event).row - clickedBufferRow = @editor.bufferRowForScreenRow(clickedRow) - + clickedBufferRow = @editor.bufferRowForScreenRow(@screenPositionForMouseEvent(event).row) @editor.setSelectedBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]], preserveFolds: true) - - @handleDragUntilMouseUp (screenPosition) => - dragRow = screenPosition.row - dragBufferRow = @editor.bufferRowForScreenRow(dragRow) - if dragBufferRow < clickedBufferRow # dragging up - @editor.setSelectedBufferRange([[dragBufferRow, 0], [clickedBufferRow + 1, 0]], reversed: true, preserveFolds: true, autoscroll: false) - else - @editor.setSelectedBufferRange([[clickedBufferRow, 0], [dragBufferRow + 1, 0]], reversed: false, preserveFolds: true, autoscroll: false) - @editor.getLastCursor().autoscroll() + @handleGutterDrag(clickedBufferRow) onGutterMetaClick: (event) => - clickedRow = @screenPositionForMouseEvent(event).row - clickedBufferRow = @editor.bufferRowForScreenRow(clickedRow) - - bufferRange = new Range([clickedBufferRow, 0], [clickedBufferRow + 1, 0]) - rowSelection = @editor.addSelectionForBufferRange(bufferRange, preserveFolds: true) - - @handleDragUntilMouseUp (screenPosition) => - dragRow = screenPosition.row - dragBufferRow = @editor.bufferRowForScreenRow(dragRow) - - if dragBufferRow < clickedBufferRow # dragging up - rowSelection.setBufferRange([[dragBufferRow, 0], [clickedBufferRow + 1, 0]], preserveFolds: true) - else - rowSelection.setBufferRange([[clickedBufferRow, 0], [dragBufferRow + 1, 0]], 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) -> - selection.intersectsBufferRange(bufferRange) + clickedBufferRow = @editor.bufferRowForScreenRow(@screenPositionForMouseEvent(event).row) + @editor.addSelectionForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]], preserveFolds: true) + @handleGutterDrag(clickedBufferRow) onGutterShiftClick: (event) => - clickedRow = @screenPositionForMouseEvent(event).row - clickedBufferRow = @editor.bufferRowForScreenRow(clickedRow) - tailPosition = @editor.getLastSelection().getTailScreenPosition() - tailBufferPosition = @editor.bufferPositionForScreenPosition(tailPosition) + clickedBufferRow = @editor.bufferRowForScreenRow(@screenPositionForMouseEvent(event).row) + tailBufferPosition = @editor.getLastSelection().getTailBufferPosition() - if clickedRow < tailPosition.row - @editor.selectToBufferPosition([clickedBufferRow, 0]) + if clickedBufferRow < tailBufferPosition.row + @editor.selectToBufferPosition([clickedBufferRow, 0], true) else - @editor.selectToBufferPosition([clickedBufferRow + 1, 0]) + @editor.selectToBufferPosition([clickedBufferRow + 1, 0], true) + + @handleGutterDrag(tailBufferPosition.row, tailBufferPosition.column) + + handleGutterDrag: (tailRow, tailColumn) -> + tailPosition = [tailRow, tailColumn] if tailColumn? @handleDragUntilMouseUp (screenPosition) => - dragRow = screenPosition.row - dragBufferRow = @editor.bufferRowForScreenRow(dragRow) - if dragRow < tailPosition.row # dragging up - @editor.setSelectedBufferRange([[dragBufferRow, 0], tailBufferPosition], preserveFolds: true) + dragRow = @editor.bufferPositionForScreenPosition(screenPosition).row + if dragRow < tailRow + @editor.getLastSelection().setBufferRange([[dragRow, 0], tailPosition ? [tailRow + 1, 0]], reversed: true, autoscroll: false, preserveFolds: true) else - @editor.setSelectedBufferRange([tailBufferPosition, [dragBufferRow + 1, 0]], preserveFolds: true) - + @editor.getLastSelection().setBufferRange([tailPosition ? [tailRow, 0], [dragRow + 1, 0]], reversed: false, autoscroll: false, preserveFolds: true) + @editor.getLastCursor().autoscroll() onStylesheetsChanged: (styleElement) => return unless @performedInitialMeasurement From c9a89035eaefb179a98beeaca5ade9841ee019a7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 13 Aug 2015 15:14:15 -0700 Subject: [PATCH 031/198] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 279b65793..b45620e87 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "space-pen": "3.8.2", "stacktrace-parser": "0.1.1", "temp": "0.8.1", - "text-buffer": "6.5.2", + "text-buffer": "6.6.1", "theorist": "^1.0.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", From 927ec7aa317ffa87d834b3af95f354194b12a01f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 14 Aug 2015 11:29:07 -0700 Subject: [PATCH 032/198] :arrow_up: symbols-view@0.101.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b45620e87..72bfba208 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "spell-check": "0.59.0", "status-bar": "0.78.0", "styleguide": "0.44.0", - "symbols-view": "0.100.0", + "symbols-view": "0.101.0", "tabs": "0.82.0", "timecop": "0.31.0", "tree-view": "0.183.0", From 31634f3f531bbd6fdbf87eeda38a7c489cc539c1 Mon Sep 17 00:00:00 2001 From: Jonathan Delgado Date: Sat, 15 Aug 2015 10:21:25 -0700 Subject: [PATCH 033/198] Add disableInitialEmptyEditor setting --- src/atom.coffee | 1 + src/config-schema.coffee | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index 7065a0d93..3bba8ffca 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -673,6 +673,7 @@ class Atom extends Model @windowEventHandler?.unsubscribe() openInitialEmptyEditorIfNecessary: -> + return if @config.get('core.disableInitialEmptyEditor') if @getLoadSettings().initialPaths?.length is 0 and @workspace.getPaneItems().length is 0 @workspace.open(null) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index a1bd91cc7..2487f4c37 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -89,6 +89,10 @@ module.exports = 'windows1258', 'windows866' ] + disableInitialEmptyEditor: + description: 'Disable the initial empty editor when atom starts.' + type: 'boolean' + default: false editor: type: 'object' From 33abc6698c57b3dcf2a7884e4fc52535a2f8139b Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Sun, 16 Aug 2015 09:04:33 +0200 Subject: [PATCH 034/198] move maintainClipboard into if block --- src/text-editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index d1e4913ed..e17272801 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2594,7 +2594,7 @@ class TextEditor extends Model for selection in @getSelectionsOrderedByBufferPosition() if not selection.isEmpty() selection.copy(maintainClipboard, true) - maintainClipboard = true + maintainClipboard = true return # Essential: For each selection, cut the selected text. From 96f8c10ac1cc045d63ec1d6b63148fba6544840f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 17 Aug 2015 16:29:29 -0700 Subject: [PATCH 035/198] :arrow_up: apm@1.0.2 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index d1f210f29..b49a76ec8 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.0.1" + "atom-package-manager": "1.0.2" } } From bc4a822161496d36b35dbb948c7d3c3e365f339f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 17 Aug 2015 16:40:43 -0700 Subject: [PATCH 036/198] :arrow_up: language-html@0.41 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 72bfba208..d13a7cae7 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "language-gfm": "0.80.0", "language-git": "0.10.0", "language-go": "0.37.0", - "language-html": "0.40.1", + "language-html": "0.41.0", "language-hyperlink": "0.14.0", "language-java": "0.16.0", "language-javascript": "0.87.1", From 5a418d1961d772d873674307c8fcd0a0a671acb5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 17 Aug 2015 17:38:58 -0700 Subject: [PATCH 037/198] :arrow_up: notifications@0.58 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d13a7cae7..977fa3f22 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "link": "0.30.0", "markdown-preview": "0.150.0", "metrics": "0.51.0", - "notifications": "0.57.0", + "notifications": "0.58.0", "open-on-github": "0.38.0", "package-generator": "0.40.0", "release-notes": "0.53.0", From 6463a8e9f4580cf1e5a5dda13285a05398b1002f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 17 Aug 2015 17:40:06 -0700 Subject: [PATCH 038/198] :arrow_up: settings-view@0.214 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 977fa3f22..855033db9 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "open-on-github": "0.38.0", "package-generator": "0.40.0", "release-notes": "0.53.0", - "settings-view": "0.213.1", + "settings-view": "0.214.0", "snippets": "0.95.0", "spell-check": "0.59.0", "status-bar": "0.78.0", From 05989060bd31b66c1ab1d390edcfd771c68f17a0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 17 Aug 2015 18:19:21 -0700 Subject: [PATCH 039/198] :arrow_up: first-mate --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 855033db9..d529fdf0d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "delegato": "^1", "emissary": "^1.3.3", "event-kit": "^1.2.0", - "first-mate": "^4.2", + "first-mate": "^5.0.0", "fs-plus": "^2.8.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", From ad91d8c6e7d49af5e4b6bea832ee13ebe8a589a1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 17 Aug 2015 18:33:20 -0700 Subject: [PATCH 040/198] :arrow_up: language-make@0.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d529fdf0d..01e82f48c 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "language-javascript": "0.87.1", "language-json": "0.16.0", "language-less": "0.28.2", - "language-make": "0.16.0", + "language-make": "0.17.0", "language-mustache": "0.12.0", "language-objective-c": "0.15.0", "language-perl": "0.28.0", From f88c0648922d69da9ca727ceac27853e33b5a120 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 18 Aug 2015 10:58:27 +0900 Subject: [PATCH 041/198] :arrow_up: base16-tomorrow-dark-theme@v0.27.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 01e82f48c..207627305 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "atom-dark-ui": "0.50.0", "atom-light-syntax": "0.28.0", "atom-light-ui": "0.43.0", - "base16-tomorrow-dark-theme": "0.26.0", + "base16-tomorrow-dark-theme": "0.27.0", "base16-tomorrow-light-theme": "0.9.0", "one-dark-ui": "1.0.3", "one-dark-syntax": "1.1.0", From 064d49a717725d330974d4e13c2cc9aa944b9d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EF=A3=BF=20Arana=20Jhonny?= Date: Mon, 17 Aug 2015 22:15:57 -0700 Subject: [PATCH 042/198] Change YUM package manager for DNF DNF is now the default package manager in Fedora --- 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 13b03a9bf..fd53b9612 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -24,7 +24,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. ### Fedora / CentOS / RHEL -* `sudo yum --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools` +* `sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools` * Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora). ### Arch From fdb66fd4dc3074e59bcee12779e8f31204598dda Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Tue, 18 Aug 2015 08:10:31 +0200 Subject: [PATCH 043/198] change from essential to private, update comment --- src/text-editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index e17272801..8304b72f2 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2588,7 +2588,7 @@ class TextEditor extends Model return - # Essential: For each selection, only copy hightlighted text. Line is coptied if no selected text and cursor is at the beginning of the line + # Private: For each selection, only copy highlighted text. copyOnlySelectedText: -> maintainClipboard = false for selection in @getSelectionsOrderedByBufferPosition() From 1a965edd0023543247e7a9d0c0391daed1d35355 Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Tue, 18 Aug 2015 17:06:59 +0200 Subject: [PATCH 044/198] :tulip: spec more specific --- spec/text-editor-spec.coffee | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index fa5d1c423..3ecb24ff9 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -2995,19 +2995,20 @@ describe "TextEditor", -> items """ describe ".copyOnlySelectedText()", -> - it "copies selected text onto the clipboard", -> - editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]], [[2, 8], [2, 13]]]) + describe "when thee are multiple selections", -> + it "copies selected text onto the clipboard", -> + editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]], [[2, 8], [2, 13]]]) - editor.copyOnlySelectedText() - expect(buffer.lineForRow(0)).toBe "var quicksort = function () {" - expect(buffer.lineForRow(1)).toBe " var sort = function(items) {" - expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;" - expect(clipboard.readText()).toBe 'quicksort\nsort\nitems' - expect(atom.clipboard.read()).toEqual """ - quicksort - sort - items - """ + editor.copyOnlySelectedText() + expect(buffer.lineForRow(0)).toBe "var quicksort = function () {" + expect(buffer.lineForRow(1)).toBe " var sort = function(items) {" + expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;" + expect(clipboard.readText()).toBe 'quicksort\nsort\nitems' + expect(atom.clipboard.read()).toEqual """ + quicksort + sort + items + """ describe "when no text is selected", -> it "does not copy anything", -> From e96d6f50adf0defcf00154b1917ab959ced23909 Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Tue, 18 Aug 2015 18:20:08 +0200 Subject: [PATCH 045/198] :art: newlines fix --- spec/text-editor-spec.coffee | 1 + src/text-editor.coffee | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 3ecb24ff9..cddc10691 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -2994,6 +2994,7 @@ describe "TextEditor", -> sort items """ + describe ".copyOnlySelectedText()", -> describe "when thee are multiple selections", -> it "copies selected text onto the clipboard", -> diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8304b72f2..1a3abcfe0 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2587,7 +2587,6 @@ class TextEditor extends Model maintainClipboard = true return - # Private: For each selection, only copy highlighted text. copyOnlySelectedText: -> maintainClipboard = false From 5612071cf304cdd18b0a41afc3149d3b85adc78d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Aug 2015 11:01:44 -0700 Subject: [PATCH 046/198] Add line-ending-selector to packageDependencies --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 207627305..88853e79c 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "image-view": "0.54.0", "incompatible-packages": "0.24.1", "keybinding-resolver": "0.33.0", + "line-ending-selector": "0.0.3", "link": "0.30.0", "markdown-preview": "0.150.0", "metrics": "0.51.0", From 22ca33a3b1edc6c1a9d1df34d64e52794dd1b537 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 18 Aug 2015 21:38:00 +0200 Subject: [PATCH 047/198] Extend ViewRegistry with support for objects with an element property By adding this extension to ViewRegistry::getView we're paving the way for Etch-like view frameworks which promotes the usage of plain objects and classes with an element property which is an instance of HTMLElement. --- spec/view-registry-spec.coffee | 8 ++++++++ src/view-registry.coffee | 2 ++ 2 files changed, 10 insertions(+) diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee index a12d46dde..fa3e5ba1f 100644 --- a/spec/view-registry-spec.coffee +++ b/spec/view-registry-spec.coffee @@ -26,6 +26,14 @@ describe "ViewRegistry", -> expect(node.textContent).toBe "Hello" expect(node.spacePenView).toBe view + describe "when passed an object with an element property", -> + it "returns the element property if it's an instance of HTMLElement", -> + class TestComponent + constructor: -> @element = document.createElement('div') + + component = new TestComponent + expect(registry.getView(component)).toBe component.element + describe "when passed a model object", -> describe "when a view provider is registered matching the object's constructor", -> it "constructs a view element and assigns the model on it", -> diff --git a/src/view-registry.coffee b/src/view-registry.coffee index ca9b7cdb7..0c533e9a8 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -138,6 +138,8 @@ class ViewRegistry createView: (object) -> if object instanceof HTMLElement object + else if object?.element instanceof HTMLElement + object.element else if object?.jquery object[0] else if provider = @findProvider(object) From 7adcbc60a360079cf30a8136d3a60468a491b5fe Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Aug 2015 14:31:03 -0700 Subject: [PATCH 048/198] Compile babel files in place during compile task --- build/Gruntfile.coffee | 19 ++++++++++++++++++- build/lib/uses-babel.coffee | 18 ++++++++++++++++++ build/package.json | 4 +++- 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 build/lib/uses-babel.coffee diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index abeb13f14..d74c0b50e 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -1,6 +1,8 @@ fs = require 'fs' path = require 'path' os = require 'os' +glob = require 'glob' +usesBabel = require './lib/uses-babel' # Add support for obselete APIs of vm module so we can make some third-party # modules work under node v0.11.x. @@ -15,6 +17,7 @@ packageJson = require '../package.json' _.extend(global, require('harmony-collections')) unless global.WeakMap? module.exports = (grunt) -> + grunt.loadNpmTasks('grunt-babel') grunt.loadNpmTasks('grunt-coffeelint') grunt.loadNpmTasks('grunt-lesslint') grunt.loadNpmTasks('grunt-cson') @@ -77,6 +80,11 @@ module.exports = (grunt) -> dest: appDir ext: '.js' + babelConfig = + options: {} + dist: + files: [] + lessConfig = options: paths: [ @@ -141,6 +149,13 @@ module.exports = (grunt) -> pegConfig.glob_to_multiple.src.push("#{directory}/lib/*.pegjs") + for jsFile in glob.sync("#{directory}/lib/**/*.js") + if usesBabel(jsFile) + babelConfig.dist.files.push({ + src: [jsFile] + dest: jsFile + }) + grunt.initConfig pkg: grunt.file.readJSON('package.json') @@ -148,6 +163,8 @@ module.exports = (grunt) -> docsOutputDir: 'docs/output' + babel: babelConfig + coffee: coffeeConfig less: lessConfig @@ -229,7 +246,7 @@ module.exports = (grunt) -> stderr: false failOnError: false - grunt.registerTask('compile', ['coffee', 'prebuild-less', 'cson', 'peg']) + grunt.registerTask('compile', ['babel', 'coffee', 'prebuild-less', 'cson', 'peg']) grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint']) grunt.registerTask('test', ['shell:kill-atom', 'run-specs']) diff --git a/build/lib/uses-babel.coffee b/build/lib/uses-babel.coffee new file mode 100644 index 000000000..e7b9050a1 --- /dev/null +++ b/build/lib/uses-babel.coffee @@ -0,0 +1,18 @@ +fs = require 'fs' + +BABEL_PREFIXES = [ + "'use babel'" + '"use babel"' + '/** use babel */' +].map(Buffer) + +PREFIX_LENGTH = Math.max(BABEL_PREFIXES.map((prefix) -> prefix.length)...) + +buffer = Buffer(PREFIX_LENGTH) + +module.exports = (filename) -> + file = fs.openSync(filename, 'r') + fs.readSync(file, buffer, 0, PREFIX_LENGTH) + fs.closeSync(file) + BABEL_PREFIXES.some (prefix) -> + prefix.equals(buffer.slice(0, prefix.length)) diff --git a/build/package.json b/build/package.json index cb41bb0a4..07e9fa7ad 100644 --- a/build/package.json +++ b/build/package.json @@ -12,8 +12,9 @@ "formidable": "~1.0.14", "fs-plus": "2.x", "github-releases": "~0.2.0", + "glob": "^5.0.14", "grunt": "~0.4.1", - "grunt-electron-installer": "^0.37.0", + "grunt-babel": "^5.0.1", "grunt-cli": "~0.1.9", "grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git#cfb99aa99811d52687969532bd5a98011ed95bfe", "grunt-contrib-coffee": "~0.12.0", @@ -21,6 +22,7 @@ "grunt-contrib-less": "~0.8.0", "grunt-cson": "0.14.0", "grunt-download-atom-shell": "~0.14.0", + "grunt-electron-installer": "^0.37.0", "grunt-lesslint": "0.17.0", "grunt-peg": "~1.1.0", "grunt-shell": "~0.3.1", From 76f05814d710ad22c27d1d3c4ee08a40d9e29e82 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Aug 2015 14:51:15 -0700 Subject: [PATCH 049/198] convert-tabs-to-spaces --- build/lib/uses-babel.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/lib/uses-babel.coffee b/build/lib/uses-babel.coffee index e7b9050a1..e1a7c14ef 100644 --- a/build/lib/uses-babel.coffee +++ b/build/lib/uses-babel.coffee @@ -1,9 +1,9 @@ fs = require 'fs' BABEL_PREFIXES = [ - "'use babel'" - '"use babel"' - '/** use babel */' + "'use babel'" + '"use babel"' + '/** use babel */' ].map(Buffer) PREFIX_LENGTH = Math.max(BABEL_PREFIXES.map((prefix) -> prefix.length)...) From ca58687c889148d9b2f07f2b4cac7b6e884cca3b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Aug 2015 15:43:38 -0700 Subject: [PATCH 050/198] Write babel-compiled JS files to app dir --- build/Gruntfile.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index d74c0b50e..0ff65d506 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -153,7 +153,7 @@ module.exports = (grunt) -> if usesBabel(jsFile) babelConfig.dist.files.push({ src: [jsFile] - dest: jsFile + dest: path.join(appDir, jsFile) }) grunt.initConfig From e4b1d512cd01175583a615a8493b0ee75ec8f73e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Aug 2015 15:49:34 -0700 Subject: [PATCH 051/198] Honor /** use babel */ in addition to 'use babel' --- spec/babel-spec.coffee | 6 ++++++ spec/fixtures/babel/babel-comment.js | 3 +++ src/babel.coffee | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/babel/babel-comment.js diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index 4aec0ef8d..1f71faa93 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -33,6 +33,12 @@ describe "Babel transpiler support", -> 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-comment.js') + expect(transpiled(3)).toBe 4 + expect(grim.getDeprecationsLength()).toBe 0 + describe "when a .js file starts with 'use babel';", -> it "transpiles it using babel", -> transpiled = require('./fixtures/babel/babel-single-quotes.js') diff --git a/spec/fixtures/babel/babel-comment.js b/spec/fixtures/babel/babel-comment.js new file mode 100644 index 000000000..4fb454999 --- /dev/null +++ b/spec/fixtures/babel/babel-comment.js @@ -0,0 +1,3 @@ +/** use babel */ + +module.exports = v => v + 1 diff --git a/src/babel.coffee b/src/babel.coffee index 201210817..0adc19db4 100644 --- a/src/babel.coffee +++ b/src/babel.coffee @@ -142,7 +142,7 @@ transpile = (sourceCode, filePath, cachePath) -> # either generated on the fly or pulled from cache. loadFile = (module, filePath) -> sourceCode = fs.readFileSync(filePath, 'utf8') - if sourceCode.startsWith('"use babel"') or sourceCode.startsWith("'use babel'") + if sourceCode.startsWith('"use babel"') or sourceCode.startsWith("'use babel'") or sourceCode.startsWith('/** use babel */') # Continue. else if sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'") # Create a manual deprecation since the stack is too deep to use Grim From bea45b5921a3dbd06f083a972b327237d04b9312 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Aug 2015 16:17:27 -0700 Subject: [PATCH 052/198] Don't use Buffer::equals Node 0.10 doesn't have it --- build/lib/uses-babel.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/lib/uses-babel.coffee b/build/lib/uses-babel.coffee index e1a7c14ef..08f8721d2 100644 --- a/build/lib/uses-babel.coffee +++ b/build/lib/uses-babel.coffee @@ -4,7 +4,7 @@ BABEL_PREFIXES = [ "'use babel'" '"use babel"' '/** use babel */' -].map(Buffer) +] PREFIX_LENGTH = Math.max(BABEL_PREFIXES.map((prefix) -> prefix.length)...) @@ -15,4 +15,4 @@ module.exports = (filename) -> fs.readSync(file, buffer, 0, PREFIX_LENGTH) fs.closeSync(file) BABEL_PREFIXES.some (prefix) -> - prefix.equals(buffer.slice(0, prefix.length)) + prefix is buffer.toString('utf8', 0, prefix.length) From a25a8c83e46a9c9c1f741283eb961483ce83577f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Aug 2015 17:50:49 -0700 Subject: [PATCH 053/198] Use same babel options for compiling bundled packages --- build/Gruntfile.coffee | 3 ++- src/babel.coffee | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 0ff65d506..4329c9cd9 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -3,6 +3,7 @@ path = require 'path' os = require 'os' glob = require 'glob' usesBabel = require './lib/uses-babel' +babelOptions = require('../src/babel').defaultOptions # Add support for obselete APIs of vm module so we can make some third-party # modules work under node v0.11.x. @@ -81,7 +82,7 @@ module.exports = (grunt) -> ext: '.js' babelConfig = - options: {} + options: babelOptions dist: files: [] diff --git a/src/babel.coffee b/src/babel.coffee index 0adc19db4..afa48eb1c 100644 --- a/src/babel.coffee +++ b/src/babel.coffee @@ -188,6 +188,7 @@ module.exports = setCacheDirectory: setCacheDirectory getCacheMisses: -> stats.misses getCacheHits: -> stats.hits + defaultOptions: defaultOptions # Visible for testing. createBabelVersionAndOptionsDigest: createBabelVersionAndOptionsDigest From ea391d017de83394d45d853c1a2d739332cc75ac Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Aug 2015 11:32:16 +0200 Subject: [PATCH 054/198] :racehorse: Batch cursor updates --- src/text-editor-presenter.coffee | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 825600a70..b79abc267 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -410,10 +410,7 @@ class TextEditorPresenter @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation return - updateCursorState: (cursor, destroyOnly = false) -> - delete @state.content.cursors[cursor.id] - - return if destroyOnly + updateCursorState: (cursor) -> return unless @startRow? and @endRow? and @hasPixelRectRequirements() and @baseCharacterWidth? return unless cursor.isVisible() and @startRow <= cursor.getScreenRow() < @endRow @@ -1365,20 +1362,20 @@ class TextEditorPresenter observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => @shouldUpdateHiddenInputState = true if cursor.isLastCursor() + @shouldUpdateCursorsState = true @pauseCursorBlinking() - @updateCursorState(cursor) @emitDidUpdateState() didChangeVisibilityDisposable = cursor.onDidChangeVisibility => - @updateCursorState(cursor) + @shouldUpdateCursorsState = true didDestroyDisposable = cursor.onDidDestroy => @disposables.remove(didChangePositionDisposable) @disposables.remove(didChangeVisibilityDisposable) @disposables.remove(didDestroyDisposable) @shouldUpdateHiddenInputState = true - @updateCursorState(cursor, true) + @shouldUpdateCursorsState = true @emitDidUpdateState() @@ -1389,8 +1386,9 @@ class TextEditorPresenter didAddCursor: (cursor) -> @observeCursor(cursor) @shouldUpdateHiddenInputState = true + @shouldUpdateCursorsState = true @pauseCursorBlinking() - @updateCursorState(cursor) + @emitDidUpdateState() startBlinkingCursors: -> From 66d0e7c7524200de78c4e7fa4a3ec4dd61bc8041 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 19 Aug 2015 15:44:16 +0200 Subject: [PATCH 055/198] :memo: Attempt to document view resolution algorithm --- src/view-registry.coffee | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/view-registry.coffee b/src/view-registry.coffee index 0c533e9a8..67170d3b8 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -124,6 +124,24 @@ class ViewRegistry # * `object` The object for which you want to retrieve a view. This can be a # pane item, a pane, or the workspace itself. # + # ## View Resolution Algorithm + # + # The view associated with the object is resolved using the following + # heuristic sequence + # + # 1. Is the object an instance of `HTMLElement`? If true, return the object. + # 2. Does the object have a property named `element` with a value which is + # an instance of `HTMLElement`? If true, return the property value. + # 3. Is the object a SpacePen view, which means it has a `jquery` property? + # If true, return the root DOM node (i.e. `object[0]`). + # 4. Has a view provider been registered for the object? If true, use the + # provider to create a view associated with the object, and return the + # view. + # 5. Does the object have a `getViewClass` method? If true, use the method + # to create a view associated with the object, and return the view. + # + # If no associated view is returned by the sequence an error is thrown. + # # Returns a DOM element. getView: (object) -> return unless object? From bb0dc68e4089140b97e5fbbccd60daf843051f28 Mon Sep 17 00:00:00 2001 From: Lee Dohm Date: Wed, 19 Aug 2015 07:48:15 -0700 Subject: [PATCH 056/198] :memo: Add docs for Markdown formatting of config descriptions Related to atom/settings-view#635 --- src/config.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/config.coffee b/src/config.coffee index 7c6dc8286..00ebabf58 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -283,6 +283,17 @@ ScopeDescriptor = require './scope-descriptor' # __Note__: You should strive to be so clear in your naming of the setting that # you do not need to specify a title or description! # +# Descriptions allow a subset of +# [Markdown formatting](https://help.github.com/articles/github-flavored-markdown/). +# Specifically, you may use the following in configuration setting descriptions: +# +# * **bold** - `**bold**` +# * *italics* - `*italics*` +# * [links](https://atom.io) - `[links](https://atom.io)` +# * `code spans` - `\`code spans\`` +# * line breaks - `line breaks
` +# * ~~strikethrough~~ - `~~strikethrough~~` +# # ## Best practices # # * Don't depend on (or write to) configuration keys outside of your keypath. From 3cf9238d798909f4d82cee70ff269c63b51d10d9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 13 Aug 2015 16:35:48 -0600 Subject: [PATCH 057/198] Select screen rows when click-dragging in gutter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …Instead of entire buffer rows --- spec/text-editor-component-spec.coffee | 60 +++++++++++++------------- src/text-editor-component.coffee | 37 +++++++++------- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 22562032c..ddc0f6d1e 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2073,123 +2073,123 @@ describe "TextEditorComponent", -> nextAnimationFrame() describe "when the gutter is clicked", -> - it "selects the clicked buffer row", -> + it "selects the clicked screen row", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1))) - expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [2, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [2, 0]] describe "when the gutter is meta-clicked", -> - it "creates a new selection for the clicked buffer row", -> + it "creates a new selection for the clicked screen row", -> editor.setSelectedScreenRange([[1, 0], [1, 2]]) gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [5, 0]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [2, 12]]] gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(7), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [5, 0]], [[5, 0], [10, 0]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [2, 12]], [[7, 4], [7, 12]]] describe "when the gutter is shift-clicked", -> beforeEach -> editor.setSelectedScreenRange([[7, 4], [7, 6]]) describe "when the clicked row is before the current selection's tail", -> - it "selects to the beginning of the clicked buffer row", -> + it "selects to the beginning of the clicked screen row", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), shiftKey: true)) - expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [7, 4]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [7, 4]] describe "when the clicked row is after the current selection's tail", -> - it "selects to the beginning of the buffer row following the clicked buffer row", -> + it "selects to the beginning of the screen row following the clicked screen row", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(11), shiftKey: true)) - expect(editor.getSelectedScreenRange()).toEqual [[7, 4], [16, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[7, 4], [11, 14]] describe "when the gutter is clicked and dragged", -> describe "when dragging downward", -> - it "selects the buffer rows between the start and end of the drag", -> + it "selects the screen rows between the start and end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1))) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6))) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(6))) - expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [10, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [6, 14]] describe "when dragging upward", -> - it "selects the buffer rows between the start and end of the drag", -> + it "selects the screen rows between the start and end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(6))) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(1))) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(1))) - expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [10, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [6, 14]] describe "when the gutter is meta-clicked and dragged", -> beforeEach -> editor.setSelectedScreenRange([[7, 4], [7, 6]]) describe "when dragging downward", -> - it "selects the buffer rows between the start and end of the drag", -> + it "selects the screen rows between the start and end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), metaKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(3), metaKey: true)) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(3), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[0, 0], [5, 0]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[1, 0], [3, 14]]] it "merges overlapping selections", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), metaKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(7), metaKey: true)) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(7), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[0, 0], [10, 0]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [7, 12]]] describe "when dragging upward", -> - it "selects the buffer rows between the start and end of the drag", -> + it "selects the screen rows between the start and end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(17), metaKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(11), metaKey: true)) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(11), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[10, 0], [19, 0]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[11, 4], [17, 13]]] it "merges overlapping selections", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(17), metaKey: true)) - gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(9), metaKey: true)) + gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(5), metaKey: true)) nextAnimationFrame() - gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(9), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[5, 0], [19, 0]]] + gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(5), metaKey: true)) + expect(editor.getSelectedScreenRanges()).toEqual [[[5, 0], [17, 13]]] describe "when the gutter is shift-clicked and dragged", -> describe "when the shift-click is below the existing selection's tail", -> describe "when dragging downward", -> - it "selects the buffer rows between the existing selection's tail and the end of the drag", -> + it "selects the screen rows between the existing selection's tail and the end of the drag", -> editor.setSelectedScreenRange([[1, 4], [1, 7]]) gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(7), shiftKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(11))) nextAnimationFrame() - expect(editor.getSelectedScreenRange()).toEqual [[1, 4], [16, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 4], [11, 14]] describe "when dragging upward", -> - it "selects the buffer rows between the end of the drag and the tail of the existing selection", -> + it "selects the screen rows between the end of the drag and the tail of the existing selection", -> editor.setSelectedScreenRange([[1, 4], [1, 7]]) gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(11), shiftKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(7))) nextAnimationFrame() - expect(editor.getSelectedScreenRange()).toEqual [[1, 4], [10, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 4], [7, 12]] describe "when the shift-click is above the existing selection's tail", -> describe "when dragging upward", -> - it "selects the buffer rows between the end of the drag and the tail of the existing selection", -> + it "selects the screen rows between the end of the drag and the tail of the existing selection", -> editor.setSelectedScreenRange([[7, 4], [7, 6]]) gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(3), shiftKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(1))) nextAnimationFrame() - expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [7, 4]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [7, 4]] describe "when dragging downward", -> - it "selects the buffer rows between the existing selection's tail and the end of the drag", -> + it "selects the screen rows between the existing selection's tail and the end of the drag", -> editor.setSelectedScreenRange([[7, 4], [7, 6]]) gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), shiftKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(3))) nextAnimationFrame() - expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [7, 4]] + expect(editor.getSelectedScreenRange()).toEqual [[3, 2], [7, 4]] describe "focus handling", -> inputNode = null @@ -3189,7 +3189,7 @@ describe "TextEditorComponent", -> {clientX, clientY} clientCoordinatesForScreenRowInGutter = (screenRow) -> - positionOffset = wrapperNode.pixelPositionForScreenPosition([screenRow, 1]) + positionOffset = wrapperNode.pixelPositionForScreenPosition([screenRow, Infinity]) gutterClientRect = componentNode.querySelector('.gutter').getBoundingClientRect() clientX = gutterClientRect.left + positionOffset.left - editor.getScrollLeft() clientY = gutterClientRect.top + positionOffset.top - editor.getScrollTop() diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index a46b352a3..101d27254 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -419,35 +419,42 @@ class TextEditorComponent @onGutterClick(event) onGutterClick: (event) => - clickedBufferRow = @editor.bufferRowForScreenRow(@screenPositionForMouseEvent(event).row) - @editor.setSelectedBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]], preserveFolds: true) - @handleGutterDrag(clickedBufferRow) + clickedScreenRow = @screenPositionForMouseEvent(event).row + startPosition = @editor.clipScreenPosition([clickedScreenRow, 0], skipSoftWrapIndentation: true) + endPosition = [clickedScreenRow + 1, 0] + @editor.setSelectedScreenRange([startPosition, endPosition], preserveFolds: true) + @handleGutterDrag(clickedScreenRow) onGutterMetaClick: (event) => - clickedBufferRow = @editor.bufferRowForScreenRow(@screenPositionForMouseEvent(event).row) - @editor.addSelectionForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]], preserveFolds: true) - @handleGutterDrag(clickedBufferRow) + clickedScreenRow = @screenPositionForMouseEvent(event).row + startPosition = @editor.clipScreenPosition([clickedScreenRow, 0], skipSoftWrapIndentation: true) + endPosition = [clickedScreenRow + 1, 0] + @editor.addSelectionForScreenRange([startPosition, endPosition], preserveFolds: true) + @handleGutterDrag(clickedScreenRow) onGutterShiftClick: (event) => - clickedBufferRow = @editor.bufferRowForScreenRow(@screenPositionForMouseEvent(event).row) - tailBufferPosition = @editor.getLastSelection().getTailBufferPosition() + clickedScreenRow = @screenPositionForMouseEvent(event).row + tailScreenPosition = @editor.getLastSelection().getTailScreenPosition() - if clickedBufferRow < tailBufferPosition.row - @editor.selectToBufferPosition([clickedBufferRow, 0], true) + if clickedScreenRow < tailScreenPosition.row + selectedPosition = @editor.clipScreenPosition([clickedScreenRow, 0], skipSoftWrapIndentation: true) + @editor.selectToScreenPosition(selectedPosition, true) else - @editor.selectToBufferPosition([clickedBufferRow + 1, 0], true) + @editor.selectToScreenPosition([clickedScreenRow + 1, 0], true) - @handleGutterDrag(tailBufferPosition.row, tailBufferPosition.column) + @handleGutterDrag(tailScreenPosition.row, tailScreenPosition.column) handleGutterDrag: (tailRow, tailColumn) -> tailPosition = [tailRow, tailColumn] if tailColumn? @handleDragUntilMouseUp (screenPosition) => - dragRow = @editor.bufferPositionForScreenPosition(screenPosition).row + dragRow = screenPosition.row if dragRow < tailRow - @editor.getLastSelection().setBufferRange([[dragRow, 0], tailPosition ? [tailRow + 1, 0]], reversed: true, autoscroll: false, preserveFolds: true) + startPosition = @editor.clipScreenPosition([dragRow, 0], skipSoftWrapIndentation: true) + @editor.getLastSelection().setScreenRange([startPosition, tailPosition ? [tailRow + 1, 0]], reversed: true, autoscroll: false, preserveFolds: true) else - @editor.getLastSelection().setBufferRange([tailPosition ? [tailRow, 0], [dragRow + 1, 0]], reversed: false, autoscroll: false, preserveFolds: true) + startPosition = tailPosition ? @editor.clipScreenPosition([tailRow, 0], skipSoftWrapIndentation: true) + @editor.getLastSelection().setScreenRange([startPosition, [dragRow + 1, 0]], reversed: false, autoscroll: false, preserveFolds: true) @editor.getLastCursor().autoscroll() onStylesheetsChanged: (styleElement) => From 31bdd563ed7651f75028435df3cbf35078c1c447 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 19 Aug 2015 12:40:14 -0600 Subject: [PATCH 058/198] Select by buffer row on gutter click, but by screen row on gutter drag --- spec/text-editor-component-spec.coffee | 42 +++++++++++++------------- src/text-editor-component.coffee | 41 +++++++++++++------------ 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index ddc0f6d1e..6ae37a7fd 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2073,84 +2073,84 @@ describe "TextEditorComponent", -> nextAnimationFrame() describe "when the gutter is clicked", -> - it "selects the clicked screen row", -> + it "selects the clicked buffer row", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1))) - expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [2, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [2, 0]] describe "when the gutter is meta-clicked", -> - it "creates a new selection for the clicked screen row", -> + it "creates a new selection for the clicked buffer row", -> editor.setSelectedScreenRange([[1, 0], [1, 2]]) gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [2, 12]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [5, 0]]] gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(7), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [2, 12]], [[7, 4], [7, 12]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [1, 2]], [[2, 0], [5, 0]], [[5, 0], [10, 0]]] describe "when the gutter is shift-clicked", -> beforeEach -> editor.setSelectedScreenRange([[7, 4], [7, 6]]) describe "when the clicked row is before the current selection's tail", -> - it "selects to the beginning of the clicked screen row", -> + it "selects to the beginning of the clicked buffer row", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), shiftKey: true)) - expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [7, 4]] + expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [7, 4]] describe "when the clicked row is after the current selection's tail", -> - it "selects to the beginning of the screen row following the clicked screen row", -> + it "selects to the beginning of the screen row following the clicked buffer row", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(11), shiftKey: true)) - expect(editor.getSelectedScreenRange()).toEqual [[7, 4], [11, 14]] + expect(editor.getSelectedScreenRange()).toEqual [[7, 4], [16, 0]] describe "when the gutter is clicked and dragged", -> describe "when dragging downward", -> - it "selects the screen rows between the start and end of the drag", -> + it "selects the buffer row containing the click, then screen rows until the end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1))) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6))) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(6))) - expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [6, 14]] + expect(editor.getSelectedScreenRange()).toEqual [[0, 0], [6, 14]] describe "when dragging upward", -> - it "selects the screen rows between the start and end of the drag", -> + it "selects the buffer row containing the click, then screen rows until the end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(6))) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(1))) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(1))) - expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [6, 14]] + expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [10, 0]] describe "when the gutter is meta-clicked and dragged", -> beforeEach -> editor.setSelectedScreenRange([[7, 4], [7, 6]]) describe "when dragging downward", -> - it "selects the screen rows between the start and end of the drag", -> + it "adds a selection from the buffer row containing the click to the screen row containing the end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), metaKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(3), metaKey: true)) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(3), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[1, 0], [3, 14]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[0, 0], [3, 14]]] - it "merges overlapping selections", -> + it "merges overlapping selections on mouseup", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), metaKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(7), metaKey: true)) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(7), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[1, 0], [7, 12]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[0, 0], [7, 12]]] describe "when dragging upward", -> - it "selects the screen rows between the start and end of the drag", -> + it "adds a selection from the buffer row containing the click to the screen row containing the end of the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(17), metaKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(11), metaKey: true)) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(11), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[11, 4], [17, 13]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[11, 4], [19, 0]]] - it "merges overlapping selections", -> + it "merges overlapping selections on mouseup", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(17), metaKey: true)) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(5), metaKey: true)) nextAnimationFrame() gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(5), metaKey: true)) - expect(editor.getSelectedScreenRanges()).toEqual [[[5, 0], [17, 13]]] + expect(editor.getSelectedScreenRanges()).toEqual [[[5, 0], [19, 0]]] describe "when the gutter is shift-clicked and dragged", -> describe "when the shift-click is below the existing selection's tail", -> diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 101d27254..3386eb184 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -420,41 +420,42 @@ class TextEditorComponent onGutterClick: (event) => clickedScreenRow = @screenPositionForMouseEvent(event).row - startPosition = @editor.clipScreenPosition([clickedScreenRow, 0], skipSoftWrapIndentation: true) - endPosition = [clickedScreenRow + 1, 0] - @editor.setSelectedScreenRange([startPosition, endPosition], preserveFolds: true) - @handleGutterDrag(clickedScreenRow) + clickedBufferRow = @editor.bufferRowForScreenRow(clickedScreenRow) + initialScreenRange = @editor.screenRangeForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]]) + @editor.setSelectedScreenRange(initialScreenRange, preserveFolds: true) + @handleGutterDrag(initialScreenRange) onGutterMetaClick: (event) => clickedScreenRow = @screenPositionForMouseEvent(event).row - startPosition = @editor.clipScreenPosition([clickedScreenRow, 0], skipSoftWrapIndentation: true) - endPosition = [clickedScreenRow + 1, 0] - @editor.addSelectionForScreenRange([startPosition, endPosition], preserveFolds: true) - @handleGutterDrag(clickedScreenRow) + clickedBufferRow = @editor.bufferRowForScreenRow(clickedScreenRow) + initialScreenRange = @editor.screenRangeForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]]) + @editor.addSelectionForScreenRange(initialScreenRange, preserveFolds: true) + @handleGutterDrag(initialScreenRange) onGutterShiftClick: (event) => - clickedScreenRow = @screenPositionForMouseEvent(event).row tailScreenPosition = @editor.getLastSelection().getTailScreenPosition() + clickedScreenRow = @screenPositionForMouseEvent(event).row + clickedBufferRow = @editor.bufferRowForScreenRow(clickedScreenRow) + clickedLineScreenRange = @editor.screenRangeForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]]) if clickedScreenRow < tailScreenPosition.row - selectedPosition = @editor.clipScreenPosition([clickedScreenRow, 0], skipSoftWrapIndentation: true) - @editor.selectToScreenPosition(selectedPosition, true) + @editor.selectToScreenPosition(clickedLineScreenRange.start, true) else - @editor.selectToScreenPosition([clickedScreenRow + 1, 0], true) + @editor.selectToScreenPosition(clickedLineScreenRange.end, true) - @handleGutterDrag(tailScreenPosition.row, tailScreenPosition.column) - - handleGutterDrag: (tailRow, tailColumn) -> - tailPosition = [tailRow, tailColumn] if tailColumn? + @handleGutterDrag(new Range(tailScreenPosition, tailScreenPosition)) + handleGutterDrag: (initialRange) -> @handleDragUntilMouseUp (screenPosition) => dragRow = screenPosition.row - if dragRow < tailRow + if dragRow < initialRange.start.row startPosition = @editor.clipScreenPosition([dragRow, 0], skipSoftWrapIndentation: true) - @editor.getLastSelection().setScreenRange([startPosition, tailPosition ? [tailRow + 1, 0]], reversed: true, autoscroll: false, preserveFolds: true) + screenRange = new Range(startPosition, startPosition).union(initialRange) + @editor.getLastSelection().setScreenRange(screenRange, reversed: true, autoscroll: false, preserveFolds: true) else - startPosition = tailPosition ? @editor.clipScreenPosition([tailRow, 0], skipSoftWrapIndentation: true) - @editor.getLastSelection().setScreenRange([startPosition, [dragRow + 1, 0]], reversed: false, autoscroll: false, preserveFolds: true) + endPosition = [dragRow + 1, 0] + screenRange = new Range(endPosition, endPosition).union(initialRange) + @editor.getLastSelection().setScreenRange(screenRange, reversed: false, autoscroll: false, preserveFolds: true) @editor.getLastCursor().autoscroll() onStylesheetsChanged: (styleElement) => From 675e9be262645da5b99cc07666b9db717ae0e239 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 19 Aug 2015 20:46:24 +0200 Subject: [PATCH 059/198] :fire: Remove mention of semi-deprecated view resolution step --- src/view-registry.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/view-registry.coffee b/src/view-registry.coffee index 67170d3b8..b02f3b600 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -137,8 +137,6 @@ class ViewRegistry # 4. Has a view provider been registered for the object? If true, use the # provider to create a view associated with the object, and return the # view. - # 5. Does the object have a `getViewClass` method? If true, use the method - # to create a view associated with the object, and return the view. # # If no associated view is returned by the sequence an error is thrown. # From c16f252ab7fd79e77c11433f87926717ef3ef29e Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 19 Aug 2015 20:54:39 +0200 Subject: [PATCH 060/198] :memo: Rephrase view resolution docs --- src/view-registry.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/view-registry.coffee b/src/view-registry.coffee index b02f3b600..6af7bd024 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -127,13 +127,13 @@ class ViewRegistry # ## View Resolution Algorithm # # The view associated with the object is resolved using the following - # heuristic sequence + # sequence # # 1. Is the object an instance of `HTMLElement`? If true, return the object. # 2. Does the object have a property named `element` with a value which is # an instance of `HTMLElement`? If true, return the property value. - # 3. Is the object a SpacePen view, which means it has a `jquery` property? - # If true, return the root DOM node (i.e. `object[0]`). + # 3. Is the object a jQuery object, indicated by the presence of a `jquery` + # property? If true, return the root DOM element (i.e. `object[0]`). # 4. Has a view provider been registered for the object? If true, use the # provider to create a view associated with the object, and return the # view. From 8ae3761cd768414eb53c019ce8419e73895617f3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 19 Aug 2015 11:50:14 -0600 Subject: [PATCH 061/198] Terminate selection drag on text input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On recent versions of OS X Yosemite, there is a delay before we receive the `mouseup` event when dragging selections with the trackpad. This means that we’re frequently accidentally inserting text before the selection process terminates, leading to unexpected selections after the text insertion. This fixes that behavior and makes the behavior more straightforward even in the case where the mouse button is remains held down during text insertion. --- spec/text-editor-component-spec.coffee | 36 ++++++++++++++++++++++++++ src/text-editor-component.coffee | 9 ++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 22562032c..9f8d5dfec 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1792,6 +1792,24 @@ describe "TextEditorComponent", -> expect(nextAnimationFrame).toBe noAnimationFrame expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]] + it "stops selecting if a textInput event occurs during the drag", -> + linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1)) + linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([6, 8]), which: 1)) + nextAnimationFrame() + expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]] + + inputEvent = new Event('textInput') + inputEvent.data = 'x' + Object.defineProperty(inputEvent, 'target', get: -> componentNode.querySelector('.hidden-input')) + componentNode.dispatchEvent(inputEvent) + nextAnimationFrame() + + expect(editor.getSelectedScreenRange()).toEqual [[2, 5], [2, 5]] + + linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 0]), which: 1)) + expect(nextAnimationFrame).toBe noAnimationFrame + expect(editor.getSelectedScreenRange()).toEqual [[2, 5], [2, 5]] + describe "when the command key is held down", -> it "adds a new selection and selects to the nearest screen position, then merges intersecting selections when the mouse button is released", -> editor.setSelectedScreenRange([[4, 4], [4, 9]]) @@ -1979,6 +1997,24 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getScrollTop()).toBe maxScrollTop + it "stops selecting if a textInput event occurs during the drag", -> + gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2))) + gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6))) + nextAnimationFrame() + expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [7, 0]] + + inputEvent = new Event('textInput') + inputEvent.data = 'x' + Object.defineProperty(inputEvent, 'target', get: -> componentNode.querySelector('.hidden-input')) + componentNode.dispatchEvent(inputEvent) + nextAnimationFrame() + + expect(editor.getSelectedScreenRange()).toEqual [[2, 1], [2, 1]] + + gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(12))) + expect(nextAnimationFrame).toBe noAnimationFrame + expect(editor.getSelectedScreenRange()).toEqual [[2, 1], [2, 1]] + describe "when the gutter is meta-clicked and dragged", -> beforeEach -> editor.setSelectedScreenRange([[3, 0], [3, 2]]) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index a46b352a3..af0b71f3b 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -523,15 +523,17 @@ class TextEditorComponent onMouseUp() if event.which is 0 onMouseUp = (event) => - stopDragging() - @editor.finalizeSelections() - @editor.mergeIntersectingSelections() + if dragging + stopDragging() + @editor.finalizeSelections() + @editor.mergeIntersectingSelections() pasteSelectionClipboard(event) stopDragging = -> dragging = false window.removeEventListener('mousemove', onMouseMove) window.removeEventListener('mouseup', onMouseUp) + willInsertTextSubscription.dispose() pasteSelectionClipboard = (event) => if event?.which is 2 and process.platform is 'linux' @@ -540,6 +542,7 @@ class TextEditorComponent window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) + willInsertTextSubscription = @editor.onWillInsertText(onMouseUp) isVisible: -> @domNode.offsetHeight > 0 or @domNode.offsetWidth > 0 From 7131c39a738337adb173433717e758f3a7bac646 Mon Sep 17 00:00:00 2001 From: Martin Rodalgaard Date: Thu, 20 Aug 2015 00:15:38 +0200 Subject: [PATCH 062/198] :memo: fix pane flex scale doc --- src/pane.coffee | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 4b13fca14..ae90a6317 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -84,7 +84,8 @@ class Pane extends Model # Public: Invoke the given callback when the pane resize # - # the callback will be invoked when pane's flexScale property changes + # The callback will be invoked when pane's flexScale property changes. + # Use {::getFlexScale} to get the current value. # # * `callback` {Function} to be called when the pane is resized # @@ -92,11 +93,13 @@ class Pane extends Model onDidChangeFlexScale: (callback) -> @emitter.on 'did-change-flex-scale', callback - # Public: Invoke the given callback with all current and future items. + # Public: Invoke the given callback with the current and future values of + # {::getFlexScale}. # - # * `callback` {Function} to be called with current and future items. - # * `item` An item that is present in {::getItems} at the time of - # subscription or that is added at some later time. + # * `callback` {Function} to be called with the current and future values of + # the {::getFlexScale} property. + # * `flexScale` {Number} representing the panes `flex-grow`; ability for a + # flex item to grow if necessary. # # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. observeFlexScale: (callback) -> From 65a465416ab4350113d6e55d02817987f1387a63 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 19 Aug 2015 15:23:11 -0700 Subject: [PATCH 063/198] :memo: Document flexScale passed to callback --- src/pane.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pane.coffee b/src/pane.coffee index ae90a6317..06ea88e47 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -82,12 +82,14 @@ class Pane extends Model Section: Event Subscription ### - # Public: Invoke the given callback when the pane resize + # Public: Invoke the given callback when the pane resizes # # The callback will be invoked when pane's flexScale property changes. # Use {::getFlexScale} to get the current value. # # * `callback` {Function} to be called when the pane is resized + # * `flexScale` {Number} representing the panes `flex-grow`; ability for a + # flex item to grow if necessary. # # Returns a {Disposable} on which '.dispose()' can be called to unsubscribe. onDidChangeFlexScale: (callback) -> From bca901ecc6c98dd2c2cacc7ef0d032ce37de4608 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 19 Aug 2015 15:46:42 -0700 Subject: [PATCH 064/198] :arrow_up: archive-view@0.60 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 207627305..8380e1af9 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "solarized-dark-syntax": "0.38.1", "solarized-light-syntax": "0.22.1", "about": "1.1.0", - "archive-view": "0.58.0", + "archive-view": "0.60.0", "autocomplete-atom-api": "0.9.2", "autocomplete-css": "0.10.1", "autocomplete-html": "0.7.2", From f9d2d28e2a75b19c8450d1997566d801dc66d252 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 19 Aug 2015 15:50:16 -0700 Subject: [PATCH 065/198] :arrow_up: language-todo@0.27 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8380e1af9..03f49ab79 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "language-source": "0.9.0", "language-sql": "0.17.0", "language-text": "0.7.0", - "language-todo": "0.26.0", + "language-todo": "0.27.0", "language-toml": "0.16.0", "language-xml": "0.32.0", "language-yaml": "0.24.0" From 7a42d6e40966b3e023b0954f695b0d917b16b97d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 19 Aug 2015 16:20:38 -0700 Subject: [PATCH 066/198] :arrow_up: language-javascript@0.89 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03f49ab79..1350630cc 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "language-html": "0.41.0", "language-hyperlink": "0.14.0", "language-java": "0.16.0", - "language-javascript": "0.87.1", + "language-javascript": "0.89.0", "language-json": "0.16.0", "language-less": "0.28.2", "language-make": "0.17.0", From 49e37f541046f0ce9037494d3fe9bf7e85847980 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 19 Aug 2015 16:36:24 -0700 Subject: [PATCH 067/198] Use source-map-support to handle stack trace conversion Previously, our Error.convertStackTrace function was provided by coffeestack, which only works for coffee-script. This adds a dependency on 'source-map-support', which works for any source file with inline source maps. This also refactors the code for registering our compilers (coffee-script, typescript, and babel) so that caching logic is shared. --- package.json | 1 + spec/babel-spec.coffee | 53 +-------- spec/compile-cache-spec.coffee | 39 ------- spec/typescript-spec.coffee | 21 ---- src/babel.coffee | 201 --------------------------------- src/babel.js | 70 ++++++++++++ src/coffee-script.js | 38 +++++++ src/compile-cache.coffee | 30 ----- src/compile-cache.js | 123 ++++++++++++++++++++ src/task.coffee | 10 +- src/typescript.coffee | 106 ----------------- src/typescript.js | 53 +++++++++ static/index.js | 44 +------- 13 files changed, 291 insertions(+), 498 deletions(-) delete mode 100644 spec/compile-cache-spec.coffee delete mode 100644 src/babel.coffee create mode 100644 src/babel.js create mode 100644 src/coffee-script.js delete mode 100644 src/compile-cache.coffee create mode 100644 src/compile-cache.js delete mode 100644 src/typescript.coffee create mode 100644 src/typescript.js diff --git a/package.json b/package.json index 88853e79c..811814a0f 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "semver": "^4.3.3", "serializable": "^1", "service-hub": "^0.6.2", + "source-map-support": "^0.3.2", "space-pen": "3.8.2", "stacktrace-parser": "0.1.1", "temp": "0.8.1", diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index 1f71faa93..421e4c48a 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -1,70 +1,19 @@ -babel = require '../src/babel' -crypto = require 'crypto' -grim = require 'grim' - describe "Babel transpiler support", -> - beforeEach -> - jasmine.snapshotDeprecations() - - afterEach -> - jasmine.restoreDeprecationsSnapshot() - - describe "::createBabelVersionAndOptionsDigest", -> - it "returns a digest for the library version and specified options", -> - defaultOptions = - blacklist: [ - 'useStrict' - ] - experimental: true - optional: [ - 'asyncToGenerator' - ] - reactCompat: true - sourceMap: 'inline' - version = '3.0.14' - shasum = crypto.createHash('sha1') - 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 = 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-comment.js') expect(transpiled(3)).toBe 4 - expect(grim.getDeprecationsLength()).toBe 0 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 - expect(grim.getDeprecationsLength()).toBe 0 - - describe "when a .js file starts with 'use 6to5';", -> - it "transpiles it using babel and adds a pragma deprecation", -> - expect(grim.getDeprecationsLength()).toBe 0 - transpiled = require('./fixtures/babel/6to5-single-quotes.js') - expect(transpiled(3)).toBe 4 - expect(grim.getDeprecationsLength()).toBe 1 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 - expect(grim.getDeprecationsLength()).toBe 0 - describe 'when a .js file starts with "use 6to5";', -> - it "transpiles it using babel and adds a pragma deprecation", -> - expect(grim.getDeprecationsLength()).toBe 0 - transpiled = require('./fixtures/babel/6to5-double-quotes.js') - expect(transpiled(3)).toBe 4 - expect(grim.getDeprecationsLength()).toBe 1 - - describe "when a .js file does not start with 'use 6to6';", -> + describe "when a .js file does not start with 'use babel';", -> 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 deleted file mode 100644 index 6eb6556d0..000000000 --- a/spec/compile-cache-spec.coffee +++ /dev/null @@ -1,39 +0,0 @@ -path = require 'path' -CSON = require 'season' -CoffeeCache = require 'coffee-cash' - -babel = require '../src/babel' -typescript = require '../src/typescript' -CompileCache = require '../src/compile-cache' - -describe "Compile Cache", -> - describe ".addPathToCache(filePath)", -> - it "adds the path to the correct CSON, CoffeeScript, babel or typescript cache", -> - spyOn(CSON, 'readFileSync').andCallThrough() - spyOn(CoffeeCache, 'addPathToCache').andCallThrough() - spyOn(babel, 'addPathToCache').andCallThrough() - spyOn(typescript, 'addPathToCache').andCallThrough() - - CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'cson.cson')) - expect(CSON.readFileSync.callCount).toBe 1 - expect(CoffeeCache.addPathToCache.callCount).toBe 0 - expect(babel.addPathToCache.callCount).toBe 0 - expect(typescript.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(babel.addPathToCache.callCount).toBe 0 - expect(typescript.addPathToCache.callCount).toBe 0 - - 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 - expect(typescript.addPathToCache.callCount).toBe 0 - - CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'typescript', 'valid.ts')) - expect(CSON.readFileSync.callCount).toBe 1 - expect(CoffeeCache.addPathToCache.callCount).toBe 1 - expect(babel.addPathToCache.callCount).toBe 1 - expect(typescript.addPathToCache.callCount).toBe 1 diff --git a/spec/typescript-spec.coffee b/spec/typescript-spec.coffee index 493715d36..152848c74 100644 --- a/spec/typescript-spec.coffee +++ b/spec/typescript-spec.coffee @@ -1,25 +1,4 @@ -typescript = require '../src/typescript' -crypto = require 'crypto' - describe "TypeScript transpiler support", -> - describe "::createTypeScriptVersionAndOptionsDigest", -> - it "returns a digest for the library version and specified options", -> - defaultOptions = - target: 1 # ES5 - module: 'commonjs' - sourceMap: true - version = '1.4.1' - shasum = crypto.createHash('sha1') - shasum.update('typescript', 'utf8') - shasum.update('\0', 'utf8') - shasum.update(version, 'utf8') - shasum.update('\0', 'utf8') - shasum.update(JSON.stringify(defaultOptions)) - expectedDigest = shasum.digest('hex') - - observedDigest = typescript.createTypeScriptVersionAndOptionsDigest(version, defaultOptions) - expect(observedDigest).toEqual expectedDigest - describe "when there is a .ts file", -> it "transpiles it using typescript", -> transpiled = require('./fixtures/typescript/valid.ts') diff --git a/src/babel.coffee b/src/babel.coffee deleted file mode 100644 index afa48eb1c..000000000 --- a/src/babel.coffee +++ /dev/null @@ -1,201 +0,0 @@ -### -Cache for source code transpiled by Babel. - -Inspired by https://github.com/atom/atom/blob/6b963a562f8d495fbebe6abdbafbc7caf705f2c3/src/coffee-cache.coffee. -### - -crypto = require 'crypto' -fs = require 'fs-plus' -path = require 'path' -babel = null # Defer until used -Grim = null # Defer until used - -stats = - hits: 0 - misses: 0 - -defaultOptions = - # Currently, the cache key is a function of: - # * The version of Babel used to transpile the .js file. - # * The contents of this defaultOptions object. - # * The contents of the .js file. - # That means that we cannot allow information from an unknown source - # to affect the cache key for the output of transpilation, which means - # we cannot allow users to override these default options via a .babelrc - # file, because the contents of that .babelrc file will not make it into - # the cache key. It would be great to support .babelrc files once we - # have a way to do so that is safe with respect to caching. - breakConfig: true - - # The Chrome dev tools will show the original version of the file - # when the source map is inlined. - sourceMap: 'inline' - - # 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, - # I think this can include es6.arrowFunctions, es6.classes, and - # possibly others, but I want to be conservative. - blacklist: [ - 'es6.forOf' - 'useStrict' - ] - - optional: [ - # Target a version of the regenerator runtime that - # supports yield so the transpiled code is cleaner/smaller. - 'asyncToGenerator' - ] - - # Includes support for es7 features listed at: - # http://babeljs.io/docs/usage/experimental/. - stage: 0 - - -### -shasum - Hash with an update() method. -value - Must be a value that could be returned by JSON.parse(). -### -updateDigestForJsonValue = (shasum, value) -> - # Implmentation is similar to that of pretty-printing a JSON object, except: - # * Strings are not escaped. - # * No effort is made to avoid trailing commas. - # These shortcuts should not affect the correctness of this function. - type = typeof value - if type is 'string' - shasum.update('"', 'utf8') - shasum.update(value, 'utf8') - shasum.update('"', 'utf8') - else if type in ['boolean', 'number'] - shasum.update(value.toString(), 'utf8') - else if value is null - shasum.update('null', 'utf8') - else if Array.isArray value - shasum.update('[', 'utf8') - for item in value - updateDigestForJsonValue(shasum, item) - shasum.update(',', 'utf8') - shasum.update(']', 'utf8') - else - # value must be an object: be sure to sort the keys. - keys = Object.keys value - keys.sort() - - shasum.update('{', 'utf8') - for key in keys - updateDigestForJsonValue(shasum, key) - shasum.update(': ', 'utf8') - updateDigestForJsonValue(shasum, value[key]) - shasum.update(',', 'utf8') - shasum.update('}', 'utf8') - -createBabelVersionAndOptionsDigest = (version, options) -> - shasum = crypto.createHash('sha1') - # 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') - updateDigestForJsonValue(shasum, options) - shasum.digest('hex') - -cacheDir = null -jsCacheDir = null - -getCachePath = (sourceCode) -> - digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex') - - unless jsCacheDir? - to5Version = require('babel-core/package.json').version - jsCacheDir = path.join(cacheDir, createBabelVersionAndOptionsDigest(to5Version, defaultOptions)) - - path.join(jsCacheDir, "#{digest}.js") - -getCachedJavaScript = (cachePath) -> - if fs.isFileSync(cachePath) - try - cachedJavaScript = fs.readFileSync(cachePath, 'utf8') - stats.hits++ - return cachedJavaScript - null - -# Returns the babel options that should be used to transpile filePath. -createOptions = (filePath) -> - options = filename: filePath - for key, value of defaultOptions - options[key] = value - options - -transpile = (sourceCode, filePath, cachePath) -> - options = createOptions(filePath) - babel ?= require 'babel-core' - js = babel.transform(sourceCode, options).code - stats.misses++ - - try - fs.writeFileSync(cachePath, js) - - js - -# Function that obeys the contract of an entry in the require.extensions map. -# Returns the transpiled version of the JavaScript code at filePath, which is -# either generated on the fly or pulled from cache. -loadFile = (module, filePath) -> - sourceCode = fs.readFileSync(filePath, 'utf8') - if sourceCode.startsWith('"use babel"') or sourceCode.startsWith("'use babel'") or sourceCode.startsWith('/** use babel */') - # Continue. - else if sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'") - # Create a manual deprecation since the stack is too deep to use Grim - # which limits the depth to 3 - Grim ?= require 'grim' - stack = [ - { - fileName: __filename - functionName: 'loadFile' - location: "#{__filename}:161:5" - } - { - fileName: filePath - functionName: '' - location: "#{filePath}:1:1" - } - ] - deprecation = - message: "Use the 'use babel' pragma instead of 'use 6to5'" - stacks: [stack] - Grim.addSerializedDeprecation(deprecation) - else - return module._compile(sourceCode, filePath) - - cachePath = getCachePath(sourceCode) - js = getCachedJavaScript(cachePath) ? transpile(sourceCode, filePath, cachePath) - module._compile(js, filePath) - -register = -> - Object.defineProperty(require.extensions, '.js', { - enumerable: true - writable: false - value: loadFile - }) - -setCacheDirectory = (newCacheDir) -> - if cacheDir isnt newCacheDir - cacheDir = newCacheDir - jsCacheDir = null - -module.exports = - register: register - setCacheDirectory: setCacheDirectory - getCacheMisses: -> stats.misses - getCacheHits: -> stats.hits - defaultOptions: defaultOptions - - # Visible for testing. - createBabelVersionAndOptionsDigest: createBabelVersionAndOptionsDigest - - addPathToCache: (filePath) -> - return if path.extname(filePath) isnt '.js' - - sourceCode = fs.readFileSync(filePath, 'utf8') - cachePath = getCachePath(sourceCode) - transpile(sourceCode, filePath, cachePath) diff --git a/src/babel.js b/src/babel.js new file mode 100644 index 000000000..eedfef5e2 --- /dev/null +++ b/src/babel.js @@ -0,0 +1,70 @@ +'use strict' + +const _ = require('underscore-plus') +const crypto = require('crypto') +const path = require('path') + +let babel = null +let babelVersionDirectory = null + +// This field is used by the Gruntfile for compiling babel in bundled packages. +exports.defaultOptions = { + + // Currently, the cache key is a function of: + // * The version of Babel used to transpile the .js file. + // * The contents of this defaultOptions object. + // * The contents of the .js file. + // That means that we cannot allow information from an unknown source + // to affect the cache key for the output of transpilation, which means + // we cannot allow users to override these default options via a .babelrc + // file, because the contents of that .babelrc file will not make it into + // the cache key. It would be great to support .babelrc files once we + // have a way to do so that is safe with respect to caching. + breakConfig: true, + + sourceMap: 'inline', + blacklist: ['es6.forOf', 'useStrict'], + optional: ['asyncToGenerator'], + stage: 0 +} + +exports.shouldCompile = function(sourceCode) { + return sourceCode.startsWith('/** use babel */') || + sourceCode.startsWith('"use babel"') || + sourceCode.startsWith("'use babel'") +} + +exports.getCachePath = function(sourceCode) { + if (babelVersionDirectory == null) { + let babelVersion = require('babel-core/package.json').version + babelVersionDirectory = path.join('js', 'babel', createVersionAndOptionsDigest(babelVersion, defaultOptions)) + } + + return path.join( + babelVersionDirectory, + crypto + .createHash('sha1') + .update(sourceCode, 'utf8') + .digest('hex') + ".js" + ) +} + +exports.compile = function(sourceCode, filePath) { + if (!babel) { + babel = require('babel-core') + } + + let options = _.defaults({filename: filePath}, defaultOptions) + return babel.transform(sourceCode, options).code +} + +function createVersionAndOptionsDigest (version, options) { + return crypto + .createHash('sha1') + .update('babel-core', 'utf8') + .update('\0', 'utf8') + .update(version, 'utf8') + .update('\0', 'utf8') + .update(JSON.stringify(options), 'utf8') + .digest('hex') +} diff --git a/src/coffee-script.js b/src/coffee-script.js new file mode 100644 index 000000000..9cd0a0777 --- /dev/null +++ b/src/coffee-script.js @@ -0,0 +1,38 @@ +'use strict' + +const crypto = require('crypto') +const path = require('path') + +// The coffee-script compiler is required eagerly because: +// 1. It is always used. +// 2. It reassigns Error.prepareStackTrace, so we need to make sure that +// the 'source-map-support' module is installed *after* it is loaded. +const CoffeeScript = require('coffee-script') + +exports.shouldCompile = function() { + return true +} + +exports.getCachePath = function(sourceCode) { + return path.join( + "coffee", + crypto + .createHash('sha1') + .update(sourceCode, 'utf8') + .digest('hex') + ".js" + ) +} + +exports.compile = function(sourceCode, filePath) { + let output = CoffeeScript.compile(sourceCode, { + filename: filePath, + sourceMap: true + }) + + let js = output.js + js += '\n' + js += '//# sourceMappingURL=data:application/json;base64,' + js += new Buffer(output.v3SourceMap).toString('base64') + js += '\n' + return js +} diff --git a/src/compile-cache.coffee b/src/compile-cache.coffee deleted file mode 100644 index 8fe8d6711..000000000 --- a/src/compile-cache.coffee +++ /dev/null @@ -1,30 +0,0 @@ -path = require 'path' -CSON = require 'season' -CoffeeCache = require 'coffee-cash' -babel = require './babel' -typescript = require './typescript' - -# 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 -# compile anything. -exports.addPathToCache = (filePath, atomHome) -> - atomHome ?= process.env.ATOM_HOME - cacheDir = path.join(atomHome, 'compile-cache') - # Use separate compile cache when sudo'ing as root to avoid permission issues - if process.env.USER is 'root' and process.env.SUDO_USER and process.env.SUDO_USER isnt process.env.USER - cacheDir = path.join(cacheDir, 'root') - - CoffeeCache.setCacheDirectory(path.join(cacheDir, 'coffee')) - CSON.setCacheDir(path.join(cacheDir, 'cson')) - babel.setCacheDirectory(path.join(cacheDir, 'js', 'babel')) - typescript.setCacheDirectory(path.join(cacheDir, 'ts')) - - switch path.extname(filePath) - when '.coffee' - CoffeeCache.addPathToCache(filePath) - when '.cson' - CSON.readFileSync(filePath) - when '.js' - babel.addPathToCache(filePath) - when '.ts' - typescript.addPathToCache(filePath) diff --git a/src/compile-cache.js b/src/compile-cache.js new file mode 100644 index 000000000..0666e7635 --- /dev/null +++ b/src/compile-cache.js @@ -0,0 +1,123 @@ +'use strict' + +const path = require('path') +const CSON = require('season') +const fs = require('fs-plus') + +const COMPILERS = { + '.js': require('./babel'), + '.ts': require('./typescript'), + '.coffee': require('./coffee-script') +} + +for (let extension in COMPILERS) { + let compiler = COMPILERS[extension] + Object.defineProperty(require.extensions, extension, { + enumerable: true, + writable: false, + value: function (module, filePath) { + let code = compileFileAtPath(compiler, filePath) + return module._compile(code, filePath) + } + }) +} + +let cacheDirectory = null + +exports.setAtomHomeDirectory = function (atomHome) { + let cacheDir = path.join(atomHome, 'compile-cache') + if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { + cacheDir = path.join(cacheDirectory, 'root') + } + this.setCacheDirectory(cacheDir) +} + +exports.setCacheDirectory = function (directory) { + cacheDirectory = directory + CSON.setCacheDir(path.join(cacheDirectory, 'cson')); +} + +exports.getCacheDirectory = function () { + return cacheDirectory +} + +exports.addPathToCache = function (filePath, atomHome) { + this.setAtomHomeDirectory(atomHome) + extension = path.extname(filePath) + if (extension === '.cson') { + return CSON.readFileSync(filePath) + } + + if (compiler = COMPILERS[extension]) { + return compileFileAtPath(compiler, filePath) + } +} + +function compileFileAtPath (compiler, filePath) { + let sourceCode = fs.readFileSync(filePath, 'utf8') + if (compiler.shouldCompile(sourceCode, filePath)) { + let cachePath = compiler.getCachePath(sourceCode, filePath) + let compiledCode = readCachedJavascript(cachePath) + if (compiledCode == null) { + compiledCode = compiler.compile(sourceCode, filePath) + writeCachedJavascript(cachePath, compiledCode) + } + return compiledCode + } + return sourceCode +} + +function readCachedJavascript (relativeCachePath) { + let cachePath = path.join(cacheDirectory, relativeCachePath) + if (fs.isFileSync(cachePath)) { + try { + return fs.readFileSync(cachePath, 'utf8') + } catch (error) {} + } + return null +} + +function writeCachedJavascript (relativeCachePath, code) { + let cachePath = path.join(cacheDirectory, relativeCachePath) + fs.writeFileSync(cachePath, code, 'utf8') +} + +const InlineSourceMapRegExp = /\/\/[#@]\s*sourceMappingURL=([^'"]+)\s*$/g + +require('source-map-support').install({ + handleUncaughtExceptions: false, + + // Most of this logic is the same as the default implementation in the + // source-map-support module, but we've overridden it to read the javascript + // code from our cache directory. + retrieveSourceMap: function (filePath) { + if (!fs.isFileSync(filePath)){ + return null + } + + let sourceCode = fs.readFileSync(filePath, 'utf8') + let compiler = COMPILERS[path.extname(filePath)] + let fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) + if (fileData == null) { + return null + } + + let match, lastMatch + InlineSourceMapRegExp.lastIndex = 0 + while ((match = InlineSourceMapRegExp.exec(fileData))) { + lastMatch = match + } + if (lastMatch == null){ + return null + } + + let sourceMappingURL = lastMatch[1] + let rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) + let sourceMapData = new Buffer(rawData, 'base64').toString() + + return { + map: JSON.parse(sourceMapData), + url: null + } + } +}) diff --git a/src/task.coffee b/src/task.coffee index 337192dcd..6b1162396 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -66,15 +66,11 @@ class Task # * `taskPath` The {String} path to the CoffeeScript/JavaScript file that # exports a single {Function} to execute. constructor: (taskPath) -> - coffeeCacheRequire = "require('#{require.resolve('coffee-cash')}')" - coffeeCachePath = require('coffee-cash').getCacheDirectory() - coffeeStackRequire = "require('#{require.resolve('coffeestack')}')" - stackCachePath = require('coffeestack').getCacheDirectory() + compileCacheRequire = "require('#{require.resolve('./compile-cache')}')" + compileCachePath = require('./compile-cache').getCacheDirectory() taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');" bootstrap = """ - #{coffeeCacheRequire}.setCacheDirectory('#{coffeeCachePath}'); - #{coffeeCacheRequire}.register(); - #{coffeeStackRequire}.setCacheDirectory('#{stackCachePath}'); + #{compileCacheRequire}.setCacheDirectory('#{compileCachePath}'); #{taskBootstrapRequire} """ bootstrap = bootstrap.replace(/\\/g, "\\\\") diff --git a/src/typescript.coffee b/src/typescript.coffee deleted file mode 100644 index 3a54941f3..000000000 --- a/src/typescript.coffee +++ /dev/null @@ -1,106 +0,0 @@ -### -Cache for source code transpiled by TypeScript. - -Inspired by https://github.com/atom/atom/blob/7a719d585db96ff7d2977db9067e1d9d4d0adf1a/src/babel.coffee -### - -crypto = require 'crypto' -fs = require 'fs-plus' -path = require 'path' -tss = null # Defer until used - -stats = - hits: 0 - misses: 0 - -defaultOptions = - target: 1 # ES5 - module: 'commonjs' - sourceMap: true - -createTypeScriptVersionAndOptionsDigest = (version, options) -> - shasum = crypto.createHash('sha1') - # Include the version of typescript in the hash. - shasum.update('typescript', 'utf8') - shasum.update('\0', 'utf8') - shasum.update(version, 'utf8') - shasum.update('\0', 'utf8') - shasum.update(JSON.stringify(options)) - shasum.digest('hex') - -cacheDir = null -jsCacheDir = null - -getCachePath = (sourceCode) -> - digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex') - - unless jsCacheDir? - tssVersion = require('typescript-simple/package.json').version - jsCacheDir = path.join(cacheDir, createTypeScriptVersionAndOptionsDigest(tssVersion, defaultOptions)) - - path.join(jsCacheDir, "#{digest}.js") - -getCachedJavaScript = (cachePath) -> - if fs.isFileSync(cachePath) - try - cachedJavaScript = fs.readFileSync(cachePath, 'utf8') - stats.hits++ - return cachedJavaScript - null - -# Returns the TypeScript options that should be used to transpile filePath. -createOptions = (filePath) -> - options = filename: filePath - for key, value of defaultOptions - options[key] = value - options - -transpile = (sourceCode, filePath, cachePath) -> - options = createOptions(filePath) - unless tss? - {TypeScriptSimple} = require 'typescript-simple' - tss = new TypeScriptSimple(options, false) - js = tss.compile(sourceCode, filePath) - stats.misses++ - - try - fs.writeFileSync(cachePath, js) - - js - -# Function that obeys the contract of an entry in the require.extensions map. -# Returns the transpiled version of the JavaScript code at filePath, which is -# either generated on the fly or pulled from cache. -loadFile = (module, filePath) -> - sourceCode = fs.readFileSync(filePath, 'utf8') - cachePath = getCachePath(sourceCode) - js = getCachedJavaScript(cachePath) ? transpile(sourceCode, filePath, cachePath) - module._compile(js, filePath) - -register = -> - Object.defineProperty(require.extensions, '.ts', { - enumerable: true - writable: false - value: loadFile - }) - -setCacheDirectory = (newCacheDir) -> - if cacheDir isnt newCacheDir - cacheDir = newCacheDir - jsCacheDir = null - -module.exports = - register: register - setCacheDirectory: setCacheDirectory - getCacheMisses: -> stats.misses - getCacheHits: -> stats.hits - - # Visible for testing. - createTypeScriptVersionAndOptionsDigest: createTypeScriptVersionAndOptionsDigest - - addPathToCache: (filePath) -> - return if path.extname(filePath) isnt '.ts' - - sourceCode = fs.readFileSync(filePath, 'utf8') - cachePath = getCachePath(sourceCode) - transpile(sourceCode, filePath, cachePath) diff --git a/src/typescript.js b/src/typescript.js new file mode 100644 index 000000000..c372c775b --- /dev/null +++ b/src/typescript.js @@ -0,0 +1,53 @@ +'use strict' + +const _ = require('underscore-plus') +const crypto = require('crypto') +const path = require('path') + +let TypeScriptSimple = null +let typescriptVersionDir = null + +const defaultOptions = { + target: 1, + module: 'commonjs', + sourceMap: true +} + +exports.shouldCompile = function() { + return true +} + +exports.getCachePath = function(sourceCode) { + if (typescriptVersionDir == null) { + let version = require('typescript-simple/package.json').version + typescriptVersionDir = path.join('ts', createVersionAndOptionsDigest(version, defaultOptions)) + } + + return path.join( + typescriptVersionDir, + crypto + .createHash('sha1') + .update(sourceCode, 'utf8') + .digest('hex') + ".js" + ) +} + +exports.compile = function(sourceCode, filePath) { + if (!TypeScriptSimple) { + TypeScriptSimple = require('typescript-simple').TypeScriptSimple + } + + let options = _.defaults({filename: filePath}, defaultOptions) + return new TypeScriptSimple(options, false).compile(sourceCode, filePath) +} + +function createVersionAndOptionsDigest (version, options) { + return crypto + .createHash('sha1') + .update('typescript', 'utf8') + .update('\0', 'utf8') + .update(version, 'utf8') + .update('\0', 'utf8') + .update(JSON.stringify(options), 'utf8') + .digest('hex') +} diff --git a/static/index.js b/static/index.js index 8fe71a6a9..238cb385e 100644 --- a/static/index.js +++ b/static/index.js @@ -41,15 +41,6 @@ window.onload = function() { } } -var getCacheDirectory = function() { - var cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache'); - // Use separate compile cache when sudo'ing as root to avoid permission issues - if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { - cacheDir = path.join(cacheDir, 'root'); - } - return cacheDir; -} - var setLoadTime = function(loadTime) { if (global.atom) { global.atom.loadTime = loadTime; @@ -67,9 +58,8 @@ var handleSetupError = function(error) { } var setupWindow = function(loadSettings) { - var cacheDir = getCacheDirectory(); - - setupCoffeeCache(cacheDir); + var compileCache = require('../src/compile-cache') + compileCache.setAtomHomeDirectory(process.env.ATOM_HOME) ModuleCache = require('../src/module-cache'); ModuleCache.register(loadSettings); @@ -88,21 +78,11 @@ var setupWindow = function(loadSettings) { }); setupVmCompatibility(); - setupCsonCache(cacheDir); - setupSourceMapCache(cacheDir); - setupBabel(cacheDir); - setupTypeScript(cacheDir); require(loadSettings.bootstrapScript); require('ipc').sendChannel('window-command', 'window:loaded'); } -var setupCoffeeCache = function(cacheDir) { - var CoffeeCache = require('coffee-cash'); - CoffeeCache.setCacheDirectory(path.join(cacheDir, 'coffee')); - CoffeeCache.register(); -} - var setupAtomHome = function() { if (!process.env.ATOM_HOME) { var home; @@ -121,26 +101,6 @@ var setupAtomHome = function() { } } -var setupBabel = function(cacheDir) { - var babel = require('../src/babel'); - babel.setCacheDirectory(path.join(cacheDir, 'js', 'babel')); - babel.register(); -} - -var setupTypeScript = function(cacheDir) { - var typescript = require('../src/typescript'); - typescript.setCacheDirectory(path.join(cacheDir, 'typescript')); - typescript.register(); -} - -var setupCsonCache = function(cacheDir) { - require('season').setCacheDir(path.join(cacheDir, 'cson')); -} - -var setupSourceMapCache = function(cacheDir) { - require('coffeestack').setCacheDirectory(path.join(cacheDir, 'coffee', 'source-maps')); -} - var setupVmCompatibility = function() { var vm = require('vm'); if (!vm.Script.createContext) { From 2285e8a42c3f12d47d31c22f96c541ca48a575ee Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 19 Aug 2015 17:27:57 -0700 Subject: [PATCH 068/198] Move babel config to a separate file in static directory --- build/Gruntfile.coffee | 2 +- src/babel.js | 21 +-------------------- static/babelrc.json | 7 +++++++ 3 files changed, 9 insertions(+), 21 deletions(-) create mode 100644 static/babelrc.json diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 4329c9cd9..860616745 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -3,7 +3,7 @@ path = require 'path' os = require 'os' glob = require 'glob' usesBabel = require './lib/uses-babel' -babelOptions = require('../src/babel').defaultOptions +babelOptions = require('../static/babelrc.json') # Add support for obselete APIs of vm module so we can make some third-party # modules work under node v0.11.x. diff --git a/src/babel.js b/src/babel.js index eedfef5e2..a80694587 100644 --- a/src/babel.js +++ b/src/babel.js @@ -7,26 +7,7 @@ const path = require('path') let babel = null let babelVersionDirectory = null -// This field is used by the Gruntfile for compiling babel in bundled packages. -exports.defaultOptions = { - - // Currently, the cache key is a function of: - // * The version of Babel used to transpile the .js file. - // * The contents of this defaultOptions object. - // * The contents of the .js file. - // That means that we cannot allow information from an unknown source - // to affect the cache key for the output of transpilation, which means - // we cannot allow users to override these default options via a .babelrc - // file, because the contents of that .babelrc file will not make it into - // the cache key. It would be great to support .babelrc files once we - // have a way to do so that is safe with respect to caching. - breakConfig: true, - - sourceMap: 'inline', - blacklist: ['es6.forOf', 'useStrict'], - optional: ['asyncToGenerator'], - stage: 0 -} +const defaultOptions = require('../static/babelrc.json') exports.shouldCompile = function(sourceCode) { return sourceCode.startsWith('/** use babel */') || diff --git a/static/babelrc.json b/static/babelrc.json new file mode 100644 index 000000000..26b70dc41 --- /dev/null +++ b/static/babelrc.json @@ -0,0 +1,7 @@ +{ + "breakConfig": true, + "sourceMap": "inline", + "blacklist": ["es6.forOf", "useStrict"], + "optional": ["asyncToGenerator"], + "stage": 0 +} From 5c519036dcfc65aa3b6dac435733a74d870904b6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Aug 2015 10:04:31 +0200 Subject: [PATCH 069/198] :fire: Avoid emitting event on cursor update --- src/text-editor-presenter.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index b79abc267..5c7bac851 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -418,8 +418,6 @@ class TextEditorPresenter pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect - @emitDidUpdateState() - updateOverlaysState: -> return unless @hasOverlayPositionRequirements() @@ -1370,6 +1368,8 @@ class TextEditorPresenter didChangeVisibilityDisposable = cursor.onDidChangeVisibility => @shouldUpdateCursorsState = true + @emitDidUpdateState() + didDestroyDisposable = cursor.onDidDestroy => @disposables.remove(didChangePositionDisposable) @disposables.remove(didChangeVisibilityDisposable) From e76c2658262ad9869f22c3ea1e6823f2632e73cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C5=BDu=C5=BEak?= Date: Thu, 20 Aug 2015 19:04:25 +0200 Subject: [PATCH 070/198] :arrow_up: language-python@0.39.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1350630cc..1e2e45202 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "language-perl": "0.28.0", "language-php": "0.29.0", "language-property-list": "0.8.0", - "language-python": "0.38.0", + "language-python": "0.39.0", "language-ruby": "0.57.0", "language-ruby-on-rails": "0.22.0", "language-sass": "0.40.1", From 1bfbf7f9d30c6722241639c15c4f74fc4aa949b7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 Aug 2015 10:00:38 -0700 Subject: [PATCH 071/198] Map line numbers in onerror handler w/ source-map-support --- package.json | 1 - spec/atom-reporter.coffee | 4 +--- src/atom.coffee | 8 ++------ 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 811814a0f..3d9061beb 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "clear-cut": "^2.0.1", "coffee-cash": "0.8.0", "coffee-script": "1.8.0", - "coffeestack": "^1.1.2", "color": "^0.7.3", "delegato": "^1", "emissary": "^1.3.3", diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index 1fdbd1385..5cd700c39 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -1,11 +1,9 @@ path = require 'path' _ = require 'underscore-plus' -{convertStackTrace} = require 'coffeestack' {View, $, $$} = require '../src/space-pen-extensions' grim = require 'grim' marked = require 'marked' -sourceMaps = {} formatStackTrace = (spec, message='', stackTrace) -> return stackTrace unless stackTrace @@ -15,8 +13,8 @@ formatStackTrace = (spec, message='', stackTrace) -> for line in stackTrace.split('\n') convertedLines.push(line) unless jasminePattern.test(line) break if firstJasmineLinePattern.test(line) + stackTrace = convertedLines.join('\n') - stackTrace = convertStackTrace(convertedLines.join('\n'), sourceMaps) lines = stackTrace.split('\n') # Remove first line of stack when it is the same as the error message diff --git a/src/atom.coffee b/src/atom.coffee index 7065a0d93..28058f8dd 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -9,7 +9,7 @@ _ = require 'underscore-plus' {deprecate, includeDeprecatedAPIs} = require 'grim' {CompositeDisposable, Emitter} = require 'event-kit' fs = require 'fs-plus' -{convertStackTrace, convertLine} = require 'coffeestack' +{mapSourcePosition} = require 'source-map-support' Model = require './model' {$} = require './space-pen-extensions' WindowEventHandler = require './window-event-handler' @@ -196,15 +196,11 @@ class Atom extends Model # # Call after this instance has been assigned to the `atom` global. initialize: -> - sourceMapCache = {} - window.onerror = => @lastUncaughtError = Array::slice.call(arguments) [message, url, line, column, originalError] = @lastUncaughtError - convertedLine = convertLine(url, line, column, sourceMapCache) - {line, column} = convertedLine if convertedLine? - originalError.stack = convertStackTrace(originalError.stack, sourceMapCache) if originalError + {line, column} = mapSourcePosition({source: url, line, column}) eventObject = {message, url, line, column, originalError} From 973c2a6a18f7e7229fa73d1b80456c69a13e63e4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 Aug 2015 11:22:32 -0700 Subject: [PATCH 072/198] Don't match newlines when finding source-map data --- src/compile-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index 0666e7635..61a703abe 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -82,7 +82,7 @@ function writeCachedJavascript (relativeCachePath, code) { fs.writeFileSync(cachePath, code, 'utf8') } -const InlineSourceMapRegExp = /\/\/[#@]\s*sourceMappingURL=([^'"]+)\s*$/g +const InlineSourceMapRegExp = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/g require('source-map-support').install({ handleUncaughtExceptions: false, From 070f4000f0039b8cc9fbbff2cfccaa8b028e6c81 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 Aug 2015 11:25:38 -0700 Subject: [PATCH 073/198] Replace 'use babel' -> '@babel' in comment directive --- build/lib/uses-babel.coffee | 2 +- spec/babel-spec.coffee | 2 +- spec/fixtures/babel/babel-comment.js | 2 +- src/babel.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/lib/uses-babel.coffee b/build/lib/uses-babel.coffee index 08f8721d2..dcd480d4e 100644 --- a/build/lib/uses-babel.coffee +++ b/build/lib/uses-babel.coffee @@ -3,7 +3,7 @@ fs = require 'fs' BABEL_PREFIXES = [ "'use babel'" '"use babel"' - '/** use babel */' + '/** @babel */' ] PREFIX_LENGTH = Math.max(BABEL_PREFIXES.map((prefix) -> prefix.length)...) diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index 421e4c48a..caaaed9f2 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -1,5 +1,5 @@ describe "Babel transpiler support", -> - describe 'when a .js file starts with /** use babel */;', -> + describe 'when a .js file starts with /** @babel */;', -> it "transpiles it using babel", -> transpiled = require('./fixtures/babel/babel-comment.js') expect(transpiled(3)).toBe 4 diff --git a/spec/fixtures/babel/babel-comment.js b/spec/fixtures/babel/babel-comment.js index 4fb454999..ee9a27373 100644 --- a/spec/fixtures/babel/babel-comment.js +++ b/spec/fixtures/babel/babel-comment.js @@ -1,3 +1,3 @@ -/** use babel */ +/** @babel */ module.exports = v => v + 1 diff --git a/src/babel.js b/src/babel.js index a80694587..738433ed8 100644 --- a/src/babel.js +++ b/src/babel.js @@ -10,7 +10,7 @@ let babelVersionDirectory = null const defaultOptions = require('../static/babelrc.json') exports.shouldCompile = function(sourceCode) { - return sourceCode.startsWith('/** use babel */') || + return sourceCode.startsWith('/** @babel */') || sourceCode.startsWith('"use babel"') || sourceCode.startsWith("'use babel'") } From 6ef15468eb9b60c833f34fb08f2c30f46015b9ef Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 20 Aug 2015 13:35:49 -0600 Subject: [PATCH 074/198] :arrow_up: grim to drop its custom CoffeeScript stack trace support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s interfering with our global stack trace support. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d9061beb..b3527e292 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "^3.0.0", - "grim": "1.4.1", + "grim": "1.4.2", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", "jquery": "^2.1.1", From 61ef1da81531839efb281278e438318ac2a61859 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 20 Aug 2015 16:07:37 -0600 Subject: [PATCH 075/198] :art: Signed-off-by: Max Brunsfeld --- src/compile-cache.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index 61a703abe..cea85cbe9 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -113,10 +113,10 @@ require('source-map-support').install({ let sourceMappingURL = lastMatch[1] let rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) - let sourceMapData = new Buffer(rawData, 'base64').toString() + let sourceMap = JSON.parse(new Buffer(rawData, 'base64').toString()) return { - map: JSON.parse(sourceMapData), + map: sourceMap, url: null } } From c0f3ca6dcdc56c45582c86249665b66f5c45784b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 20 Aug 2015 16:08:03 -0600 Subject: [PATCH 076/198] Fix inline source map regex Need to enable multi-line mode after all Signed-off-by: Max Brunsfeld --- src/compile-cache.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index cea85cbe9..821eac45e 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -82,7 +82,7 @@ function writeCachedJavascript (relativeCachePath, code) { fs.writeFileSync(cachePath, code, 'utf8') } -const InlineSourceMapRegExp = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/g +const INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg require('source-map-support').install({ handleUncaughtExceptions: false, @@ -103,8 +103,8 @@ require('source-map-support').install({ } let match, lastMatch - InlineSourceMapRegExp.lastIndex = 0 - while ((match = InlineSourceMapRegExp.exec(fileData))) { + INLINE_SOURCE_MAP_REGEXP.lastIndex = 0 + while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { lastMatch = match } if (lastMatch == null){ From 6f44fc4c7f7a0b85ce483b1b641493c4bdbc74e8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 20 Aug 2015 16:08:25 -0600 Subject: [PATCH 077/198] Generate sourceFiles in CoffeeScript source maps correctly --- src/coffee-script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coffee-script.js b/src/coffee-script.js index 9cd0a0777..0aa428ab8 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -26,6 +26,7 @@ exports.getCachePath = function(sourceCode) { exports.compile = function(sourceCode, filePath) { let output = CoffeeScript.compile(sourceCode, { filename: filePath, + sourceFiles: [filePath], sourceMap: true }) From f531684acf7ee03eda4bba64e3a7be71433c2dc9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 Aug 2015 17:59:29 -0700 Subject: [PATCH 078/198] Use compile-cache in browser process (for dev mode) --- package.json | 1 - src/browser/main.coffee | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5cf309976..b80369c35 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "babel-core": "^5.8.21", "bootstrap": "^3.3.4", "clear-cut": "^2.0.1", - "coffee-cash": "0.8.0", "coffee-script": "1.8.0", "color": "^0.7.3", "delegato": "^1", diff --git a/src/browser/main.coffee b/src/browser/main.coffee index 8d429f15f..fac1191a7 100644 --- a/src/browser/main.coffee +++ b/src/browser/main.coffee @@ -16,7 +16,7 @@ process.on 'uncaughtException', (error={}) -> start = -> setupAtomHome() - setupCoffeeCache() + setupCompileCache() if process.platform is 'win32' SquirrelUpdate = require './squirrel-update' @@ -77,14 +77,9 @@ setupAtomHome = -> atomHome = fs.realpathSync(atomHome) process.env.ATOM_HOME = atomHome -setupCoffeeCache = -> - CoffeeCache = require 'coffee-cash' - cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache') - # Use separate compile cache when sudo'ing as root to avoid permission issues - if process.env.USER is 'root' and process.env.SUDO_USER and process.env.SUDO_USER isnt process.env.USER - cacheDir = path.join(cacheDir, 'root') - CoffeeCache.setCacheDirectory(path.join(cacheDir, 'coffee')) - CoffeeCache.register() +setupCompileCache = -> + compileCache = require('../compile-cache') + compileCache.setAtomHomeDirectory(process.env.ATOM_HOME) parseCommandLine = -> version = app.getVersion() From 8c5eed28d20fee1d64ad647e2ca45dd2c4712ee3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 10:31:29 -0700 Subject: [PATCH 079/198] Don't use es6 features in compile-cache APM requires it directly, and it uses an older version of node --- src/babel.js | 32 ++++++++++++++-------- src/coffee-script.js | 10 +++---- src/compile-cache.js | 64 +++++++++++++++++++++++--------------------- src/typescript.js | 18 ++++++------- 4 files changed, 68 insertions(+), 56 deletions(-) diff --git a/src/babel.js b/src/babel.js index 738433ed8..e44d78996 100644 --- a/src/babel.js +++ b/src/babel.js @@ -1,23 +1,33 @@ 'use strict' -const _ = require('underscore-plus') -const crypto = require('crypto') -const path = require('path') +var _ = require('underscore-plus') +var crypto = require('crypto') +var path = require('path') +var defaultOptions = require('../static/babelrc.json') -let babel = null -let babelVersionDirectory = null +var babel = null +var babelVersionDirectory = null -const defaultOptions = require('../static/babelrc.json') +var PREFIXES = [ + '/** @babel */', + '"use babel"', + '\'use babel\'' +] + +var PREFIX_LENGTH = Math.max.apply(Math, PREFIXES.map(function (prefix) { + return prefix.length +})) exports.shouldCompile = function(sourceCode) { - return sourceCode.startsWith('/** @babel */') || - sourceCode.startsWith('"use babel"') || - sourceCode.startsWith("'use babel'") + var start = sourceCode.substr(0, PREFIX_LENGTH) + return PREFIXES.some(function (prefix) { + return start.indexOf(prefix) === 0 + }) } exports.getCachePath = function(sourceCode) { if (babelVersionDirectory == null) { - let babelVersion = require('babel-core/package.json').version + var babelVersion = require('babel-core/package.json').version babelVersionDirectory = path.join('js', 'babel', createVersionAndOptionsDigest(babelVersion, defaultOptions)) } @@ -35,7 +45,7 @@ exports.compile = function(sourceCode, filePath) { babel = require('babel-core') } - let options = _.defaults({filename: filePath}, defaultOptions) + var options = _.defaults({filename: filePath}, defaultOptions) return babel.transform(sourceCode, options).code } diff --git a/src/coffee-script.js b/src/coffee-script.js index 0aa428ab8..efdcbb72b 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -1,13 +1,13 @@ 'use strict' -const crypto = require('crypto') -const path = require('path') +var crypto = require('crypto') +var path = require('path') // The coffee-script compiler is required eagerly because: // 1. It is always used. // 2. It reassigns Error.prepareStackTrace, so we need to make sure that // the 'source-map-support' module is installed *after* it is loaded. -const CoffeeScript = require('coffee-script') +var CoffeeScript = require('coffee-script') exports.shouldCompile = function() { return true @@ -24,13 +24,13 @@ exports.getCachePath = function(sourceCode) { } exports.compile = function(sourceCode, filePath) { - let output = CoffeeScript.compile(sourceCode, { + var output = CoffeeScript.compile(sourceCode, { filename: filePath, sourceFiles: [filePath], sourceMap: true }) - let js = output.js + var js = output.js js += '\n' js += '//# sourceMappingURL=data:application/json;base64,' js += new Buffer(output.v3SourceMap).toString('base64') diff --git a/src/compile-cache.js b/src/compile-cache.js index 821eac45e..0f2554efb 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -1,31 +1,31 @@ 'use strict' -const path = require('path') -const CSON = require('season') -const fs = require('fs-plus') +var path = require('path') +var CSON = require('season') +var fs = require('fs-plus') +var _ = require('underscore-plus') -const COMPILERS = { +var COMPILERS = { '.js': require('./babel'), '.ts': require('./typescript'), '.coffee': require('./coffee-script') } -for (let extension in COMPILERS) { - let compiler = COMPILERS[extension] +var cacheDirectory = null + +_.each(COMPILERS, function (compiler, extension) { Object.defineProperty(require.extensions, extension, { enumerable: true, writable: false, value: function (module, filePath) { - let code = compileFileAtPath(compiler, filePath) + var code = compileFileAtPath(compiler, filePath, extension) return module._compile(code, filePath) } }) -} - -let cacheDirectory = null +}) exports.setAtomHomeDirectory = function (atomHome) { - let cacheDir = path.join(atomHome, 'compile-cache') + var cacheDir = path.join(atomHome, 'compile-cache') if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { cacheDir = path.join(cacheDirectory, 'root') } @@ -43,21 +43,23 @@ exports.getCacheDirectory = function () { exports.addPathToCache = function (filePath, atomHome) { this.setAtomHomeDirectory(atomHome) - extension = path.extname(filePath) - if (extension === '.cson') { - return CSON.readFileSync(filePath) - } + var extension = path.extname(filePath) - if (compiler = COMPILERS[extension]) { - return compileFileAtPath(compiler, filePath) + if (extension === '.cson') { + CSON.readFileSync(filePath) + } else { + var compiler = COMPILERS[extension] + if (compiler) { + compileFileAtPath(compiler, filePath, extension) + } } } -function compileFileAtPath (compiler, filePath) { - let sourceCode = fs.readFileSync(filePath, 'utf8') +function compileFileAtPath (compiler, filePath, extension) { + var sourceCode = fs.readFileSync(filePath, 'utf8') if (compiler.shouldCompile(sourceCode, filePath)) { - let cachePath = compiler.getCachePath(sourceCode, filePath) - let compiledCode = readCachedJavascript(cachePath) + var cachePath = compiler.getCachePath(sourceCode, filePath) + var compiledCode = readCachedJavascript(cachePath) if (compiledCode == null) { compiledCode = compiler.compile(sourceCode, filePath) writeCachedJavascript(cachePath, compiledCode) @@ -68,7 +70,7 @@ function compileFileAtPath (compiler, filePath) { } function readCachedJavascript (relativeCachePath) { - let cachePath = path.join(cacheDirectory, relativeCachePath) + var cachePath = path.join(cacheDirectory, relativeCachePath) if (fs.isFileSync(cachePath)) { try { return fs.readFileSync(cachePath, 'utf8') @@ -78,11 +80,11 @@ function readCachedJavascript (relativeCachePath) { } function writeCachedJavascript (relativeCachePath, code) { - let cachePath = path.join(cacheDirectory, relativeCachePath) + var cachePath = path.join(cacheDirectory, relativeCachePath) fs.writeFileSync(cachePath, code, 'utf8') } -const INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg +var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg require('source-map-support').install({ handleUncaughtExceptions: false, @@ -95,14 +97,14 @@ require('source-map-support').install({ return null } - let sourceCode = fs.readFileSync(filePath, 'utf8') - let compiler = COMPILERS[path.extname(filePath)] - let fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) + var sourceCode = fs.readFileSync(filePath, 'utf8') + var compiler = COMPILERS[path.extname(filePath)] + var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) if (fileData == null) { return null } - let match, lastMatch + var match, lastMatch INLINE_SOURCE_MAP_REGEXP.lastIndex = 0 while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { lastMatch = match @@ -111,9 +113,9 @@ require('source-map-support').install({ return null } - let sourceMappingURL = lastMatch[1] - let rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) - let sourceMap = JSON.parse(new Buffer(rawData, 'base64').toString()) + var sourceMappingURL = lastMatch[1] + var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) + var sourceMap = JSON.parse(new Buffer(rawData, 'base64').toString()) return { map: sourceMap, diff --git a/src/typescript.js b/src/typescript.js index c372c775b..967fdf18e 100644 --- a/src/typescript.js +++ b/src/typescript.js @@ -1,25 +1,25 @@ 'use strict' -const _ = require('underscore-plus') -const crypto = require('crypto') -const path = require('path') +var _ = require('underscore-plus') +var crypto = require('crypto') +var path = require('path') -let TypeScriptSimple = null -let typescriptVersionDir = null - -const defaultOptions = { +var defaultOptions = { target: 1, module: 'commonjs', sourceMap: true } +var TypeScriptSimple = null +var typescriptVersionDir = null + exports.shouldCompile = function() { return true } exports.getCachePath = function(sourceCode) { if (typescriptVersionDir == null) { - let version = require('typescript-simple/package.json').version + var version = require('typescript-simple/package.json').version typescriptVersionDir = path.join('ts', createVersionAndOptionsDigest(version, defaultOptions)) } @@ -37,7 +37,7 @@ exports.compile = function(sourceCode, filePath) { TypeScriptSimple = require('typescript-simple').TypeScriptSimple } - let options = _.defaults({filename: filePath}, defaultOptions) + var options = _.defaults({filename: filePath}, defaultOptions) return new TypeScriptSimple(options, false).compile(sourceCode, filePath) } From 5de1a26283d778180503b91c22d4a91ad5193fcd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 21 Aug 2015 11:35:31 -0600 Subject: [PATCH 080/198] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e2e45202..5680909ee 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "symbols-view": "0.101.0", "tabs": "0.82.0", "timecop": "0.31.0", - "tree-view": "0.183.0", + "tree-view": "0.184.0", "update-package-dependencies": "0.10.0", "welcome": "0.30.0", "whitespace": "0.30.0", From 408202729ec842029a68a730bf2dbff532379f20 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 11:19:11 -0700 Subject: [PATCH 081/198] Add sourceURL comment to all compiled js files --- src/compile-cache.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index 0f2554efb..c30f03105 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -61,7 +61,7 @@ function compileFileAtPath (compiler, filePath, extension) { var cachePath = compiler.getCachePath(sourceCode, filePath) var compiledCode = readCachedJavascript(cachePath) if (compiledCode == null) { - compiledCode = compiler.compile(sourceCode, filePath) + compiledCode = addSourceURL(compiler.compile(sourceCode, filePath), filePath) writeCachedJavascript(cachePath, compiledCode) } return compiledCode @@ -84,6 +84,12 @@ function writeCachedJavascript (relativeCachePath, code) { fs.writeFileSync(cachePath, code, 'utf8') } +function addSourceURL (jsCode, filePath) { + if (process.platform === 'win32') + filePath = '/' + path.resolve(filePath).replace(/\\/g, '/') + return jsCode + '\n' + '//# sourceURL=' + encodeURI(filePath) + '\n' +} + var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg require('source-map-support').install({ From bb69f1352dc00348c16a2f5f89e1d9c46a853722 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 11:50:54 -0700 Subject: [PATCH 082/198] :art: --- src/compile-cache.js | 6 +++--- src/typescript.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index c30f03105..ab9f8b663 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -99,7 +99,7 @@ require('source-map-support').install({ // source-map-support module, but we've overridden it to read the javascript // code from our cache directory. retrieveSourceMap: function (filePath) { - if (!fs.isFileSync(filePath)){ + if (!fs.isFileSync(filePath)) { return null } @@ -115,13 +115,13 @@ require('source-map-support').install({ while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { lastMatch = match } - if (lastMatch == null){ + if (lastMatch == null) { return null } var sourceMappingURL = lastMatch[1] var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) - var sourceMap = JSON.parse(new Buffer(rawData, 'base64').toString()) + var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) return { map: sourceMap, diff --git a/src/typescript.js b/src/typescript.js index 967fdf18e..60421ef9e 100644 --- a/src/typescript.js +++ b/src/typescript.js @@ -28,7 +28,7 @@ exports.getCachePath = function(sourceCode) { crypto .createHash('sha1') .update(sourceCode, 'utf8') - .digest('hex') + ".js" + .digest('hex') + '.js' ) } From 1f646761898876cd2bf8a707fb2b98a777188d0e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 12:14:10 -0700 Subject: [PATCH 083/198] :fire: Unnecessary joining and splitting --- spec/atom-reporter.coffee | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index 5cd700c39..5e52b4b9b 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -9,13 +9,10 @@ formatStackTrace = (spec, message='', stackTrace) -> jasminePattern = /^\s*at\s+.*\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/ firstJasmineLinePattern = /^\s*at [/\\].*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/ - convertedLines = [] + lines = [] for line in stackTrace.split('\n') - convertedLines.push(line) unless jasminePattern.test(line) + lines.push(line) unless jasminePattern.test(line) break if firstJasmineLinePattern.test(line) - stackTrace = convertedLines.join('\n') - - lines = stackTrace.split('\n') # Remove first line of stack when it is the same as the error message errorMatch = lines[0]?.match(/^Error: (.*)/) From b5b8591a512de6c3790305a52a3f7b2e550dd94b Mon Sep 17 00:00:00 2001 From: Jess Lin Date: Mon, 17 Aug 2015 22:52:56 -0700 Subject: [PATCH 084/198] Destroy a TextEditor's Gutters when it is destroyed --- spec/gutter-container-spec.coffee | 10 ++++++++++ src/gutter-container.coffee | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/gutter-container-spec.coffee b/spec/gutter-container-spec.coffee index 03c9ebc10..e38367835 100644 --- a/spec/gutter-container-spec.coffee +++ b/spec/gutter-container-spec.coffee @@ -50,3 +50,13 @@ describe 'GutterContainer', -> otherGutterContainer = new GutterContainer fakeOtherTextEditor gutter = new Gutter 'gutter-name', otherGutterContainer expect(gutterContainer.removeGutter.bind(null, gutter)).toThrow() + + describe '::destroy', -> + it 'clears its array of gutters and destroys custom gutters', -> + newGutter = gutterContainer.addGutter {'test-gutter', priority: 1} + newGutterSpy = jasmine.createSpy() + newGutter.onDidDestroy(newGutterSpy) + + gutterContainer.destroy() + expect(newGutterSpy).toHaveBeenCalled() + expect(gutterContainer.getGutters()).toEqual [] diff --git a/src/gutter-container.coffee b/src/gutter-container.coffee index 1be52d519..4fa7c61fb 100644 --- a/src/gutter-container.coffee +++ b/src/gutter-container.coffee @@ -13,7 +13,12 @@ class GutterContainer @emitter = new Emitter destroy: -> - @gutters = null + # Create a copy, because `Gutter::destroy` removes the gutter from + # GutterContainer's @gutters. + guttersToDestroy = @gutters.slice(0) + for gutter in guttersToDestroy + gutter.destroy() if gutter.name isnt 'line-number' + @gutters = [] @emitter.dispose() # Creates and returns a {Gutter}. From 73dded1e4246d00871f974e3ac2c6f4f945efd89 Mon Sep 17 00:00:00 2001 From: Lee Dohm Date: Fri, 21 Aug 2015 13:14:28 -0700 Subject: [PATCH 085/198] :memo: Fix a typo and make recommendation more clear --- src/workspace.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index 9f1f35e56..86e860fe2 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -779,9 +779,9 @@ class Workspace extends Model # Essential: Adds a panel item as a modal dialog. # # * `options` {Object} - # * `item` Your panel content. It can be DOM element, a jQuery element, or + # * `item` Your panel content. It can be a DOM element, a jQuery element, or # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # latter. See {ViewRegistry::addViewProvider} for more information. + # model option. See {ViewRegistry::addViewProvider} for more information. # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden # (default: true) # * `priority` (optional) {Number} Determines stacking order. Lower priority items are From b533aff8f497224bbf08583a73733dba5002099d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 13:35:52 -0700 Subject: [PATCH 086/198] :racehorse: Minimize dependencies loaded before the module-cache --- src/babel.js | 6 ++++-- src/compile-cache.js | 9 +++++---- static/index.js | 5 +++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/babel.js b/src/babel.js index e44d78996..8223cb634 100644 --- a/src/babel.js +++ b/src/babel.js @@ -1,6 +1,5 @@ 'use strict' -var _ = require('underscore-plus') var crypto = require('crypto') var path = require('path') var defaultOptions = require('../static/babelrc.json') @@ -45,7 +44,10 @@ exports.compile = function(sourceCode, filePath) { babel = require('babel-core') } - var options = _.defaults({filename: filePath}, defaultOptions) + var options = {filename: filePath} + for (var key in defaultOptions) { + options[key] = defaultOptions[key] + } return babel.transform(sourceCode, options).code } diff --git a/src/compile-cache.js b/src/compile-cache.js index ab9f8b663..366c58800 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -1,9 +1,8 @@ 'use strict' var path = require('path') -var CSON = require('season') var fs = require('fs-plus') -var _ = require('underscore-plus') +var CSON = null var COMPILERS = { '.js': require('./babel'), @@ -13,7 +12,9 @@ var COMPILERS = { var cacheDirectory = null -_.each(COMPILERS, function (compiler, extension) { +Object.keys(COMPILERS).forEach(function (extension) { + var compiler = COMPILERS[extension] + Object.defineProperty(require.extensions, extension, { enumerable: true, writable: false, @@ -34,7 +35,6 @@ exports.setAtomHomeDirectory = function (atomHome) { exports.setCacheDirectory = function (directory) { cacheDirectory = directory - CSON.setCacheDir(path.join(cacheDirectory, 'cson')); } exports.getCacheDirectory = function () { @@ -46,6 +46,7 @@ exports.addPathToCache = function (filePath, atomHome) { var extension = path.extname(filePath) if (extension === '.cson') { + CSON = CSON || require('season') CSON.readFileSync(filePath) } else { var compiler = COMPILERS[extension] diff --git a/static/index.js b/static/index.js index 238cb385e..4769837cc 100644 --- a/static/index.js +++ b/static/index.js @@ -78,6 +78,7 @@ var setupWindow = function(loadSettings) { }); setupVmCompatibility(); + setupCsonCache(compileCache.getCacheDirectory()) require(loadSettings.bootstrapScript); require('ipc').sendChannel('window-command', 'window:loaded'); @@ -101,6 +102,10 @@ var setupAtomHome = function() { } } +var setupCsonCache = function(cacheDir) { + require('season').setCacheDir(path.join(cacheDir, 'cson')); +} + var setupVmCompatibility = function() { var vm = require('vm'); if (!vm.Script.createContext) { From f8397661d17148c8183bd5397d8874715c39b2a5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 13:45:02 -0700 Subject: [PATCH 087/198] Defer coffee-script require, prevent it from reassigning prepareStackTrace --- src/coffee-script.js | 11 +++++------ src/compile-cache.js | 9 +++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/coffee-script.js b/src/coffee-script.js index efdcbb72b..b40a3d9d6 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -2,12 +2,7 @@ var crypto = require('crypto') var path = require('path') - -// The coffee-script compiler is required eagerly because: -// 1. It is always used. -// 2. It reassigns Error.prepareStackTrace, so we need to make sure that -// the 'source-map-support' module is installed *after* it is loaded. -var CoffeeScript = require('coffee-script') +var CoffeeScript = null exports.shouldCompile = function() { return true @@ -24,6 +19,10 @@ exports.getCachePath = function(sourceCode) { } exports.compile = function(sourceCode, filePath) { + if (!CoffeeScript) { + CoffeeScript = require('coffee-script') + } + var output = CoffeeScript.compile(sourceCode, { filename: filePath, sourceFiles: [filePath], diff --git a/src/compile-cache.js b/src/compile-cache.js index 366c58800..63ba008ae 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -130,3 +130,12 @@ require('source-map-support').install({ } } }) + +// This prevents coffee-script from reassigning `Error.prepareStackTrace` when +// it is loaded. +Object.defineProperty(Error, 'prepareStackTrace', { + value: Error.prepareStackTrace, + configurable: false, + enumerable: true, + writable: false +}) From a6fa098a8cf20afc434e84146b0dfbc6ec37bb99 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 14:47:32 -0700 Subject: [PATCH 088/198] Add CompileCache spec --- spec/compile-cache-spec.coffee | 71 ++++++++++++++++++++++++++++++++++ src/compile-cache.js | 52 ++++++++++++++++++------- 2 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 spec/compile-cache-spec.coffee diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee new file mode 100644 index 000000000..94910bb58 --- /dev/null +++ b/spec/compile-cache-spec.coffee @@ -0,0 +1,71 @@ +path = require 'path' +temp = require('temp').track() +Babel = require 'babel-core' +CoffeeScript = require 'coffee-script' +{TypeScriptSimple} = require 'typescript-simple' +CSON = require 'season' +CSONParser = require 'season/node_modules/cson-parser' +CompileCache = require '../src/compile-cache' + +describe 'CompileCache', -> + [atomHome, fixtures] = [] + + beforeEach -> + fixtures = atom.project.getPaths()[0] + atomHome = temp.mkdirSync('fake-atom-home') + + CSON.setCacheDir(null) + CompileCache.resetCacheStats() + + spyOn(Babel, 'transform').andReturn {code: 'the-babel-code'} + spyOn(CoffeeScript, 'compile').andReturn {js: 'the-coffee-code', v3SourceMap: {}} + spyOn(TypeScriptSimple::, 'compile').andReturn 'the-typescript-code' + spyOn(CSONParser, 'parse').andReturn {the: 'cson-data'} + + afterEach -> + CSON.setCacheDir(CompileCache.getCacheDirectory()) + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) + + describe 'addPathToCache(filePath, atomHome)', -> + describe 'when the given file is plain javascript', -> + it 'does not compile or cache the file', -> + CompileCache.addPathToCache(path.join(fixtures, 'sample.js'), atomHome) + expect(CompileCache.getCacheStats()['.js']).toEqual {hits: 0, misses: 0} + + describe 'when the given file uses babel', -> + it 'compiles the file with babel and caches it', -> + CompileCache.addPathToCache(path.join(fixtures, 'babel', 'babel-comment.js'), atomHome) + expect(CompileCache.getCacheStats()['.js']).toEqual {hits: 0, misses: 1} + expect(Babel.transform.callCount).toBe 1 + + CompileCache.addPathToCache(path.join(fixtures, 'babel', 'babel-comment.js'), atomHome) + expect(CompileCache.getCacheStats()['.js']).toEqual {hits: 1, misses: 1} + expect(Babel.transform.callCount).toBe 1 + + describe 'when the given file is coffee-script', -> + it 'compiles the file with coffee-script and caches it', -> + CompileCache.addPathToCache(path.join(fixtures, 'coffee.coffee'), atomHome) + expect(CompileCache.getCacheStats()['.coffee']).toEqual {hits: 0, misses: 1} + expect(CoffeeScript.compile.callCount).toBe 1 + + CompileCache.addPathToCache(path.join(fixtures, 'coffee.coffee'), atomHome) + expect(CompileCache.getCacheStats()['.coffee']).toEqual {hits: 1, misses: 1} + expect(CoffeeScript.compile.callCount).toBe 1 + + describe 'when the given file is typescript', -> + it 'compiles the file with typescript and caches it', -> + CompileCache.addPathToCache(path.join(fixtures, 'typescript', 'valid.ts'), atomHome) + expect(CompileCache.getCacheStats()['.ts']).toEqual {hits: 0, misses: 1} + expect(TypeScriptSimple::compile.callCount).toBe 1 + + CompileCache.addPathToCache(path.join(fixtures, 'typescript', 'valid.ts'), atomHome) + expect(CompileCache.getCacheStats()['.ts']).toEqual {hits: 1, misses: 1} + expect(TypeScriptSimple::compile.callCount).toBe 1 + + describe 'when the given file is CSON', -> + it 'compiles the file to JSON and caches it', -> + CompileCache.addPathToCache(path.join(fixtures, 'cson.cson'), atomHome) + expect(CSONParser.parse.callCount).toBe 1 + + CompileCache.addPathToCache(path.join(fixtures, 'cson.cson'), atomHome) + expect(CSONParser.parse.callCount).toBe 1 diff --git a/src/compile-cache.js b/src/compile-cache.js index 63ba008ae..6c8c6cb88 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -10,21 +10,9 @@ var COMPILERS = { '.coffee': require('./coffee-script') } +var cacheStats = {} var cacheDirectory = null -Object.keys(COMPILERS).forEach(function (extension) { - var compiler = COMPILERS[extension] - - Object.defineProperty(require.extensions, extension, { - enumerable: true, - writable: false, - value: function (module, filePath) { - var code = compileFileAtPath(compiler, filePath, extension) - return module._compile(code, filePath) - } - }) -}) - exports.setAtomHomeDirectory = function (atomHome) { var cacheDir = path.join(atomHome, 'compile-cache') if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { @@ -46,7 +34,10 @@ exports.addPathToCache = function (filePath, atomHome) { var extension = path.extname(filePath) if (extension === '.cson') { - CSON = CSON || require('season') + if (!CSON) { + CSON = require('season') + CSON.setCacheDir(this.getCacheDirectory()) + } CSON.readFileSync(filePath) } else { var compiler = COMPILERS[extension] @@ -56,12 +47,28 @@ exports.addPathToCache = function (filePath, atomHome) { } } +exports.getCacheStats = function () { + return cacheStats +} + +exports.resetCacheStats = function () { + Object.keys(COMPILERS).forEach(function (extension) { + cacheStats[extension] = { + hits: 0, + misses: 0 + } + }) +} + function compileFileAtPath (compiler, filePath, extension) { var sourceCode = fs.readFileSync(filePath, 'utf8') if (compiler.shouldCompile(sourceCode, filePath)) { var cachePath = compiler.getCachePath(sourceCode, filePath) var compiledCode = readCachedJavascript(cachePath) - if (compiledCode == null) { + if (compiledCode != null) { + cacheStats[extension].hits++ + } else { + cacheStats[extension].misses++ compiledCode = addSourceURL(compiler.compile(sourceCode, filePath), filePath) writeCachedJavascript(cachePath, compiledCode) } @@ -139,3 +146,18 @@ Object.defineProperty(Error, 'prepareStackTrace', { enumerable: true, writable: false }) + +Object.keys(COMPILERS).forEach(function (extension) { + var compiler = COMPILERS[extension] + + Object.defineProperty(require.extensions, extension, { + enumerable: true, + writable: false, + value: function (module, filePath) { + var code = compileFileAtPath(compiler, filePath, extension) + return module._compile(code, filePath) + } + }) +}) + +exports.resetCacheStats() From a18b7e2fa9cb7f6c675d6dab1fc0e9303bb17764 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 14:57:39 -0700 Subject: [PATCH 089/198] Don't make prepareStackTrace unwritable This breaks other code (such as Grim) that may want to temporarily use a different prepareStackTrace function. --- src/coffee-script.js | 6 ++++++ src/compile-cache.js | 9 --------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/coffee-script.js b/src/coffee-script.js index b40a3d9d6..de4deb0f1 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -20,7 +20,13 @@ exports.getCachePath = function(sourceCode) { exports.compile = function(sourceCode, filePath) { if (!CoffeeScript) { + var previousPrepareStackTrace = Error.prepareStackTrace CoffeeScript = require('coffee-script') + + // When it loads, coffee-script reassigns Error.prepareStackTrace. We have + // already reassigned it via the 'source-map-support' module, so we need + // to set it back. + Error.prepareStackTrace = previousPrepareStackTrace } var output = CoffeeScript.compile(sourceCode, { diff --git a/src/compile-cache.js b/src/compile-cache.js index 6c8c6cb88..6a261b70d 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -138,15 +138,6 @@ require('source-map-support').install({ } }) -// This prevents coffee-script from reassigning `Error.prepareStackTrace` when -// it is loaded. -Object.defineProperty(Error, 'prepareStackTrace', { - value: Error.prepareStackTrace, - configurable: false, - enumerable: true, - writable: false -}) - Object.keys(COMPILERS).forEach(function (extension) { var compiler = COMPILERS[extension] From f3778148a065c713b98738d42cd7acb49c0b8ae0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 15:37:36 -0700 Subject: [PATCH 090/198] Catch errors when retrieving source maps --- src/compile-cache.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index 6a261b70d..ae51d4980 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -111,9 +111,22 @@ require('source-map-support').install({ return null } - var sourceCode = fs.readFileSync(filePath, 'utf8') + try { + var sourceCode = fs.readFileSync(filePath, 'utf8') + } catch (error) { + console.warn("Error reading source file", error.stack) + return null + } + var compiler = COMPILERS[path.extname(filePath)] - var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) + + try { + var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) + } catch (error) { + console.warn("Error reading compiled file", error.stack) + return null + } + if (fileData == null) { return null } @@ -129,7 +142,13 @@ require('source-map-support').install({ var sourceMappingURL = lastMatch[1] var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) - var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) + + try { + var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) + } catch (error) { + console.warn("Error parsing source map", error.stack) + return null + } return { map: sourceMap, From ca4922bb70c20730e4446aed7e3af00a133d0a61 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 16:00:39 -0700 Subject: [PATCH 091/198] :fire: File extension --- build/Gruntfile.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 860616745..e4fbb39c6 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -3,7 +3,7 @@ path = require 'path' os = require 'os' glob = require 'glob' usesBabel = require './lib/uses-babel' -babelOptions = require('../static/babelrc.json') +babelOptions = require '../static/babelrc' # Add support for obselete APIs of vm module so we can make some third-party # modules work under node v0.11.x. From a4b9b9c6cd27e1403bd1ea4b82bd02f97031fc6a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 16:56:32 -0700 Subject: [PATCH 092/198] :shirt: Use standard style for js code --- build/Gruntfile.coffee | 9 +- build/package.json | 1 + src/babel.js | 8 +- src/coffee-script.js | 10 +- src/compile-cache.js | 9 +- src/typescript.js | 6 +- static/index.js | 368 ++++++++++++++++++++--------------------- 7 files changed, 209 insertions(+), 202 deletions(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index e4fbb39c6..d89a86531 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -21,6 +21,7 @@ module.exports = (grunt) -> grunt.loadNpmTasks('grunt-babel') grunt.loadNpmTasks('grunt-coffeelint') grunt.loadNpmTasks('grunt-lesslint') + grunt.loadNpmTasks('grunt-standard') grunt.loadNpmTasks('grunt-cson') grunt.loadNpmTasks('grunt-contrib-csslint') grunt.loadNpmTasks('grunt-contrib-coffee') @@ -192,6 +193,12 @@ module.exports = (grunt) -> 'spec/*.coffee' ] + standard: + src: [ + 'src/**/*.js' + 'static/*.js' + ] + csslint: options: 'adjoining-classes': false @@ -248,7 +255,7 @@ module.exports = (grunt) -> failOnError: false grunt.registerTask('compile', ['babel', 'coffee', 'prebuild-less', 'cson', 'peg']) - grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint']) + grunt.registerTask('lint', ['standard', 'coffeelint', 'csslint', 'lesslint']) grunt.registerTask('test', ['shell:kill-atom', 'run-specs']) ciTasks = ['output-disk-space', 'download-atom-shell', 'download-atom-shell-chromedriver', 'build'] diff --git a/build/package.json b/build/package.json index 07e9fa7ad..251cc1ba6 100644 --- a/build/package.json +++ b/build/package.json @@ -26,6 +26,7 @@ "grunt-lesslint": "0.17.0", "grunt-peg": "~1.1.0", "grunt-shell": "~0.3.1", + "grunt-standard": "^1.0.2", "harmony-collections": "~0.3.8", "legal-eagle": "~0.10.0", "minidump": "~0.9", diff --git a/src/babel.js b/src/babel.js index 8223cb634..f53dbc758 100644 --- a/src/babel.js +++ b/src/babel.js @@ -17,14 +17,14 @@ var PREFIX_LENGTH = Math.max.apply(Math, PREFIXES.map(function (prefix) { return prefix.length })) -exports.shouldCompile = function(sourceCode) { +exports.shouldCompile = function (sourceCode) { var start = sourceCode.substr(0, PREFIX_LENGTH) return PREFIXES.some(function (prefix) { return start.indexOf(prefix) === 0 }) } -exports.getCachePath = function(sourceCode) { +exports.getCachePath = function (sourceCode) { if (babelVersionDirectory == null) { var babelVersion = require('babel-core/package.json').version babelVersionDirectory = path.join('js', 'babel', createVersionAndOptionsDigest(babelVersion, defaultOptions)) @@ -35,11 +35,11 @@ exports.getCachePath = function(sourceCode) { crypto .createHash('sha1') .update(sourceCode, 'utf8') - .digest('hex') + ".js" + .digest('hex') + '.js' ) } -exports.compile = function(sourceCode, filePath) { +exports.compile = function (sourceCode, filePath) { if (!babel) { babel = require('babel-core') } diff --git a/src/coffee-script.js b/src/coffee-script.js index de4deb0f1..90f23bfa5 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -4,21 +4,21 @@ var crypto = require('crypto') var path = require('path') var CoffeeScript = null -exports.shouldCompile = function() { +exports.shouldCompile = function () { return true } -exports.getCachePath = function(sourceCode) { +exports.getCachePath = function (sourceCode) { return path.join( - "coffee", + 'coffee', crypto .createHash('sha1') .update(sourceCode, 'utf8') - .digest('hex') + ".js" + .digest('hex') + '.js' ) } -exports.compile = function(sourceCode, filePath) { +exports.compile = function (sourceCode, filePath) { if (!CoffeeScript) { var previousPrepareStackTrace = Error.prepareStackTrace CoffeeScript = require('coffee-script') diff --git a/src/compile-cache.js b/src/compile-cache.js index ae51d4980..9e4d14e15 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -93,8 +93,9 @@ function writeCachedJavascript (relativeCachePath, code) { } function addSourceURL (jsCode, filePath) { - if (process.platform === 'win32') + if (process.platform === 'win32') { filePath = '/' + path.resolve(filePath).replace(/\\/g, '/') + } return jsCode + '\n' + '//# sourceURL=' + encodeURI(filePath) + '\n' } @@ -114,7 +115,7 @@ require('source-map-support').install({ try { var sourceCode = fs.readFileSync(filePath, 'utf8') } catch (error) { - console.warn("Error reading source file", error.stack) + console.warn('Error reading source file', error.stack) return null } @@ -123,7 +124,7 @@ require('source-map-support').install({ try { var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) } catch (error) { - console.warn("Error reading compiled file", error.stack) + console.warn('Error reading compiled file', error.stack) return null } @@ -146,7 +147,7 @@ require('source-map-support').install({ try { var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) } catch (error) { - console.warn("Error parsing source map", error.stack) + console.warn('Error parsing source map', error.stack) return null } diff --git a/src/typescript.js b/src/typescript.js index 60421ef9e..c942f542a 100644 --- a/src/typescript.js +++ b/src/typescript.js @@ -13,11 +13,11 @@ var defaultOptions = { var TypeScriptSimple = null var typescriptVersionDir = null -exports.shouldCompile = function() { +exports.shouldCompile = function () { return true } -exports.getCachePath = function(sourceCode) { +exports.getCachePath = function (sourceCode) { if (typescriptVersionDir == null) { var version = require('typescript-simple/package.json').version typescriptVersionDir = path.join('ts', createVersionAndOptionsDigest(version, defaultOptions)) @@ -32,7 +32,7 @@ exports.getCachePath = function(sourceCode) { ) } -exports.compile = function(sourceCode, filePath) { +exports.compile = function (sourceCode, filePath) { if (!TypeScriptSimple) { TypeScriptSimple = require('typescript-simple').TypeScriptSimple } diff --git a/static/index.js b/static/index.js index 4769837cc..914290321 100644 --- a/static/index.js +++ b/static/index.js @@ -1,199 +1,197 @@ -(function() { +(function () { + var fs = require('fs') + var path = require('path') -var fs = require('fs'); -var path = require('path'); + var loadSettings = null + var loadSettingsError = null -var loadSettings = null; -var loadSettingsError = null; - -window.onload = function() { - try { - var startTime = Date.now(); - - process.on('unhandledRejection', function(error, promise) { - console.error('Unhandled promise rejection %o with error: %o', promise, error); - }); - - // Ensure ATOM_HOME is always set before anything else is required - setupAtomHome(); - - // Normalize to make sure drive letter case is consistent on Windows - process.resourcesPath = path.normalize(process.resourcesPath); - - if (loadSettingsError) { - throw loadSettingsError; - } - - var devMode = loadSettings.devMode || !loadSettings.resourcePath.startsWith(process.resourcesPath + path.sep); - - if (devMode) { - setupDeprecatedPackages(); - } - - if (loadSettings.profileStartup) { - profileStartup(loadSettings, Date.now() - startTime); - } else { - setupWindow(loadSettings); - setLoadTime(Date.now() - startTime); - } - } catch (error) { - handleSetupError(error); - } -} - -var setLoadTime = function(loadTime) { - if (global.atom) { - global.atom.loadTime = loadTime; - console.log('Window load time: ' + global.atom.getWindowLoadTime() + 'ms'); - } -} - -var handleSetupError = function(error) { - var currentWindow = require('remote').getCurrentWindow(); - currentWindow.setSize(800, 600); - currentWindow.center(); - currentWindow.show(); - currentWindow.openDevTools(); - console.error(error.stack || error); -} - -var setupWindow = function(loadSettings) { - var compileCache = require('../src/compile-cache') - compileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - - ModuleCache = require('../src/module-cache'); - ModuleCache.register(loadSettings); - ModuleCache.add(loadSettings.resourcePath); - - // Only include deprecated APIs when running core spec - require('grim').includeDeprecatedAPIs = isRunningCoreSpecs(loadSettings); - - // Start the crash reporter before anything else. - require('crash-reporter').start({ - productName: 'Atom', - companyName: 'GitHub', - // By explicitly passing the app version here, we could save the call - // of "require('remote').require('app').getVersion()". - extra: {_version: loadSettings.appVersion} - }); - - setupVmCompatibility(); - setupCsonCache(compileCache.getCacheDirectory()) - - require(loadSettings.bootstrapScript); - require('ipc').sendChannel('window-command', 'window:loaded'); -} - -var setupAtomHome = function() { - if (!process.env.ATOM_HOME) { - var home; - if (process.platform === 'win32') { - home = process.env.USERPROFILE; - } else { - home = process.env.HOME; - } - var atomHome = path.join(home, '.atom'); - try { - atomHome = fs.realpathSync(atomHome); - } catch (error) { - // Ignore since the path might just not exist yet. - } - process.env.ATOM_HOME = atomHome; - } -} - -var setupCsonCache = function(cacheDir) { - require('season').setCacheDir(path.join(cacheDir, 'cson')); -} - -var setupVmCompatibility = function() { - var vm = require('vm'); - if (!vm.Script.createContext) { - vm.Script.createContext = vm.createContext; - } -} - -var setupDeprecatedPackages = function() { - var metadata = require('../package.json'); - if (!metadata._deprecatedPackages) { - try { - metadata._deprecatedPackages = require('../build/deprecated-packages.json'); - } catch(requireError) { - console.error('Failed to setup deprecated packages list', requireError.stack); - } - } -} - -var profileStartup = function(loadSettings, initialTime) { - var profile = function() { - console.profile('startup'); + window.onload = function () { try { var startTime = Date.now() - setupWindow(loadSettings); - setLoadTime(Date.now() - startTime + initialTime); + + process.on('unhandledRejection', function (error, promise) { + console.error('Unhandled promise rejection %o with error: %o', promise, error) + }) + + // Ensure ATOM_HOME is always set before anything else is required + setupAtomHome() + + // Normalize to make sure drive letter case is consistent on Windows + process.resourcesPath = path.normalize(process.resourcesPath) + + if (loadSettingsError) { + throw loadSettingsError + } + + var devMode = loadSettings.devMode || !loadSettings.resourcePath.startsWith(process.resourcesPath + path.sep) + + if (devMode) { + setupDeprecatedPackages() + } + + if (loadSettings.profileStartup) { + profileStartup(loadSettings, Date.now() - startTime) + } else { + setupWindow(loadSettings) + setLoadTime(Date.now() - startTime) + } } catch (error) { - handleSetupError(error); - } finally { - console.profileEnd('startup'); - console.log("Switch to the Profiles tab to view the created startup profile") + handleSetupError(error) } - }; - - var currentWindow = require('remote').getCurrentWindow(); - if (currentWindow.devToolsWebContents) { - profile(); - } else { - currentWindow.openDevTools(); - currentWindow.once('devtools-opened', function() { - setTimeout(profile, 100); - }); - } -} - -var parseLoadSettings = function() { - var rawLoadSettings = decodeURIComponent(location.hash.substr(1)); - try { - loadSettings = JSON.parse(rawLoadSettings); - } catch (error) { - console.error("Failed to parse load settings: " + rawLoadSettings); - loadSettingsError = error; - } -} - -var setupWindowBackground = function() { - if (loadSettings && loadSettings.isSpec) { - return; } - var backgroundColor = window.localStorage.getItem('atom:window-background-color'); - if (!backgroundColor) { - return; + function setLoadTime (loadTime) { + if (global.atom) { + global.atom.loadTime = loadTime + console.log('Window load time: ' + global.atom.getWindowLoadTime() + 'ms') + } } - var backgroundStylesheet = document.createElement('style'); - backgroundStylesheet.type = 'text/css'; - backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + '; }'; - document.head.appendChild(backgroundStylesheet); + function handleSetupError (error) { + var currentWindow = require('remote').getCurrentWindow() + currentWindow.setSize(800, 600) + currentWindow.center() + currentWindow.show() + currentWindow.openDevTools() + console.error(error.stack || error) + } - // Remove once the page loads - window.addEventListener("load", function loadWindow() { - window.removeEventListener("load", loadWindow, false); - setTimeout(function() { - backgroundStylesheet.remove(); - backgroundStylesheet = null; - }, 1000); - }, false); -} + function setupWindow (loadSettings) { + var CompileCache = require('../src/compile-cache') + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) -var isRunningCoreSpecs = function(loadSettings) { - return !!(loadSettings && - loadSettings.isSpec && - loadSettings.specDirectory && - loadSettings.resourcePath && - path.dirname(loadSettings.specDirectory) === loadSettings.resourcePath); -} + var ModuleCache = require('../src/module-cache') + ModuleCache.register(loadSettings) + ModuleCache.add(loadSettings.resourcePath) -parseLoadSettings(); -setupWindowBackground(); + // Only include deprecated APIs when running core spec + require('grim').includeDeprecatedAPIs = isRunningCoreSpecs(loadSettings) -})(); + // Start the crash reporter before anything else. + require('crash-reporter').start({ + productName: 'Atom', + companyName: 'GitHub', + // By explicitly passing the app version here, we could save the call + // of "require('remote').require('app').getVersion()". + extra: {_version: loadSettings.appVersion} + }) + + setupVmCompatibility() + setupCsonCache(CompileCache.getCacheDirectory()) + + require(loadSettings.bootstrapScript) + require('ipc').sendChannel('window-command', 'window:loaded') + } + + function setupAtomHome () { + if (!process.env.ATOM_HOME) { + var home + if (process.platform === 'win32') { + home = process.env.USERPROFILE + } else { + home = process.env.HOME + } + var atomHome = path.join(home, '.atom') + try { + atomHome = fs.realpathSync(atomHome) + } catch (error) { + // Ignore since the path might just not exist yet. + } + process.env.ATOM_HOME = atomHome + } + } + + function setupCsonCache (cacheDir) { + require('season').setCacheDir(path.join(cacheDir, 'cson')) + } + + function setupVmCompatibility () { + var vm = require('vm') + if (!vm.Script.createContext) { + vm.Script.createContext = vm.createContext + } + } + + function setupDeprecatedPackages () { + var metadata = require('../package.json') + if (!metadata._deprecatedPackages) { + try { + metadata._deprecatedPackages = require('../build/deprecated-packages.json') + } catch(requireError) { + console.error('Failed to setup deprecated packages list', requireError.stack) + } + } + } + + function profileStartup (loadSettings, initialTime) { + function profile () { + console.profile('startup') + try { + var startTime = Date.now() + setupWindow(loadSettings) + setLoadTime(Date.now() - startTime + initialTime) + } catch (error) { + handleSetupError(error) + } finally { + console.profileEnd('startup') + console.log('Switch to the Profiles tab to view the created startup profile') + } + } + + var currentWindow = require('remote').getCurrentWindow() + if (currentWindow.devToolsWebContents) { + profile() + } else { + currentWindow.openDevTools() + currentWindow.once('devtools-opened', function () { + setTimeout(profile, 100) + }) + } + } + + function parseLoadSettings () { + var rawLoadSettings = decodeURIComponent(window.location.hash.substr(1)) + try { + loadSettings = JSON.parse(rawLoadSettings) + } catch (error) { + console.error('Failed to parse load settings: ' + rawLoadSettings) + loadSettingsError = error + } + } + + function setupWindowBackground () { + if (loadSettings && loadSettings.isSpec) { + return + } + + var backgroundColor = window.localStorage.getItem('atom:window-background-color') + if (!backgroundColor) { + return + } + + var backgroundStylesheet = document.createElement('style') + backgroundStylesheet.type = 'text/css' + backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + '; }' + document.head.appendChild(backgroundStylesheet) + + // Remove once the page loads + window.addEventListener('load', function loadWindow () { + window.removeEventListener('load', loadWindow, false) + setTimeout(function () { + backgroundStylesheet.remove() + backgroundStylesheet = null + }, 1000) + }, false) + } + + function isRunningCoreSpecs (loadSettings) { + return !!(loadSettings && + loadSettings.isSpec && + loadSettings.specDirectory && + loadSettings.resourcePath && + path.dirname(loadSettings.specDirectory) === loadSettings.resourcePath) + } + + parseLoadSettings() + setupWindowBackground() +})() From 0dd19f3133479ad5405cffd19d0574c0f3dcfa1a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 16:59:53 -0700 Subject: [PATCH 093/198] Remove 6to5 fixtures --- spec/fixtures/babel/6to5-double-quotes.js | 3 --- spec/fixtures/babel/6to5-single-quotes.js | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 spec/fixtures/babel/6to5-double-quotes.js delete mode 100644 spec/fixtures/babel/6to5-single-quotes.js diff --git a/spec/fixtures/babel/6to5-double-quotes.js b/spec/fixtures/babel/6to5-double-quotes.js deleted file mode 100644 index 254912bc0..000000000 --- a/spec/fixtures/babel/6to5-double-quotes.js +++ /dev/null @@ -1,3 +0,0 @@ -"use 6to5"; - -module.exports = v => v + 1 diff --git a/spec/fixtures/babel/6to5-single-quotes.js b/spec/fixtures/babel/6to5-single-quotes.js deleted file mode 100644 index 16e382ec2..000000000 --- a/spec/fixtures/babel/6to5-single-quotes.js +++ /dev/null @@ -1,3 +0,0 @@ -'use 6to5'; - -module.exports = v => v + 1 From 3a66096ea43aa50115917be3c4af9927862113d0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Aug 2015 17:25:25 -0700 Subject: [PATCH 094/198] Remove harmony-collections shim in Gruntfile eslint cannot load if this shim is present. --- build/Gruntfile.coffee | 4 ---- build/package.json | 1 - 2 files changed, 5 deletions(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index d89a86531..d21e8fa4a 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -13,10 +13,6 @@ _ = require 'underscore-plus' packageJson = require '../package.json' -# Shim harmony collections in case grunt was invoked without harmony -# collections enabled -_.extend(global, require('harmony-collections')) unless global.WeakMap? - module.exports = (grunt) -> grunt.loadNpmTasks('grunt-babel') grunt.loadNpmTasks('grunt-coffeelint') diff --git a/build/package.json b/build/package.json index 251cc1ba6..bd0c9a474 100644 --- a/build/package.json +++ b/build/package.json @@ -27,7 +27,6 @@ "grunt-peg": "~1.1.0", "grunt-shell": "~0.3.1", "grunt-standard": "^1.0.2", - "harmony-collections": "~0.3.8", "legal-eagle": "~0.10.0", "minidump": "~0.9", "npm": "2.13.3", From 4560914273127105eac7ce2d46bcb8903b972dae Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 22 Aug 2015 08:47:16 +0200 Subject: [PATCH 095/198] :arrow_up: settings-view@0.215.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b947df59f..f2b24f37a 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "open-on-github": "0.38.0", "package-generator": "0.40.0", "release-notes": "0.53.0", - "settings-view": "0.214.0", + "settings-view": "0.215.0", "snippets": "0.95.0", "spell-check": "0.59.0", "status-bar": "0.78.0", From f2c9688b435b9337698606c2fd3f0b45f8873a93 Mon Sep 17 00:00:00 2001 From: Jeremy Ebneyamin Date: Sat, 22 Aug 2015 18:42:16 -0700 Subject: [PATCH 096/198] Fix config.get for schemas containing objects The config.get method would not return the default values for the properties in objects. Now with _.deepExtends, the default values are the base while any new values overwrite the defaults. This way all default values appear, including those nested in an object. This fixes atom/settings-view#386 and fixes atom/settings-view#518. --- src/config.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 00ebabf58..d4fe1e753 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -864,8 +864,8 @@ class Config defaultValue = _.valueForKeyPath(@defaultSettings, keyPath) if value? - value = @deepClone(value) - _.defaults(value, defaultValue) if isPlainObject(value) and isPlainObject(defaultValue) + defaultValue = @deepClone(defaultValue) + value = _.deepExtends(defaultValue, value) if isPlainObject(value) and isPlainObject(defaultValue) else value = @deepClone(defaultValue) From 85800f6d00aac38628dc9fc66a874cae01870dc2 Mon Sep 17 00:00:00 2001 From: Jeremy Ramin Date: Sat, 22 Aug 2015 21:16:32 -0700 Subject: [PATCH 097/198] Fix method typo --- src/config.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.coffee b/src/config.coffee index d4fe1e753..18b111471 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -865,7 +865,7 @@ class Config if value? defaultValue = @deepClone(defaultValue) - value = _.deepExtends(defaultValue, value) if isPlainObject(value) and isPlainObject(defaultValue) + value = _.deepExtend(defaultValue, value) if isPlainObject(value) and isPlainObject(defaultValue) else value = @deepClone(defaultValue) From 9a0d657d52e77f0cfe123b26b9285f98d1c79962 Mon Sep 17 00:00:00 2001 From: Jeremy Ramin Date: Sat, 22 Aug 2015 23:03:19 -0700 Subject: [PATCH 098/198] Adds deepDefaults to config Took at the deepExtend in the atom/underscore-plus repo and modified it to become a deepDefaults method. Using this instead to see if it fixes the failing specs. --- src/config.coffee | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 18b111471..15aa664bb 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -864,8 +864,8 @@ class Config defaultValue = _.valueForKeyPath(@defaultSettings, keyPath) if value? - defaultValue = @deepClone(defaultValue) - value = _.deepExtend(defaultValue, value) if isPlainObject(value) and isPlainObject(defaultValue) + value = @deepClone(value) + @deepDefaults(value, defaultValue) if isPlainObject(value) and isPlainObject(defaultValue) else value = @deepClone(defaultValue) @@ -928,6 +928,19 @@ class Config else object + deepDefaults: (target) -> + result = target + i = 0 + while ++i < arguments.length + object = arguments[i] + if isPlainObject(result) and isPlainObject(object) + for key in Object.keys(object) + result[key] = @deepDefaults(result[key], object[key]) + else + if not result? + result = @deepClone(object) + result + # `schema` will look something like this # # ```coffee From c292299516747bd4953c48ea68f8af4c5024bbba Mon Sep 17 00:00:00 2001 From: Jonathan Delgado Date: Sun, 23 Aug 2015 07:51:22 -0700 Subject: [PATCH 099/198] Changed setting disableInitialEmptyEditor to openEmptyEditorOnStart --- src/atom.coffee | 2 +- src/config-schema.coffee | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 3bba8ffca..297e0ce31 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -673,7 +673,7 @@ class Atom extends Model @windowEventHandler?.unsubscribe() openInitialEmptyEditorIfNecessary: -> - return if @config.get('core.disableInitialEmptyEditor') + return unless @config.get('core.openEmptyEditorOnStart') if @getLoadSettings().initialPaths?.length is 0 and @workspace.getPaneItems().length is 0 @workspace.open(null) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 2487f4c37..d8eeb6fe3 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -89,10 +89,10 @@ module.exports = 'windows1258', 'windows866' ] - disableInitialEmptyEditor: - description: 'Disable the initial empty editor when atom starts.' + openEmptyEditorOnStart: + description: 'Automatically opens an empty editor when atom starts.' type: 'boolean' - default: false + default: true editor: type: 'object' From 86dfb50becf41a64809b730318109f2e83614832 Mon Sep 17 00:00:00 2001 From: Jonathan Delgado Date: Sun, 23 Aug 2015 08:07:19 -0700 Subject: [PATCH 100/198] Added spec for openEmptyEditorOnStart --- .../fixtures/atom-home/config-openEmptyEditorOnStart.cson | 6 ++++++ spec/integration/startup-spec.coffee | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson diff --git a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson new file mode 100644 index 000000000..4d09f3deb --- /dev/null +++ b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson @@ -0,0 +1,6 @@ +"*": + welcome: + showOnStartup: false + "exception-reporting": + userId: "7c0a3c52-795c-5e20-5323-64efcf91f212" + openEmptyEditorOnStart: false \ No newline at end of file diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index d2583ed27..67f4dc0a8 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -196,6 +196,13 @@ describe "Starting Atom", -> , 5000) .waitForExist("atom-workspace") .waitForPaneItemCount(1, 5000) + + it "doesn't open a new window if openEmptyEditorOnStart is disabled", -> + fs.writeFileSync(path.join(atomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config-openEmptyEditorOnStart.cson'))) + runAtom [], {ATOM_HOME: atomHome}, (client) -> + client + .waitForExist("atom-workspace") + .waitForPaneItemCount(0, 5000) it "reopens any previously opened windows", -> runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) -> From e5d78860974c067ee5a4ef934ff5b9263f4c9369 Mon Sep 17 00:00:00 2001 From: Jonathan Delgado Date: Sun, 23 Aug 2015 08:09:09 -0700 Subject: [PATCH 101/198] Fix openEmptyEditorOnStart spec styling --- .../fixtures/atom-home/config-openEmptyEditorOnStart.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson index 4d09f3deb..cf86d6120 100644 --- a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson +++ b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson @@ -3,4 +3,4 @@ showOnStartup: false "exception-reporting": userId: "7c0a3c52-795c-5e20-5323-64efcf91f212" - openEmptyEditorOnStart: false \ No newline at end of file + openEmptyEditorOnStart: false From d03db928f894622624165a6fdec229023286d593 Mon Sep 17 00:00:00 2001 From: Luke Pommersheim Date: Mon, 24 Aug 2015 18:29:21 +0200 Subject: [PATCH 102/198] :memo: add preserveFolds documentation to setSelectedBufferRange(s) --- src/text-editor.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index e89eec3fc..e2b93a3b2 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1839,6 +1839,8 @@ class TextEditor extends Model # * `options` (optional) An options {Object}: # * `reversed` A {Boolean} indicating whether to create the selection in a # reversed orientation. + # * `preserveFolds` A {Boolean}, which if `true` preserves the fold settings after the + # selection is set. setSelectedBufferRange: (bufferRange, options) -> @setSelectedBufferRanges([bufferRange], options) @@ -1849,6 +1851,8 @@ class TextEditor extends Model # * `options` (optional) An options {Object}: # * `reversed` A {Boolean} indicating whether to create the selection in a # reversed orientation. + # * `preserveFolds` A {Boolean}, which if `true` preserves the fold settings after the + # selection is set. setSelectedBufferRanges: (bufferRanges, options={}) -> throw new Error("Passed an empty array to setSelectedBufferRanges") unless bufferRanges.length From effd96f4bb692aa5551e315c5b548bdc27bdfb18 Mon Sep 17 00:00:00 2001 From: Jonathan Delgado Date: Mon, 24 Aug 2015 10:31:37 -0700 Subject: [PATCH 103/198] Fix openEmptyEditorOnStart fixture --- .../fixtures/atom-home/config-openEmptyEditorOnStart.cson | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson index cf86d6120..23ac98d08 100644 --- a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson +++ b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson @@ -3,4 +3,5 @@ showOnStartup: false "exception-reporting": userId: "7c0a3c52-795c-5e20-5323-64efcf91f212" - openEmptyEditorOnStart: false + core: + openEmptyEditorOnStart: false From 82a80cd732018de87e09df2e3b4897355e324f53 Mon Sep 17 00:00:00 2001 From: Jonathan Delgado Date: Mon, 24 Aug 2015 10:32:56 -0700 Subject: [PATCH 104/198] Fixed openEmptyEditorOnStart fixture tabs --- .../fixtures/atom-home/config-openEmptyEditorOnStart.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson index 23ac98d08..84ea0be11 100644 --- a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson +++ b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson @@ -3,5 +3,5 @@ showOnStartup: false "exception-reporting": userId: "7c0a3c52-795c-5e20-5323-64efcf91f212" - core: + core: openEmptyEditorOnStart: false From 5c73e5a00181144ec5eefbb822d71b4ce5f938bc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 24 Aug 2015 14:10:02 -0700 Subject: [PATCH 105/198] Add CommandRegistry::onDidDispatch --- spec/command-registry-spec.coffee | 22 ++++++++++++++++++++++ src/command-registry.coffee | 13 +++++++++++++ 2 files changed, 35 insertions(+) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 0a8cafa6b..92e322478 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -148,6 +148,28 @@ describe "CommandRegistry", -> grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) expect(calls).toEqual [] + it "invokes callbacks registered with ::onWillDispatch and ::onDidDispatch", -> + sequence = [] + + registry.onDidDispatch (event) -> + sequence.push ['onDidDispatch', event] + + registry.add '.grandchild', 'command', (event) -> + sequence.push ['listener', event] + + registry.onWillDispatch (event) -> + sequence.push ['onWillDispatch', event] + + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + + expect(sequence[0][0]).toBe 'onWillDispatch' + expect(sequence[1][0]).toBe 'listener' + expect(sequence[2][0]).toBe 'onDidDispatch' + + expect(sequence[0][1] is sequence[1][1] is sequence[2][1]).toBe true + expect(sequence[0][1].constructor).toBe CustomEvent + expect(sequence[0][1].target).toBe grandchild + describe "::add(selector, commandName, callback)", -> it "throws an error when called with an invalid selector", -> badSelector = '<>' diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 870093e2f..e41a637ef 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -182,9 +182,20 @@ class CommandRegistry stopImmediatePropagation: value: -> @handleCommandEvent(eventWithTarget) + # Public: Invoke the given callback before dispatching a command event. + # + # * `callback` {Function} to be called before dispatching each command + # * `event` The Event that will be dispatched onWillDispatch: (callback) -> @emitter.on 'will-dispatch', callback + # Public: Invoke the given callback after dispatching a command event. + # + # * `callback` {Function} to be called after dispatching each command + # * `event` The Event that was dispatched + onDidDispatch: (callback) -> + @emitter.on 'did-dispatch', callback + getSnapshot: -> snapshot = {} for commandName, listeners of @selectorBasedListenersByCommandName @@ -239,6 +250,8 @@ class CommandRegistry break if propagationStopped currentTarget = currentTarget.parentNode ? window + @emitter.emit 'did-dispatch', syntheticEvent + matched commandRegistered: (commandName) -> From 46a66067c70618761a796a7da73d104a03be0fb3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 24 Aug 2015 16:38:05 -0600 Subject: [PATCH 106/198] :arrow_up: line-ending-selector --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2b24f37a..5a4f0879d 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "image-view": "0.54.0", "incompatible-packages": "0.24.1", "keybinding-resolver": "0.33.0", - "line-ending-selector": "0.0.3", + "line-ending-selector": "0.0.4", "link": "0.30.0", "markdown-preview": "0.150.0", "metrics": "0.51.0", From e17a02b8b3ab712c08e8aba9b35f9fe2b7a22778 Mon Sep 17 00:00:00 2001 From: Natthu Bharambe Date: Mon, 24 Aug 2015 16:44:09 -0700 Subject: [PATCH 107/198] Fix a bug with refreshing the gutter UI. --- src/text-editor-presenter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5c7bac851..f81f2f50a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -541,7 +541,7 @@ class TextEditorPresenter @clearDecorationsForCustomGutterName(gutterName) else @customGutterDecorations[gutterName] = {} - return if not @gutterIsVisible(gutter) + continue if not @gutterIsVisible(gutter) relevantDecorations = @customGutterDecorationsInRange(gutterName, @startRow, @endRow - 1) relevantDecorations.forEach (decoration) => From 1de707f2d7fe54ef772a796e60169b24ad866415 Mon Sep 17 00:00:00 2001 From: Jeremy Ramin Date: Mon, 24 Aug 2015 16:08:43 -0700 Subject: [PATCH 108/198] :white_check_mark: Add tests to config spec Add tests to check if the get command properly merges the stored values with the defined defaults in the schema. Two cases: - Obj has no property changes. - Obj has one or more changes to a property. --- spec/config-spec.coffee | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 3b7bd6061..23541bfb5 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -1110,6 +1110,24 @@ describe "Config", -> nestedObject: superNestedInt: 36 + expect(atom.config.get("foo")).toEqual { + bar: + anInt: 12 + anObject: + nestedInt: 24 + nestedObject: + superNestedInt: 36 + } + atom.config.set("foo.bar.anObject.nestedObject.superNestedInt", 37) + expect(atom.config.get("foo")).toEqual { + bar: + anInt: 12 + anObject: + nestedInt: 24 + nestedObject: + superNestedInt: 37 + } + it 'can set a non-object schema', -> schema = type: 'integer' From 43213766c915ce260b75fa6bdb615e8819b43587 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 24 Aug 2015 20:26:39 -0600 Subject: [PATCH 109/198] :arrow_up: line-ending-selector --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a4f0879d..3b0187c8a 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "image-view": "0.54.0", "incompatible-packages": "0.24.1", "keybinding-resolver": "0.33.0", - "line-ending-selector": "0.0.4", + "line-ending-selector": "0.0.5", "link": "0.30.0", "markdown-preview": "0.150.0", "metrics": "0.51.0", From bf20370f52326d097d218698fd26dc7c4cf80b13 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Aug 2015 10:49:30 -0700 Subject: [PATCH 110/198] :arrow_up: text-buffer (pre-release) version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b0187c8a..86c2ef9a5 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "space-pen": "3.8.2", "stacktrace-parser": "0.1.1", "temp": "0.8.1", - "text-buffer": "6.6.1", + "text-buffer": "6.7.0-0", "theorist": "^1.0.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", From b8aec9db7c5578a29ceafa63c90ee65108a63432 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Aug 2015 10:50:05 -0700 Subject: [PATCH 111/198] Fix display buffer spec for new historied marker behavior --- spec/display-buffer-spec.coffee | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 1b490d2ff..1331c2c0a 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1025,7 +1025,7 @@ describe "DisplayBuffer", -> markerChangedHandler.reset() marker2ChangedHandler.reset() - marker3 = displayBuffer.markBufferRange([[8, 1], [8, 2]], maintainHistory: true) + marker3 = displayBuffer.markBufferRange([[8, 1], [8, 2]]) marker3.onDidChange marker3ChangedHandler = jasmine.createSpy("marker3ChangedHandler") onDisplayBufferChange = -> @@ -1039,10 +1039,6 @@ describe "DisplayBuffer", -> expect(marker.getHeadScreenPosition()).toEqual [5, 10] expect(marker.getTailScreenPosition()).toEqual [5, 4] - # but marker snapshots are not restored until the end of the undo. - expect(marker2.isValid()).toBeFalsy() - expect(marker3.isValid()).toBeFalsy() - buffer.undo() expect(changeHandler).toHaveBeenCalled() expect(markerChangedHandler).toHaveBeenCalled() @@ -1078,8 +1074,6 @@ describe "DisplayBuffer", -> expect(markerChangedHandler).toHaveBeenCalled() expect(marker2ChangedHandler).toHaveBeenCalled() expect(marker3ChangedHandler).toHaveBeenCalled() - expect(marker2.isValid()).toBeFalsy() - expect(marker3.isValid()).toBeTruthy() it "updates the position of markers before emitting change events that aren't caused by a buffer change", -> displayBuffer.onDidChange changeHandler = jasmine.createSpy("changeHandler").andCallFake -> From f57da0c6f806936eee335d75a62800338b2f7432 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Aug 2015 10:51:45 -0700 Subject: [PATCH 112/198] Suppress merging selections during undo/redo Now, during undo/redo overlapping selections will be temporarily created as markers are created via snapshots. Old selections will immediately be destroyed though, since undo/redo now completely replace all historied markers w/ those in the snapshot, so there is no need to merge selections. --- spec/text-editor-spec.coffee | 57 ++++++++++++++++++++++++++++++++++++ src/text-editor.coffee | 7 +++-- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 36dc9e739..291014a89 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -3417,6 +3417,63 @@ describe "TextEditor", -> expect(buffer.lineForRow(0)).not.toContain "foo" expect(buffer.lineForRow(0)).toContain "fovar" + it "restores cursors and selections to their states before and after undone and redone changes", -> + editor.setSelectedBufferRanges([ + [[0, 0], [0, 0]], + [[1, 0], [1, 3]], + ]) + editor.insertText("abc") + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[0, 3], [0, 3]], + [[1, 3], [1, 3]] + ] + + editor.setCursorBufferPosition([0, 0]) + editor.setSelectedBufferRanges([ + [[2, 0], [2, 0]], + [[3, 0], [3, 0]], + [[4, 0], [4, 3]], + ]) + editor.insertText("def") + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[2, 3], [2, 3]], + [[3, 3], [3, 3]] + [[4, 3], [4, 3]] + ] + + editor.setCursorBufferPosition([0, 0]) + editor.undo() + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[2, 0], [2, 0]], + [[3, 0], [3, 0]], + [[4, 0], [4, 3]], + ] + + editor.undo() + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[0, 0], [0, 0]], + [[1, 0], [1, 3]] + ] + + editor.redo() + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[0, 3], [0, 3]], + [[1, 3], [1, 3]] + ] + + editor.redo() + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[2, 3], [2, 3]], + [[3, 3], [3, 3]] + [[4, 3], [4, 3]] + ] + it "restores the selected ranges after undo and redo", -> editor.setSelectedBufferRanges([[[1, 6], [1, 10]], [[1, 22], [1, 27]]]) editor.delete() diff --git a/src/text-editor.coffee b/src/text-editor.coffee index e2b93a3b2..1e0c6f4d6 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1119,12 +1119,12 @@ class TextEditor extends Model # Essential: Undo the last change. undo: -> - @buffer.undo() + @avoidMergingSelections => @buffer.undo() @getLastSelection().autoscroll() # Essential: Redo the last change. redo: -> - @buffer.redo(this) + @avoidMergingSelections => @buffer.redo() @getLastSelection().autoscroll() # Extended: Batch multiple operations as a single undo/redo step. @@ -2217,6 +2217,9 @@ class TextEditor extends Model previousSelection.intersectsScreenRowRange(screenRange.start.row, screenRange.end.row) + avoidMergingSelections: (args...) -> + @mergeSelections args..., -> false + mergeSelections: (args...) -> mergePredicate = args.pop() fn = args.pop() if _.isFunction(_.last(args)) From 4180c81706935f998fd39022804785efc8ebed63 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Aug 2015 11:13:00 -0700 Subject: [PATCH 113/198] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86c2ef9a5..46fda56c7 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "space-pen": "3.8.2", "stacktrace-parser": "0.1.1", "temp": "0.8.1", - "text-buffer": "6.7.0-0", + "text-buffer": "6.7.0", "theorist": "^1.0.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", From 3f49d9aa002e0d72fca610e2fe34fdffd6848e08 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 25 Aug 2015 21:44:34 +0200 Subject: [PATCH 114/198] :arrow_up: symbols-view@0.103.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46fda56c7..9adda8823 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "spell-check": "0.59.0", "status-bar": "0.78.0", "styleguide": "0.44.0", - "symbols-view": "0.101.0", + "symbols-view": "0.103.0", "tabs": "0.82.0", "timecop": "0.31.0", "tree-view": "0.184.0", From 499f920638cc3ea60288bd3472dc9786b5440207 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 25 Aug 2015 21:45:33 +0200 Subject: [PATCH 115/198] Rename .ctags file to ctags-config in unpack list Necessary since the file was renamed in symbols-view@0.103.0. --- build/tasks/generate-asar-task.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/tasks/generate-asar-task.coffee b/build/tasks/generate-asar-task.coffee index 800721fee..3d28f1cca 100644 --- a/build/tasks/generate-asar-task.coffee +++ b/build/tasks/generate-asar-task.coffee @@ -10,7 +10,7 @@ module.exports = (grunt) -> unpack = [ '*.node' - '.ctags' + 'ctags-config' 'ctags-darwin' 'ctags-linux' 'ctags-win32.exe' From b9ca563c3c987b9df93bb0bcb6e1629150f55897 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Aug 2015 14:24:11 -0700 Subject: [PATCH 116/198] Don't use fixture file in openEmptyEditorOnStart spec --- .../atom-home/config-openEmptyEditorOnStart.cson | 7 ------- spec/integration/startup-spec.coffee | 9 +++++++-- 2 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson diff --git a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson b/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson deleted file mode 100644 index 84ea0be11..000000000 --- a/spec/integration/fixtures/atom-home/config-openEmptyEditorOnStart.cson +++ /dev/null @@ -1,7 +0,0 @@ -"*": - welcome: - showOnStartup: false - "exception-reporting": - userId: "7c0a3c52-795c-5e20-5323-64efcf91f212" - core: - openEmptyEditorOnStart: false diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 67f4dc0a8..87ecba409 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -10,6 +10,7 @@ fs = require "fs" path = require "path" temp = require("temp").track() runAtom = require "./helpers/start-atom" +CSON = require "season" describe "Starting Atom", -> [tempDirPath, otherTempDirPath, atomHome] = [] @@ -196,9 +197,13 @@ describe "Starting Atom", -> , 5000) .waitForExist("atom-workspace") .waitForPaneItemCount(1, 5000) - + it "doesn't open a new window if openEmptyEditorOnStart is disabled", -> - fs.writeFileSync(path.join(atomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config-openEmptyEditorOnStart.cson'))) + configPath = path.join(atomHome, 'config.cson') + config = CSON.readFileSync(configPath) + config['*'].core = {openEmptyEditorOnStart: false} + CSON.writeFileSync(configPath, config) + runAtom [], {ATOM_HOME: atomHome}, (client) -> client .waitForExist("atom-workspace") From a2a1f0c66049b70c1f796a95636feaf7765e207d Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 26 Aug 2015 13:40:07 +0900 Subject: [PATCH 117/198] :arrow_up: notifications@v0.59.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9adda8823..f0b48a855 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "link": "0.30.0", "markdown-preview": "0.150.0", "metrics": "0.51.0", - "notifications": "0.58.0", + "notifications": "0.59.0", "open-on-github": "0.38.0", "package-generator": "0.40.0", "release-notes": "0.53.0", From b5c0f5ac0bd7bdabedf5be8e4259640615fb7bd4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 Aug 2015 16:21:51 +0200 Subject: [PATCH 118/198] Change lines container height based on `boundingClientRect` --- spec/text-editor-component-spec.coffee | 30 +++++++++++++++++++++++++ spec/text-editor-presenter-spec.coffee | 12 ++++++++++ src/line-number-gutter-component.coffee | 6 ++--- src/lines-component.coffee | 6 ++--- src/text-editor-presenter.coffee | 5 +++++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 0ecc08ac2..ff31351cc 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -88,6 +88,21 @@ describe "TextEditorComponent", -> else expect(lineNode.textContent).toBe(tokenizedLine.text) + it "gives the lines container the same height as the wrapper node", -> + linesNode = componentNode.querySelector(".lines") + + wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' + component.measureDimensions() + nextAnimationFrame() + + expect(linesNode.getBoundingClientRect().height).toBe(6.5 * lineHeightInPixels) + + wrapperNode.style.height = 3.5 * lineHeightInPixels + 'px' + component.measureDimensions() + nextAnimationFrame() + + expect(linesNode.getBoundingClientRect().height).toBe(3.5 * lineHeightInPixels) + it "renders tiles upper in the stack in front of the ones below", -> wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' component.measureDimensions() @@ -586,6 +601,21 @@ describe "TextEditorComponent", -> expect(lineNode.offsetTop).toBe(top) expect(lineNode.textContent).toBe(text) + it "gives the line numbers container the same height as the wrapper node", -> + linesNode = componentNode.querySelector(".lines") + + wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' + component.measureDimensions() + nextAnimationFrame() + + expect(linesNode.getBoundingClientRect().height).toBe(6.5 * lineHeightInPixels) + + wrapperNode.style.height = 3.5 * lineHeightInPixels + 'px' + component.measureDimensions() + nextAnimationFrame() + + expect(linesNode.getBoundingClientRect().height).toBe(3.5 * lineHeightInPixels) + it "renders the currently-visible line numbers in a tiled fashion", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' component.measureDimensions() diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index f37044421..f859a37a0 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -559,6 +559,18 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> advanceClock(100) expect(presenter.getState().content.scrollingVertically).toBe false + describe ".maxHeight", -> + it "changes based on boundingClientRect", -> + presenter = buildPresenter(scrollTop: 0, lineHeight: 10) + + expectStateUpdate presenter, -> + presenter.setBoundingClientRect(left: 0, top: 0, height: 20, width: 0) + expect(presenter.getState().content.maxHeight).toBe(20) + + expectStateUpdate presenter, -> + presenter.setBoundingClientRect(left: 0, top: 0, height: 50, width: 0) + expect(presenter.getState().content.maxHeight).toBe(50) + describe ".scrollHeight", -> it "is initialized based on the lineHeight, the number of lines, and the height", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) diff --git a/src/line-number-gutter-component.coffee b/src/line-number-gutter-component.coffee index b6a5b81f6..8eef6bd27 100644 --- a/src/line-number-gutter-component.coffee +++ b/src/line-number-gutter-component.coffee @@ -47,9 +47,9 @@ class LineNumberGutterComponent extends TiledComponent beforeUpdateSync: (state) -> @appendDummyLineNumber() unless @dummyLineNumberNode? - if @newState.styles.scrollHeight isnt @oldState.styles.scrollHeight - @lineNumbersNode.style.height = @newState.styles.scrollHeight + 'px' - @oldState.scrollHeight = @newState.scrollHeight + if @newState.styles.maxHeight isnt @oldState.styles.maxHeight + @lineNumbersNode.style.height = @newState.styles.maxHeight + 'px' + @oldState.maxHeight = @newState.maxHeight if @newState.styles.backgroundColor isnt @oldState.styles.backgroundColor @lineNumbersNode.style.backgroundColor = @newState.styles.backgroundColor diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 083db1f0b..2b80235dc 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -35,9 +35,9 @@ class LinesComponent extends TiledComponent @oldState.indentGuidesVisible isnt @newState.indentGuidesVisible beforeUpdateSync: (state) -> - if @newState.scrollHeight isnt @oldState.scrollHeight - @domNode.style.height = @newState.scrollHeight + 'px' - @oldState.scrollHeight = @newState.scrollHeight + if @newState.maxHeight isnt @oldState.maxHeight + @domNode.style.height = @newState.maxHeight + 'px' + @oldState.maxHeight = @newState.maxHeight if @newState.backgroundColor isnt @oldState.backgroundColor @domNode.style.backgroundColor = @newState.backgroundColor diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5c7bac851..5fd986da3 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -304,6 +304,10 @@ class TextEditorPresenter @state.hiddenInput.width = Math.max(width, 2) updateContentState: -> + if @boundingClientRect? + @sharedGutterStyles.maxHeight = @boundingClientRect.height + @state.content.maxHeight = @boundingClientRect.height + @state.content.width = Math.max(@contentWidth + @verticalScrollbarWidth, @contentFrameWidth) @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft @@ -918,6 +922,7 @@ class TextEditorPresenter unless @clientRectsEqual(@boundingClientRect, boundingClientRect) @boundingClientRect = boundingClientRect @shouldUpdateOverlaysState = true + @shouldUpdateContentState = true @emitDidUpdateState() From 28f157acd909cf263cd355159dc5156031fba91a Mon Sep 17 00:00:00 2001 From: Ross Allen Date: Mon, 24 Aug 2015 14:19:17 -0700 Subject: [PATCH 119/198] Document MenuItem `enabled` and `visible` The Electron MenuItem[1] `enabled` and `visible` options are exposed to Atom context menus in menu_helpers.coffee[2] but are not yet documented. Both options are passed through to Electron. [1] http://electron.atom.io/docs/v0.31.0/api/menu-item/ [2] https://github.com/atom/atom/blob/v1.0.7/src/menu-helpers.coffee#L49 --- src/context-menu-manager.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 73bcbf440..0258fedc7 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -86,9 +86,14 @@ class ContextMenuManager # * `label` (Optional) A {String} containing the menu item's label. # * `command` (Optional) A {String} containing the command to invoke on the # target of the right click that invoked the context menu. + # * `enabled` (Optional) A {Boolean} indicating whether the menu item + # should be clickable. Disabled menu items typically appear grayed out. + # Defaults to `true`. # * `submenu` (Optional) An {Array} of additional items. # * `type` (Optional) If you want to create a separator, provide an item # with `type: 'separator'` and no other keys. + # * `visible` (Optional) A {Boolean} indicating whether the menu item + # should appear in the menu. Defaults to `true`. # * `created` (Optional) A {Function} that is called on the item each time a # context menu is created via a right click. You can assign properties to # `this` to dynamically compute the command, label, etc. This method is From f602a6b726280c5d5c064abbbf950506936ecb01 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 Aug 2015 09:21:32 -0700 Subject: [PATCH 120/198] :arrow_up: event-kit Refs atom/event-kit#16 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0b48a855..ec61e6a9e 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "color": "^0.7.3", "delegato": "^1", "emissary": "^1.3.3", - "event-kit": "^1.2.0", + "event-kit": "^1.3.0", "first-mate": "^5.0.0", "fs-plus": "^2.8.0", "fstream": "0.1.24", From fb7c62fd0532f2f1ddd99e44e4a93457493289a9 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Wed, 26 Aug 2015 12:51:15 -0400 Subject: [PATCH 121/198] :arrow_up: language-javascript@0.90.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ec61e6a9e..73122c03d 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "language-html": "0.41.0", "language-hyperlink": "0.14.0", "language-java": "0.16.0", - "language-javascript": "0.89.0", + "language-javascript": "0.90.0", "language-json": "0.16.0", "language-less": "0.28.2", "language-make": "0.17.0", From 763d8714704fd1b40077a60c6d1684caf71969f1 Mon Sep 17 00:00:00 2001 From: Jacek Kopecky Date: Wed, 26 Aug 2015 20:28:23 +0100 Subject: [PATCH 122/198] use undo in IME composition --- src/text-editor-component.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index b058378b8..81f92476f 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -241,13 +241,13 @@ class TextEditorComponent # 4. compositionend fired # 5. textInput fired; event.data == the completion string - selectedText = null + checkpoint = null @domNode.addEventListener 'compositionstart', => - selectedText = @editor.getSelectedText() + checkpoint = @editor.createCheckpoint() @domNode.addEventListener 'compositionupdate', (event) => - @editor.insertText(event.data, select: true, undo: 'skip') + @editor.insertText(event.data, select: true) @domNode.addEventListener 'compositionend', (event) => - @editor.insertText(selectedText, select: true, undo: 'skip') + @editor.revertToCheckpoint(checkpoint) event.target.value = '' # Listen for selection changes and store the currently selected text From 0310ccb6e5983d7bfa8de40d6a3a8ddf55ee98a6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 Aug 2015 12:35:18 -0700 Subject: [PATCH 123/198] :arrow_up: grunt-download-atom-shell Fixes https://github.com/atom/atom/issues/8525 --- build/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/package.json b/build/package.json index bd0c9a474..acca67b70 100644 --- a/build/package.json +++ b/build/package.json @@ -21,7 +21,7 @@ "grunt-contrib-csslint": "~0.2.0", "grunt-contrib-less": "~0.8.0", "grunt-cson": "0.14.0", - "grunt-download-atom-shell": "~0.14.0", + "grunt-download-atom-shell": "~0.15.0", "grunt-electron-installer": "^0.37.0", "grunt-lesslint": "0.17.0", "grunt-peg": "~1.1.0", From e4b189c6c065f9449306f40b7f035e406bc51da8 Mon Sep 17 00:00:00 2001 From: Jacek Kopecky Date: Wed, 26 Aug 2015 21:04:09 +0100 Subject: [PATCH 124/198] add tests --- spec/text-editor-component-spec.coffee | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 0ecc08ac2..b70c8d6c7 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2741,6 +2741,33 @@ describe "TextEditorComponent", -> componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) expect(editor.lineTextForBufferRow(0)).toBe 'var quicksort = function () {' + describe "with multiple selections", -> + beforeEach -> + editor.setSelectedBufferRanges [[[0, 4], [0, 9]], [[0, 16], [0, 19]]] # select 'quick' and 'fun' + + it "inserts the chosen completion", -> + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) + expect(editor.lineTextForBufferRow(0)).toBe 'var ssort = sction () {' + + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) + expect(editor.lineTextForBufferRow(0)).toBe 'var sdsort = sdction () {' + + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + componentNode.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode)) + expect(editor.lineTextForBufferRow(0)).toBe 'var 速度sort = 速度ction () {' + + it "reverts back to the original text when the completion helper is dismissed", -> + componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) + expect(editor.lineTextForBufferRow(0)).toBe 'var ssort = sction () {' + + componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) + expect(editor.lineTextForBufferRow(0)).toBe 'var sdsort = sdction () {' + + componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) + expect(editor.lineTextForBufferRow(0)).toBe 'var quicksort = function () {' + describe "commands", -> describe "editor:consolidate-selections", -> it "consolidates selections on the editor model, aborting the key binding if there is only one selection", -> From d5e4c01e0aed33e39111e144207ba87c3b769376 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 Aug 2015 13:21:02 -0700 Subject: [PATCH 125/198] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73122c03d..b3e1b1e21 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "symbols-view": "0.103.0", "tabs": "0.82.0", "timecop": "0.31.0", - "tree-view": "0.184.0", + "tree-view": "0.185.0", "update-package-dependencies": "0.10.0", "welcome": "0.30.0", "whitespace": "0.30.0", From 3a814141558fdd24e12c716fc2ce73301faf8117 Mon Sep 17 00:00:00 2001 From: Jacek Kopecky Date: Wed, 26 Aug 2015 21:50:40 +0100 Subject: [PATCH 126/198] :fire: redundant old tests --- spec/text-editor-component-spec.coffee | 27 -------------------------- 1 file changed, 27 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index b70c8d6c7..f4b2905b2 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2715,33 +2715,6 @@ describe "TextEditorComponent", -> expect(editor.lineTextForBufferRow(0)).toBe "áávar quicksort = function () {" describe "when a string is selected", -> - beforeEach -> - editor.setSelectedBufferRange [[0, 4], [0, 9]] # select 'quick' - - it "inserts the chosen completion", -> - componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) - expect(editor.lineTextForBufferRow(0)).toBe 'var ssort = function () {' - - componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) - expect(editor.lineTextForBufferRow(0)).toBe 'var sdsort = function () {' - - componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) - componentNode.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode)) - expect(editor.lineTextForBufferRow(0)).toBe 'var 速度sort = function () {' - - it "reverts back to the original text when the completion helper is dismissed", -> - componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode)) - componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode)) - expect(editor.lineTextForBufferRow(0)).toBe 'var ssort = function () {' - - componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode)) - expect(editor.lineTextForBufferRow(0)).toBe 'var sdsort = function () {' - - componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode)) - expect(editor.lineTextForBufferRow(0)).toBe 'var quicksort = function () {' - - describe "with multiple selections", -> beforeEach -> editor.setSelectedBufferRanges [[[0, 4], [0, 9]], [[0, 16], [0, 19]]] # select 'quick' and 'fun' From aba76015b0b28d476186eed25844989f1761fe3c Mon Sep 17 00:00:00 2001 From: Natthu Bharambe Date: Wed, 26 Aug 2015 15:17:03 -0700 Subject: [PATCH 127/198] Add a spec to verify gutter decorations are updated even if a higher priority gutter is hidden. --- spec/text-editor-presenter-spec.coffee | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index f37044421..17140a18b 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2300,6 +2300,15 @@ describe "TextEditorPresenter", -> expect(decorationState[decoration3.id]).toBeUndefined() + it "updates when another gutter with higher priority is hidden", -> + editor.addGutter({name: 'test-gutter-1', priority: -150, visible: false}) + + # This update will scroll decoration1 out of view, and decoration3 into view. + expectStateUpdate presenter, -> presenter.setScrollTop(scrollTop + lineHeight * 5) + + decorationState = getContentForGutterWithName(presenter, 'test-gutter') + expect(decorationState[decoration1.id]).toBeUndefined() + it "updates when ::scrollTop changes", -> # This update will scroll decoration1 out of view, and decoration3 into view. expectStateUpdate presenter, -> presenter.setScrollTop(scrollTop + lineHeight * 5) From 6df058c7f331944138e5f30b42b3f526ff8716ed Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 Aug 2015 13:51:07 -0700 Subject: [PATCH 128/198] Allow adding a root folder that's within another root folder --- spec/project-spec.coffee | 256 +++++++++++++++++++++------------------ src/project.coffee | 34 +++--- 2 files changed, 153 insertions(+), 137 deletions(-) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 5039dd50e..bc417b8c9 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -16,103 +16,29 @@ describe "Project", -> # Wait for project's service consumers to be asynchronously added waits(1) - 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) - - 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 "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.") - atom.packages.serviceHub.provide( - "atom.directory-provider", "0.1.0", directoryProvider) - - tmp = temp.mkdirSync() - atom.project.setPaths([tmp]) - directories = atom.project.getDirectories() - expect(directories.length).toBe 1 - expect(directories[0].getPath()).toBe tmp - - it "gets the parent directory from the default directory provider if it's a local directory", -> - tmp = temp.mkdirSync() - atom.project.setPaths([path.join(tmp, "not-existing")]) - directories = atom.project.getDirectories() - expect(directories.length).toBe 1 - expect(directories[0].getPath()).toBe tmp - - it "only normalizes the directory path if it isn't on the local filesystem", -> - nonLocalFsDirectory = "custom_proto://abc/def" - atom.project.setPaths([nonLocalFsDirectory]) - directories = atom.project.getDirectories() - expect(directories.length).toBe 1 - expect(directories[0].getPath()).toBe path.normalize(nonLocalFsDirectory) - - it "tries to update repositories when a new RepositoryProvider is registered", -> - tmp = temp.mkdirSync('atom-project') - atom.project.setPaths([tmp]) + describe "when a new repository-provider is added", -> + it "uses it to create repositories for any directories that need one", -> + projectPath = temp.mkdirSync('atom-project') + atom.project.setPaths([projectPath]) expect(atom.project.getRepositories()).toEqual [null] expect(atom.project.repositoryProviders.length).toEqual 1 - # Register a new RepositoryProvider. - dummyRepository = destroy: -> - repositoryProvider = + dummyRepository = {destroy: -> null} + + atom.packages.serviceHub.provide("atom.repository-provider", "0.1.0", { repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository) repositoryForDirectorySync: (directory) -> dummyRepository - atom.packages.serviceHub.provide( - "atom.repository-provider", "0.1.0", repositoryProvider) + }) - waitsFor -> atom.project.repositoryProviders.length is 2 - runs -> expect(atom.project.getRepositories()).toEqual [dummyRepository] + repository = null - it "does not update @repositories if every path has a Repository", -> + waitsFor "repository to be updated", -> + repository = atom.project.getRepositories()[0] + + runs -> + expect(repository).toBe dummyRepository + + it "does not create any new repositories if every directory has a repository", -> repositories = atom.project.getRepositories() expect(repositories.length).toEqual 1 [repository] = repositories @@ -336,12 +262,13 @@ describe "Project", -> # Verify that the result is cached. expect(atom.project.repositoryForDirectory(directory)).toBe(promise) - describe ".setPaths(path)", -> + describe ".setPaths(paths)", -> describe "when path is a file", -> it "sets its path to the files parent directory and updates the root directory", -> - atom.project.setPaths([require.resolve('./fixtures/dir/a')]) - expect(atom.project.getPaths()[0]).toEqual path.dirname(require.resolve('./fixtures/dir/a')) - expect(atom.project.getDirectories()[0].path).toEqual path.dirname(require.resolve('./fixtures/dir/a')) + filePath = require.resolve('./fixtures/dir/a') + atom.project.setPaths([filePath]) + expect(atom.project.getPaths()[0]).toEqual path.dirname(filePath) + expect(atom.project.getDirectories()[0].path).toEqual path.dirname(filePath) describe "when path is a directory", -> it "assigns the directories and repositories", -> @@ -372,17 +299,86 @@ describe "Project", -> expect(onDidChangePathsSpy.callCount).toBe 1 expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual(paths) - describe "when path is null", -> - it "sets its path and root directory to null", -> + describe "when no paths are given", -> + it "clears its path", -> atom.project.setPaths([]) - expect(atom.project.getPaths()[0]?).toBeFalsy() - expect(atom.project.getDirectories()[0]?).toBeFalsy() + expect(atom.project.getPaths()).toEqual [] + expect(atom.project.getDirectories()).toEqual [] it "normalizes the path to remove consecutive slashes, ., and .. segments", -> atom.project.setPaths(["#{require.resolve('./fixtures/dir/a')}#{path.sep}b#{path.sep}#{path.sep}.."]) expect(atom.project.getPaths()[0]).toEqual path.dirname(require.resolve('./fixtures/dir/a')) expect(atom.project.getDirectories()[0].path).toEqual path.dirname(require.resolve('./fixtures/dir/a')) + it "only normalizes the directory path if it isn't on the local filesystem", -> + nonLocalFsDirectory = "custom_proto://abc/def" + atom.project.setPaths([nonLocalFsDirectory]) + directories = atom.project.getDirectories() + expect(directories.length).toBe 1 + expect(directories[0].getPath()).toBe path.normalize(nonLocalFsDirectory) + + describe "when a custom directory provider has been added", -> + describe "when custom provider handles the given path", -> + it "creates a directory using that provider", -> + class DummyDirectory + constructor: (@path) -> + getPath: -> @path + getFile: -> {existsSync: -> false} + getSubdirectory: -> {existsSync: -> false} + isRoot: -> true + existsSync: -> /does-exist/.test(@path) + off: -> + contains: (filePath) -> filePath.startsWith(@path) + + atom.packages.serviceHub.provide("atom.directory-provider", "0.1.0", { + directoryForURISync: (uri) -> + if uri.startsWith("ssh://") + new DummyDirectory(uri) + else + null + }) + + localPath = temp.mkdirSync('local-path') + remotePath = "ssh://foreign-directory:8080/exists" + + atom.project.setPaths([localPath, remotePath]) + + directories = atom.project.getDirectories() + expect(directories[0].getPath()).toBe localPath + expect(directories[0] instanceof Directory).toBe true + expect(directories[1].getPath()).toBe remotePath + expect(directories[1] instanceof DummyDirectory).toBe true + + # 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 + + describe "when a custom provider does not handle the path", -> + it "creates a local directory for the path", -> + 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) + + tmp = temp.mkdirSync() + atom.project.setPaths([tmp]) + directories = atom.project.getDirectories() + expect(directories.length).toBe 1 + expect(directories[0].getPath()).toBe tmp + describe ".addPath(path)", -> it "calls callbacks registered with ::onDidChangePaths", -> onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy') @@ -396,20 +392,26 @@ describe "Project", -> expect(onDidChangePathsSpy.callCount).toBe 1 expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual([oldPath, newPath]) - describe "when the project already has the path or one of its descendants", -> - it "doesn't add it again", -> - onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy') - atom.project.onDidChangePaths(onDidChangePathsSpy) + it "doesn't add redundant paths", -> + onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy') + atom.project.onDidChangePaths(onDidChangePathsSpy) + [oldPath] = atom.project.getPaths() - [oldPath] = atom.project.getPaths() + # Doesn't re-add an existing root directory + atom.project.addPath(oldPath) + expect(atom.project.getPaths()).toEqual([oldPath]) + expect(onDidChangePathsSpy).not.toHaveBeenCalled() - atom.project.addPath(oldPath) - atom.project.addPath(path.join(oldPath, "some-file.txt")) - atom.project.addPath(path.join(oldPath, "a-dir")) - atom.project.addPath(path.join(oldPath, "a-dir", "oh-git")) + # Doesn't add an entry for a file-path within an existing root directory + atom.project.addPath(path.join(oldPath, 'some-file.txt')) + expect(atom.project.getPaths()).toEqual([oldPath]) + expect(onDidChangePathsSpy).not.toHaveBeenCalled() - expect(atom.project.getPaths()).toEqual([oldPath]) - expect(onDidChangePathsSpy).not.toHaveBeenCalled() + # Does add an entry for a directory within an existing directory + newPath = path.join(oldPath, "a-dir") + atom.project.addPath(newPath) + expect(atom.project.getPaths()).toEqual([oldPath, newPath]) + expect(onDidChangePathsSpy).toHaveBeenCalled() describe ".removePath(path)", -> onDidChangePathsSpy = null @@ -440,19 +442,22 @@ describe "Project", -> expect(atom.project.getRepositories()[0].isSubmodule("src")).toBe false it "removes a path that is represented as a URI", -> - ftpURI = "ftp://example.com/some/folder" - directoryProvider = + atom.packages.serviceHub.provide("atom.directory-provider", "0.1.0", { directoryForURISync: (uri) -> - # Dummy implementation of Directory for which GitRepositoryProvider - # will not try to create a GitRepository. - getPath: -> ftpURI - getSubdirectory: -> {} - isRoot: -> true - off: -> - atom.packages.serviceHub.provide( - "atom.directory-provider", "0.1.0", directoryProvider) + { + getPath: -> uri + getSubdirectory: -> {} + isRoot: -> true + existsSync: -> true + off: -> + } + }) + + ftpURI = "ftp://example.com/some/folder" + atom.project.setPaths([ftpURI]) expect(atom.project.getPaths()).toEqual [ftpURI] + atom.project.removePath(ftpURI) expect(atom.project.getPaths()).toEqual [] @@ -494,6 +499,19 @@ describe "Project", -> url = "http://the-path" expect(atom.project.relativizePath(url)).toEqual [null, url] + describe "when the given path is inside more than one root folder", -> + it "uses the root folder that is closest to the given path", -> + atom.project.addPath(path.join(atom.project.getPaths()[0], 'a-dir')) + + inputPath = path.join(atom.project.getPaths()[1], 'somewhere/something.txt') + + expect(atom.project.getDirectories()[0].contains(inputPath)).toBe true + expect(atom.project.getDirectories()[1].contains(inputPath)).toBe true + expect(atom.project.relativizePath(inputPath)).toEqual [ + atom.project.getPaths()[1], + 'somewhere/something.txt' + ] + describe ".contains(path)", -> it "returns whether or not the given path is in one of the root directories", -> rootPath = atom.project.getPaths()[0] diff --git a/src/project.coffee b/src/project.coffee index b21e74076..a0a3c7fe2 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -34,12 +34,11 @@ class Project extends Model @rootDirectories = [] @repositories = [] - @directoryProviders = [new DefaultDirectoryProvider()] + @directoryProviders = [] + @defaultDirectoryProvider = 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 @@ -48,8 +47,6 @@ class Project extends Model # the same real path, so it is not a good key. @repositoryPromisesByPath = new Map() - # Note that the GitRepositoryProvider is registered synchronously so that - # it is available immediately on startup. @repositoryProviders = [new GitRepositoryProvider(this)] atom.packages.serviceHub.consume( 'atom.repository-provider', @@ -186,18 +183,16 @@ class Project extends Model # # * `projectPath` {String} The path to the directory to add. addPath: (projectPath, options) -> - 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 - 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') + directory ?= @defaultDirectoryProvider.directoryForURISync(projectPath) + + directoryExists = directory.existsSync() + for rootDirectory in @getDirectories() + return if rootDirectory.getPath() is directory.getPath() + return if not directoryExists and rootDirectory.contains(directory.getPath()) + @rootDirectories.push(directory) repo = null @@ -267,10 +262,13 @@ class Project extends Model # * `relativePath` {String} The relative path from the project directory to # the given path. relativizePath: (fullPath) -> - for rootDirectory in @rootDirectories - relativePath = rootDirectory.relativize(fullPath) - return [rootDirectory.getPath(), relativePath] unless relativePath is fullPath - [null, fullPath] + result = [null, fullPath] + if fullPath? + for rootDirectory in @rootDirectories + relativePath = rootDirectory.relativize(fullPath) + if relativePath?.length < result[1].length + result = [rootDirectory.getPath(), relativePath] + result # Public: Determines whether the given path (real or symbolic) is inside the # project's directory. From 2e46599855a96c892eb9c4d2215c90a270ce118b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 26 Aug 2015 15:28:26 -0700 Subject: [PATCH 129/198] :arrow_up: apm@1.0.4 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index b49a76ec8..114d783f2 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.0.2" + "atom-package-manager": "1.0.4" } } From 4f397af758c3269172c81d6812297122ec7788cc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 Aug 2015 16:02:48 -0700 Subject: [PATCH 130/198] :arrow_up: fuzzy-finder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3e1b1e21..9d20d0f3f 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "encoding-selector": "0.21.0", "exception-reporting": "0.36.0", "find-and-replace": "0.180.0", - "fuzzy-finder": "0.87.0", + "fuzzy-finder": "0.88.0", "git-diff": "0.55.0", "go-to-line": "0.30.0", "grammar-selector": "0.47.0", From 4bbc1d806e02c89ac3d2a2996f70087ed4b6a017 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 26 Aug 2015 17:59:19 -0600 Subject: [PATCH 131/198] Create a selection automatically as needed if the last one was destroyed Fixes atom/bracket-matcher#102 --- spec/text-editor-spec.coffee | 19 +++++++++++++++++++ src/text-editor.coffee | 12 ++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 291014a89..739e3a3c4 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -207,6 +207,15 @@ describe "TextEditor", -> lastCursor = editor.addCursorAtScreenPosition([2, 0]) expect(editor.getLastCursor()).toBe lastCursor + it "creates a new cursor at (0, 0) if the last cursor has been destroyed", -> + editor.getLastCursor().destroy() + expect(editor.getLastCursor().getBufferPosition()).toEqual([0, 0]) + + describe ".getCursors()", -> + it "creates a new cursor at (0, 0) if the last cursor has been destroyed", -> + editor.getLastCursor().destroy() + expect(editor.getCursors()[0].getBufferPosition()).toEqual([0, 0]) + describe "when the cursor moves", -> it "clears a goal column established by vertical movement", -> editor.setText('b') @@ -1027,6 +1036,16 @@ describe "TextEditor", -> beforeEach -> selection = editor.getLastSelection() + describe ".getLastSelection()", -> + it "creates a new selection at (0, 0) if the last selection has been destroyed", -> + editor.getLastSelection().destroy() + expect(editor.getLastSelection().getBufferRange()).toEqual([[0, 0], [0, 0]]) + + describe ".getSelections()", -> + it "creates a new selection at (0, 0) if the last selection has been destroyed", -> + editor.getLastSelection().destroy() + expect(editor.getSelections()[0].getBufferRange()).toEqual([[0, 0], [0, 0]]) + describe "when the selection range changes", -> it "emits an event with the old range, new range, and the selection that moved", -> editor.setSelectedBufferRange([[3, 0], [4, 5]]) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 1e0c6f4d6..5f6408c30 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -95,7 +95,7 @@ class TextEditor extends Model @subscribeToBuffer() @subscribeToDisplayBuffer() - if @getCursors().length is 0 and not suppressCursorCreation + if @cursors.length is 0 and not suppressCursorCreation initialLine = Math.max(parseInt(initialLine) or 0, 0) initialColumn = Math.max(parseInt(initialColumn) or 0, 0) @addCursorAtBufferPosition([initialLine, initialColumn]) @@ -185,7 +185,7 @@ class TextEditor extends Model @unsubscribe() if includeDeprecatedAPIs @disposables.dispose() @tabTypeSubscription.dispose() - selection.destroy() for selection in @getSelections() + selection.destroy() for selection in @selections.slice() @buffer.release() @displayBuffer.destroy() @languageMode.destroy() @@ -1744,6 +1744,7 @@ class TextEditor extends Model # Extended: Returns the most recently added {Cursor} getLastCursor: -> + @createLastSelectionIfNeeded() _.last(@cursors) # Extended: Returns the word surrounding the most recently added cursor. @@ -1754,6 +1755,7 @@ class TextEditor extends Model # Extended: Get an Array of all {Cursor}s. getCursors: -> + @createLastSelectionIfNeeded() @cursors.slice() # Extended: Get all {Cursors}s, ordered by their position in the buffer @@ -2133,12 +2135,14 @@ class TextEditor extends Model # # Returns a {Selection}. getLastSelection: -> + @createLastSelectionIfNeeded() _.last(@selections) # Extended: Get current {Selection}s. # # Returns: An {Array} of {Selection}s. getSelections: -> + @createLastSelectionIfNeeded() @selections.slice() # Extended: Get all {Selection}s, ordered by their position in the buffer @@ -2295,6 +2299,10 @@ class TextEditor extends Model @emit 'selection-screen-range-changed', event if includeDeprecatedAPIs @emitter.emit 'did-change-selection-range', event + createLastSelectionIfNeeded: -> + if @selections.length is 0 + @addSelectionForBufferRange([[0, 0], [0, 0]], autoscroll: false, preserveFolds: true) + ### Section: Searching and Replacing ### From ae238b02f683cf7be45d712c6f85aab6ced53c8d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 Aug 2015 17:10:34 -0700 Subject: [PATCH 132/198] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d20d0f3f..7796b7d9a 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "symbols-view": "0.103.0", "tabs": "0.82.0", "timecop": "0.31.0", - "tree-view": "0.185.0", + "tree-view": "0.186.0", "update-package-dependencies": "0.10.0", "welcome": "0.30.0", "whitespace": "0.30.0", From b15b103ba0c9722b016469923fa1dc2d47cc120e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 Aug 2015 17:41:34 -0700 Subject: [PATCH 133/198] :arrow_up: grunt-download-atom-shell --- build/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/package.json b/build/package.json index acca67b70..02ba4cfa1 100644 --- a/build/package.json +++ b/build/package.json @@ -21,7 +21,7 @@ "grunt-contrib-csslint": "~0.2.0", "grunt-contrib-less": "~0.8.0", "grunt-cson": "0.14.0", - "grunt-download-atom-shell": "~0.15.0", + "grunt-download-atom-shell": "~0.15.1", "grunt-electron-installer": "^0.37.0", "grunt-lesslint": "0.17.0", "grunt-peg": "~1.1.0", From 423c3d945ed4998d2be3138d88853fbf880be42d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 27 Aug 2015 11:37:23 +0200 Subject: [PATCH 134/198] :racehorse: Remove redundant calls to screen conversion methods --- src/text-editor-presenter.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5fd986da3..1e103f221 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -416,9 +416,10 @@ class TextEditorPresenter updateCursorState: (cursor) -> return unless @startRow? and @endRow? and @hasPixelRectRequirements() and @baseCharacterWidth? - return unless cursor.isVisible() and @startRow <= cursor.getScreenRow() < @endRow + screenRange = cursor.getScreenRange() + return unless cursor.isVisible() and @startRow <= screenRange.start.row < @endRow - pixelRect = @pixelRectForScreenRange(cursor.getScreenRange()) + pixelRect = @pixelRectForScreenRange(screenRange) pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect @@ -1166,7 +1167,7 @@ class TextEditorPresenter if decoration.isType('line') or decoration.isType('gutter') @addToLineDecorationCaches(decoration, range) else if decoration.isType('highlight') - @updateHighlightState(decoration) + @updateHighlightState(decoration, range) for tileId, tileState of @state.content.tiles for id, highlight of tileState.highlights @@ -1237,12 +1238,11 @@ class TextEditorPresenter intersectingRange - updateHighlightState: (decoration) -> + updateHighlightState: (decoration, range) -> return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements() properties = decoration.getProperties() marker = decoration.getMarker() - range = marker.getScreenRange() if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) return From ca285d3fe3438681dbb13ba759d809cf74d61a62 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 27 Aug 2015 12:12:45 +0200 Subject: [PATCH 135/198] :green_heart: Correct typo in container's height specs --- spec/text-editor-component-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 2869ffd6c..84d8cc1c7 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -602,7 +602,7 @@ describe "TextEditorComponent", -> expect(lineNode.textContent).toBe(text) it "gives the line numbers container the same height as the wrapper node", -> - linesNode = componentNode.querySelector(".lines") + linesNode = componentNode.querySelector(".line-numbers") wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' component.measureDimensions() From 9ea62673d2d1042260679f2ff1771292ae22f1ac Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 27 Aug 2015 12:15:55 +0200 Subject: [PATCH 136/198] Put a decreasing `z-index` on line-numbers tiles --- spec/text-editor-component-spec.coffee | 22 ++++++++++++++++++++++ src/line-numbers-tile-component.coffee | 4 ++++ src/text-editor-presenter.coffee | 4 +++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 84d8cc1c7..6c3b9f104 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -601,6 +601,28 @@ describe "TextEditorComponent", -> expect(lineNode.offsetTop).toBe(top) expect(lineNode.textContent).toBe(text) + it "renders tiles upper in the stack in front of the ones below", -> + wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' + component.measureDimensions() + nextAnimationFrame() + + tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") + + expect(tilesNodes[0].style.zIndex).toBe("2") + expect(tilesNodes[1].style.zIndex).toBe("1") + expect(tilesNodes[2].style.zIndex).toBe("0") + + verticalScrollbarNode.scrollTop = 1 * lineHeightInPixels + verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) + nextAnimationFrame() + + tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") + + expect(tilesNodes[0].style.zIndex).toBe("3") + expect(tilesNodes[1].style.zIndex).toBe("2") + expect(tilesNodes[2].style.zIndex).toBe("1") + expect(tilesNodes[3].style.zIndex).toBe("0") + it "gives the line numbers container the same height as the wrapper node", -> linesNode = componentNode.querySelector(".line-numbers") diff --git a/src/line-numbers-tile-component.coffee b/src/line-numbers-tile-component.coffee index cf58c54f3..da3d6e749 100644 --- a/src/line-numbers-tile-component.coffee +++ b/src/line-numbers-tile-component.coffee @@ -42,6 +42,10 @@ class LineNumbersTileComponent @domNode.style['-webkit-transform'] = "translate3d(0, #{@newTileState.top}px, 0px)" @oldTileState.top = @newTileState.top + if @newTileState.zIndex isnt @oldTileState.zIndex + @domNode.style.zIndex = @newTileState.zIndex + @oldTileState.zIndex = @newTileState.zIndex + if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits node.remove() for id, node of @lineNumberNodesById @oldState.tiles[@id] = {lineNumbers: {}} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5fd986da3..574c6ca8e 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -344,18 +344,20 @@ class TextEditorPresenter tile.left = -@scrollLeft tile.height = @tileSize * @lineHeight tile.display = "block" - tile.zIndex = zIndex-- + tile.zIndex = zIndex tile.highlights ?= {} gutterTile = @lineNumberGutter.tiles[startRow] ?= {} gutterTile.top = startRow * @lineHeight - @scrollTop gutterTile.height = @tileSize * @lineHeight gutterTile.display = "block" + gutterTile.zIndex = zIndex @updateLinesState(tile, startRow, endRow) if @shouldUpdateLinesState @updateLineNumbersState(gutterTile, startRow, endRow) if @shouldUpdateLineNumbersState visibleTiles[startRow] = true + zIndex-- if @mouseWheelScreenRow? and @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)? mouseWheelTile = @tileForRow(@mouseWheelScreenRow) From fa28aef2b55753945b678b620694afccea2bb2f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 27 Aug 2015 12:47:43 +0200 Subject: [PATCH 137/198] Put a decreasing `z-index` on line numbers --- spec/text-editor-component-spec.coffee | 20 ++++++++++++++++++++ src/line-numbers-tile-component.coffee | 8 ++++++-- src/text-editor-presenter.coffee | 7 +++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 6c3b9f104..bd6fd9600 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -623,6 +623,26 @@ describe "TextEditorComponent", -> expect(tilesNodes[2].style.zIndex).toBe("1") expect(tilesNodes[3].style.zIndex).toBe("0") + it "renders lines upper in the stack in front of the ones below in each tile", -> + wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' + component.measureDimensions() + nextAnimationFrame() + + # Tile 0 + expect(component.lineNumberNodeForScreenRow(0).style.zIndex).toBe("2") + expect(component.lineNumberNodeForScreenRow(1).style.zIndex).toBe("1") + expect(component.lineNumberNodeForScreenRow(2).style.zIndex).toBe("0") + + # Tile 1 + expect(component.lineNumberNodeForScreenRow(3).style.zIndex).toBe("2") + expect(component.lineNumberNodeForScreenRow(4).style.zIndex).toBe("1") + expect(component.lineNumberNodeForScreenRow(5).style.zIndex).toBe("0") + + # Tile 2 + expect(component.lineNumberNodeForScreenRow(6).style.zIndex).toBe("2") + expect(component.lineNumberNodeForScreenRow(7).style.zIndex).toBe("1") + expect(component.lineNumberNodeForScreenRow(8).style.zIndex).toBe("0") + it "gives the line numbers container the same height as the wrapper node", -> linesNode = componentNode.querySelector(".line-numbers") diff --git a/src/line-numbers-tile-component.coffee b/src/line-numbers-tile-component.coffee index da3d6e749..2ebccbc65 100644 --- a/src/line-numbers-tile-component.coffee +++ b/src/line-numbers-tile-component.coffee @@ -88,9 +88,9 @@ class LineNumbersTileComponent return buildLineNumberHTML: (lineNumberState) -> - {screenRow, bufferRow, softWrapped, top, decorationClasses} = lineNumberState + {screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState if screenRow? - style = "position: absolute; top: #{top}px;" + style = "position: absolute; top: #{top}px; z-index: #{zIndex};" else style = "visibility: hidden;" className = @buildLineNumberClassName(lineNumberState) @@ -125,6 +125,10 @@ class LineNumbersTileComponent oldLineNumberState.top = newLineNumberState.top oldLineNumberState.screenRow = newLineNumberState.screenRow + unless oldLineNumberState.zIndex is newLineNumberState.zIndex + node.style.zIndex = newLineNumberState.zIndex + oldLineNumberState.zIndex = newLineNumberState.zIndex + buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) -> className = "line-number line-number-#{bufferRow}" className += " " + decorationClasses.join(' ') if decorationClasses? diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 574c6ca8e..4635533cc 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -589,7 +589,9 @@ class TextEditorPresenter wrapCount = 0 if endRow > startRow - for bufferRow, i in @model.bufferRowsForScreenRows(startRow, endRow - 1) + bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1) + zIndex = bufferRows.length - 1 + for bufferRow, i in bufferRows if bufferRow is lastBufferRow wrapCount++ id = bufferRow + '-' + wrapCount @@ -605,8 +607,9 @@ class TextEditorPresenter decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} + tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable, zIndex} visibleLineNumberIds[id] = true + zIndex-- for id of tileState.lineNumbers delete tileState.lineNumbers[id] unless visibleLineNumberIds[id] From 707fbf255256681f59317a5d8c69be92d4d4de38 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 27 Aug 2015 11:26:22 -0400 Subject: [PATCH 138/198] :arrow_up: language-c@0.47.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7796b7d9a..3a76b9943 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "welcome": "0.30.0", "whitespace": "0.30.0", "wrap-guide": "0.35.0", - "language-c": "0.47.0", + "language-c": "0.47.1", "language-clojure": "0.16.0", "language-coffee-script": "0.41.0", "language-csharp": "0.7.0", From 2d28be38d841f6ca43c07d0e24bf50510f334029 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 27 Aug 2015 11:36:23 -0400 Subject: [PATCH 139/198] :arrow_up: language-javascript@0.91.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a76b9943..aee134c98 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "language-html": "0.41.0", "language-hyperlink": "0.14.0", "language-java": "0.16.0", - "language-javascript": "0.90.0", + "language-javascript": "0.91.0", "language-json": "0.16.0", "language-less": "0.28.2", "language-make": "0.17.0", From 20aa3a6a8731199d2fb0cc4791554c5a40e86df5 Mon Sep 17 00:00:00 2001 From: Natthu Bharambe Date: Thu, 27 Aug 2015 08:38:05 -0700 Subject: [PATCH 140/198] Fix nits with the spec. Summary: Test Plan: Reviewers: CC: Task ID: # Blame Rev: --- spec/text-editor-presenter-spec.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 17140a18b..72663f855 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2300,14 +2300,16 @@ describe "TextEditorPresenter", -> expect(decorationState[decoration3.id]).toBeUndefined() - it "updates when another gutter with higher priority is hidden", -> - editor.addGutter({name: 'test-gutter-1', priority: -150, visible: false}) + it "updates all the gutters, even when a gutter with higher priority is hidden", -> + hiddenGutter = {name: 'test-gutter-1', priority: -150, visible: false} + editor.addGutter(hiddenGutter) # This update will scroll decoration1 out of view, and decoration3 into view. expectStateUpdate presenter, -> presenter.setScrollTop(scrollTop + lineHeight * 5) decorationState = getContentForGutterWithName(presenter, 'test-gutter') expect(decorationState[decoration1.id]).toBeUndefined() + expect(decorationState[decoration3.id].top).toBeDefined() it "updates when ::scrollTop changes", -> # This update will scroll decoration1 out of view, and decoration3 into view. From 465aceadfc935a774967a7807107b1cdc96c0c23 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 27 Aug 2015 12:52:39 -0400 Subject: [PATCH 141/198] :arrow_up: language-html@0.41.1 Fixes failing specs due to a recent language-javascript change --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aee134c98..9c2731fa9 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "language-gfm": "0.80.0", "language-git": "0.10.0", "language-go": "0.37.0", - "language-html": "0.41.0", + "language-html": "0.41.1", "language-hyperlink": "0.14.0", "language-java": "0.16.0", "language-javascript": "0.91.0", From 653680c297a6ed950317e9ee6379c856a4490941 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 27 Aug 2015 10:28:10 -0700 Subject: [PATCH 142/198] Revert ":arrow_up: language-html@0.41.1" This reverts commit 465aceadfc935a774967a7807107b1cdc96c0c23. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c2731fa9..aee134c98 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "language-gfm": "0.80.0", "language-git": "0.10.0", "language-go": "0.37.0", - "language-html": "0.41.1", + "language-html": "0.41.0", "language-hyperlink": "0.14.0", "language-java": "0.16.0", "language-javascript": "0.91.0", From 9cb70ffa46df48f1ba06daeca36389689f4c42f2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 27 Aug 2015 10:28:31 -0700 Subject: [PATCH 143/198] Revert ":arrow_up: language-javascript@0.91.0" This reverts commit 2d28be38d841f6ca43c07d0e24bf50510f334029. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aee134c98..3a76b9943 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "language-html": "0.41.0", "language-hyperlink": "0.14.0", "language-java": "0.16.0", - "language-javascript": "0.91.0", + "language-javascript": "0.90.0", "language-json": "0.16.0", "language-less": "0.28.2", "language-make": "0.17.0", From 314e082f1db2867ced4ac114327ebd0a850177c8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 27 Aug 2015 17:40:01 -0600 Subject: [PATCH 144/198] Clean up max scroll margin code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we were attempting the same computation twice, once incorrectly when returning the scroll margins as integers and then doing the same thing in terms of pixels. This just cleans up the original calculation. It’s *slightly* different because it takes the floor to continue returning integers, but the behavior is extremely close. --- spec/text-editor-presenter-spec.coffee | 6 +++--- src/display-buffer.coffee | 17 +++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index f859a37a0..44b1035fc 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1214,7 +1214,7 @@ describe "TextEditorPresenter", -> # showing expectStateUpdate presenter, -> editor.getSelections()[1].clear() - expect(stateForCursor(presenter, 1)).toEqual {top: 5, left: 5 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 1)).toEqual {top: 0, left: 5 * 10, width: 10, height: 10} # hiding expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 5]]) @@ -1226,11 +1226,11 @@ describe "TextEditorPresenter", -> # adding expectStateUpdate presenter, -> editor.addCursorAtBufferPosition([4, 4]) - expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 4 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 2)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10} # moving added cursor expectStateUpdate presenter, -> editor.getCursors()[2].setBufferPosition([4, 6]) - expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 6 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 2)).toEqual {top: 0, left: 6 * 10, width: 10, height: 10} # destroying destroyedCursor = editor.getCursors()[2] diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index c1e80c576..5d5628689 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -177,21 +177,18 @@ class DisplayBuffer extends Model # visible - A {Boolean} indicating of the tokenized buffer is shown setVisible: (visible) -> @tokenizedBuffer.setVisible(visible) - getVerticalScrollMargin: -> Math.min(@verticalScrollMargin, (@getHeight() - @getLineHeightInPixels()) / 2) + getVerticalScrollMargin: -> + maxScrollMargin = Math.floor(((@getHeight() / @getLineHeightInPixels()) - 1) / 2) + Math.min(@verticalScrollMargin, maxScrollMargin) + setVerticalScrollMargin: (@verticalScrollMargin) -> @verticalScrollMargin - getVerticalScrollMarginInPixels: -> - scrollMarginInPixels = @getVerticalScrollMargin() * @getLineHeightInPixels() - maxScrollMarginInPixels = (@getHeight() - @getLineHeightInPixels()) / 2 - Math.min(scrollMarginInPixels, maxScrollMarginInPixels) + getVerticalScrollMarginInPixels: -> @getVerticalScrollMargin() * @getLineHeightInPixels() - getHorizontalScrollMargin: -> Math.min(@horizontalScrollMargin, (@getWidth() - @getDefaultCharWidth()) / 2) + getHorizontalScrollMargin: -> Math.min(@horizontalScrollMargin, Math.floor(((@getWidth() / @getDefaultCharWidth()) - 1) / 2)) setHorizontalScrollMargin: (@horizontalScrollMargin) -> @horizontalScrollMargin - getHorizontalScrollMarginInPixels: -> - scrollMarginInPixels = @getHorizontalScrollMargin() * @getDefaultCharWidth() - maxScrollMarginInPixels = (@getWidth() - @getDefaultCharWidth()) / 2 - Math.min(scrollMarginInPixels, maxScrollMarginInPixels) + getHorizontalScrollMarginInPixels: -> scrollMarginInPixels = @getHorizontalScrollMargin() * @getDefaultCharWidth() getHorizontalScrollbarHeight: -> @horizontalScrollbarHeight setHorizontalScrollbarHeight: (@horizontalScrollbarHeight) -> @horizontalScrollbarHeight From b61a4501703fc628ed0eb382850f2fe18a808c27 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 27 Aug 2015 16:48:18 -0700 Subject: [PATCH 145/198] Prepare 1.0.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a76b9943..adff56441 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.0.8", + "version": "1.0.9", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 340fe7e534cd75edd1a9231f9b789b4a902e3b4d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 Aug 2015 09:45:55 +0200 Subject: [PATCH 146/198] :memo: Fix wording on specs Thanks, @maxbrunsfeld! :fireworks: --- spec/text-editor-component-spec.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index bd6fd9600..5c2bf079b 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -103,7 +103,7 @@ describe "TextEditorComponent", -> expect(linesNode.getBoundingClientRect().height).toBe(3.5 * lineHeightInPixels) - it "renders tiles upper in the stack in front of the ones below", -> + it "renders higher tiles in front of lower ones", -> wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' component.measureDimensions() nextAnimationFrame() @@ -601,7 +601,7 @@ describe "TextEditorComponent", -> expect(lineNode.offsetTop).toBe(top) expect(lineNode.textContent).toBe(text) - it "renders tiles upper in the stack in front of the ones below", -> + it "renders higher tiles in front of lower ones", -> wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' component.measureDimensions() nextAnimationFrame() @@ -623,7 +623,7 @@ describe "TextEditorComponent", -> expect(tilesNodes[2].style.zIndex).toBe("1") expect(tilesNodes[3].style.zIndex).toBe("0") - it "renders lines upper in the stack in front of the ones below in each tile", -> + it "renders higher line numbers in front of lower ones", -> wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' component.measureDimensions() nextAnimationFrame() From fac979f71d2cd2396ffb8d1246d70d466f074909 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 11:16:23 +0300 Subject: [PATCH 147/198] :memo: Update docs for TextEditor::addGutter TextEditor::addGutter should be called directly. --- src/gutter-container.coffee | 8 -------- src/text-editor.coffee | 8 +++++++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/gutter-container.coffee b/src/gutter-container.coffee index 4fa7c61fb..bc8ce4246 100644 --- a/src/gutter-container.coffee +++ b/src/gutter-container.coffee @@ -21,14 +21,6 @@ class GutterContainer @gutters = [] @emitter.dispose() - # Creates and returns a {Gutter}. - # * `options` An {Object} with the following fields: - # * `name` (required) A unique {String} to identify this gutter. - # * `priority` (optional) A {Number} that determines stacking order between - # gutters. Lower priority items are forced closer to the edges of the - # window. (default: -100) - # * `visible` (optional) {Boolean} specifying whether the gutter is visible - # initially after being created. (default: true) addGutter: (options) -> options = options ? {} gutterName = options.name diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 5f6408c30..2a69e8a10 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -515,7 +515,13 @@ class TextEditor extends Model @emitter.on 'did-change-line-number-gutter-visible', callback # Public: Creates and returns a {Gutter}. - # See {GutterContainer::addGutter} for more details. + # * `options` An {Object} with the following fields: + # * `name` (required) A unique {String} to identify this gutter. + # * `priority` (optional) A {Number} that determines stacking order between + # gutters. Lower priority items are forced closer to the edges of the + # window. (default: -100) + # * `visible` (optional) {Boolean} specifying whether the gutter is visible + # initially after being created. (default: true) addGutter: (options) -> @gutterContainer.addGutter(options) From fbb3bc42326ace82ac4b8e7ec6cef15bc13e33d1 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 11:44:31 +0300 Subject: [PATCH 148/198] :memo: Make Gutter class extended Gutter is returned from a public method `TextEditor::addGutter` --- src/gutter.coffee | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index 0b3b6533b..d3ea16c95 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -1,19 +1,14 @@ {Emitter} = require 'event-kit' -# Public: This class represents a gutter within a TextEditor. - DefaultPriority = -100 +# Extended: Represents a gutter within a {TextEditor}. +# +# ### Gutter Creation +# +# See {TextEditor::addGutter} for usage. module.exports = class Gutter - # * `gutterContainer` The {GutterContainer} object to which this gutter belongs. - # * `options` An {Object} with the following fields: - # * `name` (required) A unique {String} to identify this gutter. - # * `priority` (optional) A {Number} that determines stacking order between - # gutters. Lower priority items are forced closer to the edges of the - # window. (default: -100) - # * `visible` (optional) {Boolean} specifying whether the gutter is visible - # initially after being created. (default: true) constructor: (gutterContainer, options) -> @gutterContainer = gutterContainer @name = options?.name From bb447099c07a0f1ea5463fe5d213f8288f07dd9c Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 10:44:59 +0300 Subject: [PATCH 149/198] :art: Move Gutter event methods up Need for generated docs --- src/gutter.coffee | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index d3ea16c95..23012b9f1 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -25,6 +25,23 @@ class Gutter @emitter.emit 'did-destroy' @emitter.dispose() + # Calls your `callback` when the {Gutter}'s' visibility changes. + # + # * `callback` {Function} + # * `gutter` The {Gutter} whose visibility changed. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChangeVisible: (callback) -> + @emitter.on 'did-change-visible', callback + + # Calls your `callback` when the {Gutter} is destroyed + # + # * `callback` {Function} + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidDestroy: (callback) -> + @emitter.on 'did-destroy', callback + hide: -> if @visible @visible = false @@ -47,20 +64,3 @@ class Gutter # Returns a {Decoration} object. decorateMarker: (marker, options) -> @gutterContainer.addGutterDecoration(this, marker, options) - - # Calls your `callback` when the {Gutter}'s' visibility changes. - # - # * `callback` {Function} - # * `gutter` The {Gutter} whose visibility changed. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeVisible: (callback) -> - @emitter.on 'did-change-visible', callback - - # Calls your `callback` when the {Gutter} is destroyed - # - # * `callback` {Function} - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy: (callback) -> - @emitter.on 'did-destroy', callback From a5edd69ec33bba8e4a73d3afafc5c40fb280a590 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 10:46:17 +0300 Subject: [PATCH 150/198] :memo: Update docs for Gutter events --- src/gutter.coffee | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index 23012b9f1..90e5feb29 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -25,16 +25,20 @@ class Gutter @emitter.emit 'did-destroy' @emitter.dispose() - # Calls your `callback` when the {Gutter}'s' visibility changes. + ### + Section: Event Subscription + ### + + # Public: Calls your `callback` when the gutter's visibility changes. # # * `callback` {Function} - # * `gutter` The {Gutter} whose visibility changed. + # * `gutter` The gutter whose visibility changed. # # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidChangeVisible: (callback) -> @emitter.on 'did-change-visible', callback - # Calls your `callback` when the {Gutter} is destroyed + # Public: Calls your `callback` when the gutter is destroyed. # # * `callback` {Function} # From 150300b63e20a2415ce9483514533a5ed884c49b Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 10:46:50 +0300 Subject: [PATCH 151/198] :memo: Make Gutter visibility methods public --- src/gutter.coffee | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gutter.coffee b/src/gutter.coffee index 90e5feb29..adcad21be 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -46,16 +46,23 @@ class Gutter onDidDestroy: (callback) -> @emitter.on 'did-destroy', callback + ### + Section: Visibility + ### + + # Public: Hide the gutter. hide: -> if @visible @visible = false @emitter.emit 'did-change-visible', this + # Public: Show the gutter. show: -> if not @visible @visible = true @emitter.emit 'did-change-visible', this + # Public: Returns the visibility of the gutter. isVisible: -> @visible From 71c9a4afe75212bfe413ff14b5490c6f2044f34e Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 10:47:51 +0300 Subject: [PATCH 152/198] :memo: Add docs for Gutter::destroy --- src/gutter.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gutter.coffee b/src/gutter.coffee index adcad21be..3a98472d2 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -17,6 +17,11 @@ class Gutter @emitter = new Emitter + ### + Section: Gutter Destruction + ### + + # Public: Destroys the gutter. destroy: -> if @name is 'line-number' throw new Error('The line-number gutter cannot be destroyed.') From 5f1c227febaab3e5a6ac43af092ae1319cd3b7a8 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 11:24:01 +0300 Subject: [PATCH 153/198] :memo: Make Gutter event methods essential --- src/text-editor.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 2a69e8a10..ab38d2df4 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -533,7 +533,7 @@ class TextEditor extends Model gutterWithName: (name) -> @gutterContainer.gutterWithName(name) - # Calls your `callback` when a {Gutter} is added to the editor. + # Essential: Calls your `callback` when a {Gutter} is added to the editor. # Immediately calls your callback for each existing gutter. # # * `callback` {Function} @@ -543,7 +543,7 @@ class TextEditor extends Model observeGutters: (callback) -> @gutterContainer.observeGutters callback - # Calls your `callback` when a {Gutter} is added to the editor. + # Essential: Calls your `callback` when a {Gutter} is added to the editor. # # * `callback` {Function} # * `gutter` {Gutter} that was added. @@ -552,7 +552,7 @@ class TextEditor extends Model onDidAddGutter: (callback) -> @gutterContainer.onDidAddGutter callback - # Calls your `callback` when a {Gutter} is removed from the editor. + # Essential: Calls your `callback` when a {Gutter} is removed from the editor. # # * `callback` {Function} # * `name` The name of the {Gutter} that was removed. From 559251a1c82edf3296dc92b07379a828700c0152 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 11:19:21 +0300 Subject: [PATCH 154/198] :art: Add section for Gutter methods Need for generated docs --- src/text-editor.coffee | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index ab38d2df4..083aef59c 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -514,25 +514,6 @@ class TextEditor extends Model onDidChangeLineNumberGutterVisible: (callback) -> @emitter.on 'did-change-line-number-gutter-visible', callback - # Public: Creates and returns a {Gutter}. - # * `options` An {Object} with the following fields: - # * `name` (required) A unique {String} to identify this gutter. - # * `priority` (optional) A {Number} that determines stacking order between - # gutters. Lower priority items are forced closer to the edges of the - # window. (default: -100) - # * `visible` (optional) {Boolean} specifying whether the gutter is visible - # initially after being created. (default: true) - addGutter: (options) -> - @gutterContainer.addGutter(options) - - # Public: Returns the {Array} of all gutters on this editor. - getGutters: -> - @gutterContainer.getGutters() - - # Public: Returns the {Gutter} with the given name, or null if it doesn't exist. - gutterWithName: (name) -> - @gutterContainer.gutterWithName(name) - # Essential: Calls your `callback` when a {Gutter} is added to the editor. # Immediately calls your callback for each existing gutter. # @@ -2840,6 +2821,29 @@ class TextEditor extends Model outermostFoldsInBufferRowRange: (startRow, endRow) -> @displayBuffer.outermostFoldsInBufferRowRange(startRow, endRow) + ### + Section: Gutters + ### + + # Public: Creates and returns a {Gutter}. + # * `options` An {Object} with the following fields: + # * `name` (required) A unique {String} to identify this gutter. + # * `priority` (optional) A {Number} that determines stacking order between + # gutters. Lower priority items are forced closer to the edges of the + # window. (default: -100) + # * `visible` (optional) {Boolean} specifying whether the gutter is visible + # initially after being created. (default: true) + addGutter: (options) -> + @gutterContainer.addGutter(options) + + # Public: Returns the {Array} of all gutters on this editor. + getGutters: -> + @gutterContainer.getGutters() + + # Public: Returns the gutter with the given name, or null if it doesn't exist. + gutterWithName: (name) -> + @gutterContainer.gutterWithName(name) + ### Section: Scrolling the TextEditor ### From 1f259a5583523d037032b91a8f34fa922543c9dd Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 10:52:00 +0300 Subject: [PATCH 155/198] :memo: Update docs for TextEditor::decorateMarker Decorations of `type: 'gutter'` have been renamed to `type: 'line-number'` --- src/text-editor.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 083aef59c..481b939e4 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1279,7 +1279,7 @@ class TextEditor extends Model # # * __line__: Adds your CSS `class` to the line nodes within the range # marked by the marker - # * __gutter__: Adds your CSS `class` to the line number nodes within the + # * __line-number__: Adds your CSS `class` to the line number nodes within the # range marked by the marker # * __highlight__: Adds a new highlight div to the editor surrounding the # range marked by the marker. When the user selects text, the selection is @@ -1297,9 +1297,9 @@ class TextEditor extends Model # * `marker` A {Marker} you want this decoration to follow. # * `decorationParams` An {Object} representing the decoration e.g. # `{type: 'line-number', class: 'linter-error'}` - # * `type` There are a few supported decoration types: `gutter`, `line`, + # * `type` There are a few supported decoration types: `line-number`, `line`, # `highlight`, and `overlay`. The behavior of the types are as follows: - # * `gutter` Adds the given `class` to the line numbers overlapping the + # * `line-number` Adds the given `class` to the line numbers overlapping the # rows spanned by the marker. # * `line` Adds the given `class` to the lines overlapping the rows # spanned by the marker. @@ -1311,14 +1311,14 @@ class TextEditor extends Model # * `class` This CSS class will be applied to the decorated line number, # line, highlight, or overlay. # * `onlyHead` (optional) If `true`, the decoration will only be applied to - # the head of the marker. Only applicable to the `line` and `gutter` + # the head of the marker. Only applicable to the `line` and `line-number` # types. # * `onlyEmpty` (optional) If `true`, the decoration will only be applied if # the associated marker is empty. Only applicable to the `line` and - # `gutter` types. + # `line-number` types. # * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied # if the associated marker is non-empty. Only applicable to the `line` - # and gutter types. + # and `line-number` types. # * `position` (optional) Only applicable to decorations of type `overlay`, # controls where the overlay view is positioned relative to the marker. # Values can be `'head'` (the default), or `'tail'`. From 6076cef1f239f84ecda07b2975b5ec0adc595966 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 10:53:06 +0300 Subject: [PATCH 156/198] :memo: Remove `gutterName` argument from TextEditor::decorateMarker Decorations of `type: 'gutter'` is deprecated. Use `Gutter::decorateMarker` to create decoration for custom gutter. --- src/text-editor.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 481b939e4..4b7c85d21 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1322,8 +1322,6 @@ class TextEditor extends Model # * `position` (optional) Only applicable to decorations of type `overlay`, # controls where the overlay view is positioned relative to the marker. # Values can be `'head'` (the default), or `'tail'`. - # * `gutterName` (optional) Only applicable to the `gutter` type. If provided, - # the decoration will be applied to the gutter with the specified name. # # Returns a {Decoration} object decorateMarker: (marker, decorationParams) -> From df2fed2474300bc4e87caa9f5a90723b13b67681 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 11:48:12 +0300 Subject: [PATCH 157/198] :memo: Update docs for Gutter::decorateMarker Docs from TextEditor::decorateMarker related to Gutter --- src/gutter.coffee | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index 3a98472d2..434b9003c 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -71,12 +71,22 @@ class Gutter isVisible: -> @visible - # * `marker` (required) A Marker object. - # * `options` (optional) An object with the following fields: - # * `class` (optional) - # * `item` (optional) A model {Object} with a corresponding view registered, - # or an {HTMLElement}. + # Public: Adds a decoration that tracks a {Marker}. When the marker moves, + # is invalidated, or is destroyed, the decoration will be updated to reflect + # the marker's state. # - # Returns a {Decoration} object. + # ## Arguments + # + # * `marker` A {Marker} you want this decoration to follow. + # * `decorationParams` An {Object} representing the decoration + # * `class` This CSS class will be applied to the decorated line number. + # * `onlyHead` (optional) If `true`, the decoration will only be applied to + # the head of the marker. + # * `onlyEmpty` (optional) If `true`, the decoration will only be applied if + # the associated marker is empty. + # * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied + # if the associated marker is non-empty. + # + # Returns a {Decoration} object decorateMarker: (marker, options) -> @gutterContainer.addGutterDecoration(this, marker, options) From 93fba6a334cd2e384e4e0127e758e8b2e7a3bf97 Mon Sep 17 00:00:00 2001 From: Maxim Sokolov Date: Wed, 26 Aug 2015 11:37:19 +0300 Subject: [PATCH 158/198] :memo: Remove redundant docs GutterContainer class shouldn't be public or extended. --- src/gutter-container.coffee | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/gutter-container.coffee b/src/gutter-container.coffee index bc8ce4246..084e1e1ad 100644 --- a/src/gutter-container.coffee +++ b/src/gutter-container.coffee @@ -1,12 +1,8 @@ {Emitter} = require 'event-kit' Gutter = require './gutter' -# This class encapsulates the logic for adding and modifying a set of gutters. - module.exports = class GutterContainer - - # * `textEditor` The {TextEditor} to which this {GutterContainer} belongs. constructor: (textEditor) -> @gutters = [] @textEditor = textEditor @@ -51,20 +47,13 @@ class GutterContainer if gutter.name is name then return gutter null - ### - Section: Event Subscription - ### - - # See {TextEditor::observeGutters} for details. observeGutters: (callback) -> callback(gutter) for gutter in @getGutters() @onDidAddGutter callback - # See {TextEditor::onDidAddGutter} for details. onDidAddGutter: (callback) -> @emitter.on 'did-add-gutter', callback - # See {TextEditor::onDidRemoveGutter} for details. onDidRemoveGutter: (callback) -> @emitter.on 'did-remove-gutter', callback From ba7af0b9cd3fe5f9c01bdc3a15628749e675740b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 09:39:03 -0600 Subject: [PATCH 159/198] Remove arguments subheading --- src/text-editor.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 4b7c85d21..e5cb0dabe 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1292,8 +1292,6 @@ class TextEditor extends Model # # ``` # - # ## Arguments - # # * `marker` A {Marker} you want this decoration to follow. # * `decorationParams` An {Object} representing the decoration e.g. # `{type: 'line-number', class: 'linter-error'}` From bd4f5ba544f6888afa95b44ff55db735dab8c954 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 09:39:24 -0600 Subject: [PATCH 160/198] Replace Public with Essential MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Still not sure about the value of this distinction, but let’s blend in. --- src/text-editor.coffee | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index e5cb0dabe..39da338f3 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -14,7 +14,7 @@ TextMateScopeSelector = require('first-mate').ScopeSelector {Directory} = require "pathwatcher" GutterContainer = require './gutter-container' -# Public: This class represents all essential editing state for a single +# Essential: This class represents all essential editing state for a single # {TextBuffer}, including cursor and selection positions, folds, and soft wraps. # If you're manipulating the state of an editor, use this class. If you're # interested in the visual appearance of editors, use {TextEditorView} instead. @@ -341,7 +341,7 @@ class TextEditor extends Model onDidInsertText: (callback) -> @emitter.on 'did-insert-text', callback - # Public: Invoke the given callback after the buffer is saved to disk. + # Essential: Invoke the given callback after the buffer is saved to disk. # # * `callback` {Function} to be called after the buffer is saved. # * `event` {Object} with the following keys: @@ -351,7 +351,7 @@ class TextEditor extends Model onDidSave: (callback) -> @getBuffer().onDidSave(callback) - # Public: Invoke the given callback when the editor is destroyed. + # Essential: Invoke the given callback when the editor is destroyed. # # * `callback` {Function} to be called when the editor is destroyed. # @@ -470,7 +470,7 @@ class TextEditor extends Model onDidUpdateMarkers: (callback) -> @displayBuffer.onDidUpdateMarkers(callback) - # Public: Retrieves the current {TextBuffer}. + # Essential: Retrieves the current {TextBuffer}. getBuffer: -> @buffer # Retrieves the current buffer's URI. @@ -616,7 +616,7 @@ class TextEditor extends Model # See {TextBuffer::save} for more details. save: -> @buffer.save(backup: atom.config.get('editor.backUpBeforeSaving')) - # Public: Saves the editor's text buffer as the given path. + # Essential: Saves the editor's text buffer as the given path. # # See {TextBuffer::saveAs} for more details. # @@ -729,7 +729,7 @@ class TextEditor extends Model # {Delegates to: TextBuffer.getEndPosition} getEofBufferPosition: -> @buffer.getEndPosition() - # Public: Get the {Range} of the paragraph surrounding the most recently added + # Essential: Get the {Range} of the paragraph surrounding the most recently added # cursor. # # Returns a {Range}. @@ -1328,7 +1328,7 @@ class TextEditor extends Model decorationParams.type = 'line-number' @displayBuffer.decorateMarker(marker, decorationParams) - # Public: Get all the decorations within a screen row range. + # Essential: Get all the decorations within a screen row range. # # * `startScreenRow` the {Number} beginning screen row # * `endScreenRow` the {Number} end screen row (inclusive) @@ -2308,7 +2308,7 @@ class TextEditor extends Model # * `replace` Call this {Function} with a {String} to replace the match. scan: (regex, iterator) -> @buffer.scan(regex, iterator) - # Public: Scan regular expression matches in a given range, calling the given + # Essential: Scan regular expression matches in a given range, calling the given # iterator function on each match. # # * `regex` A {RegExp} to search for. @@ -2322,7 +2322,7 @@ class TextEditor extends Model # * `replace` Call this {Function} with a {String} to replace the match. scanInBufferRange: (regex, range, iterator) -> @buffer.scanInRange(regex, range, iterator) - # Public: Scan regular expression matches in a given range in reverse order, + # Essential: Scan regular expression matches in a given range in reverse order, # calling the given iterator function on each match. # # * `regex` A {RegExp} to search for. @@ -2434,7 +2434,7 @@ class TextEditor extends Model # Returns a {Boolean}. toggleSoftWrapped: -> @setSoftWrapped(not @isSoftWrapped()) - # Public: Gets the column at which column will soft wrap + # Essential: Gets the column at which column will soft wrap getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn() ### @@ -2669,7 +2669,7 @@ class TextEditor extends Model @emit('did-insert-text', didInsertEvent) if includeDeprecatedAPIs @emitter.emit 'did-insert-text', didInsertEvent - # Public: For each selection, if the selection is empty, cut all characters + # Essential: For each selection, if the selection is empty, cut all characters # of the containing line following the cursor. Otherwise cut the selected # text. cutToEndOfLine: -> @@ -2821,7 +2821,7 @@ class TextEditor extends Model Section: Gutters ### - # Public: Creates and returns a {Gutter}. + # Essential: Creates and returns a {Gutter}. # * `options` An {Object} with the following fields: # * `name` (required) A unique {String} to identify this gutter. # * `priority` (optional) A {Number} that determines stacking order between @@ -2832,11 +2832,11 @@ class TextEditor extends Model addGutter: (options) -> @gutterContainer.addGutter(options) - # Public: Returns the {Array} of all gutters on this editor. + # Essential: Returns the {Array} of all gutters on this editor. getGutters: -> @gutterContainer.getGutters() - # Public: Returns the gutter with the given name, or null if it doesn't exist. + # Essential: Returns the gutter with the given name, or null if it doesn't exist. gutterWithName: (name) -> @gutterContainer.gutterWithName(name) @@ -2940,13 +2940,13 @@ class TextEditor extends Model Section: TextEditor Rendering ### - # Public: Retrieves the greyed out placeholder of a mini editor. + # Essential: Retrieves the greyed out placeholder of a mini editor. # # Returns a {String}. getPlaceholderText: -> @placeholderText - # Public: Set the greyed out placeholder of a mini editor. Placeholder text + # Essential: Set the greyed out placeholder of a mini editor. Placeholder text # will be displayed when the editor has no content. # # * `placeholderText` {String} text that is displayed when the editor has no content. From 313eb9e926690d2926921b82176d34f78bec3264 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 09:41:13 -0600 Subject: [PATCH 161/198] :art: --- src/gutter.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index 434b9003c..e564bc898 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -4,9 +4,7 @@ DefaultPriority = -100 # Extended: Represents a gutter within a {TextEditor}. # -# ### Gutter Creation -# -# See {TextEditor::addGutter} for usage. +# See {TextEditor::addGutter} for information on creating a gutter. module.exports = class Gutter constructor: (gutterContainer, options) -> From 1160c571a75b76d3b6d8ec0182f2f9d602a6175b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 09:41:23 -0600 Subject: [PATCH 162/198] Public -> Essential --- src/gutter.coffee | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index e564bc898..eb93ac25e 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -19,7 +19,7 @@ class Gutter Section: Gutter Destruction ### - # Public: Destroys the gutter. + # Essential: Destroys the gutter. destroy: -> if @name is 'line-number' throw new Error('The line-number gutter cannot be destroyed.') @@ -32,7 +32,7 @@ class Gutter Section: Event Subscription ### - # Public: Calls your `callback` when the gutter's visibility changes. + # Essential: Calls your `callback` when the gutter's visibility changes. # # * `callback` {Function} # * `gutter` The gutter whose visibility changed. @@ -41,7 +41,7 @@ class Gutter onDidChangeVisible: (callback) -> @emitter.on 'did-change-visible', callback - # Public: Calls your `callback` when the gutter is destroyed. + # Essential: Calls your `callback` when the gutter is destroyed. # # * `callback` {Function} # @@ -53,23 +53,23 @@ class Gutter Section: Visibility ### - # Public: Hide the gutter. + # Essential: Hide the gutter. hide: -> if @visible @visible = false @emitter.emit 'did-change-visible', this - # Public: Show the gutter. + # Essential: Show the gutter. show: -> if not @visible @visible = true @emitter.emit 'did-change-visible', this - # Public: Returns the visibility of the gutter. + # Essential: Returns the visibility of the gutter. isVisible: -> @visible - # Public: Adds a decoration that tracks a {Marker}. When the marker moves, + # Essential: Adds a decoration that tracks a {Marker}. When the marker moves, # is invalidated, or is destroyed, the decoration will be updated to reflect # the marker's state. # From f2a017d0136ba3717bdc5261279849824d706b8b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 09:42:39 -0600 Subject: [PATCH 163/198] Nix arguments --- src/gutter.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index eb93ac25e..0c09cdf3e 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -73,8 +73,6 @@ class Gutter # is invalidated, or is destroyed, the decoration will be updated to reflect # the marker's state. # - # ## Arguments - # # * `marker` A {Marker} you want this decoration to follow. # * `decorationParams` An {Object} representing the decoration # * `class` This CSS class will be applied to the decorated line number. From 1dac9eb80168b8024329a2ba03b22dcdb57980f9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 09:42:46 -0600 Subject: [PATCH 164/198] Use imperative voice --- src/gutter.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gutter.coffee b/src/gutter.coffee index 0c09cdf3e..160036e3e 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -65,11 +65,13 @@ class Gutter @visible = true @emitter.emit 'did-change-visible', this - # Essential: Returns the visibility of the gutter. + # Essential: Determine whether the gutter is visible. + # + # Returns a {Boolean}. isVisible: -> @visible - # Essential: Adds a decoration that tracks a {Marker}. When the marker moves, + # Essential: Add a decoration that tracks a {Marker}. When the marker moves, # is invalidated, or is destroyed, the decoration will be updated to reflect # the marker's state. # From 0a87dad74762b6e6b6b1d21930811d9077d8d6e5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 09:46:18 -0600 Subject: [PATCH 165/198] :art: --- src/text-editor.coffee | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 39da338f3..b8c2a76fb 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2821,7 +2821,8 @@ class TextEditor extends Model Section: Gutters ### - # Essential: Creates and returns a {Gutter}. + # Essential: Add a custom {Gutter}. + # # * `options` An {Object} with the following fields: # * `name` (required) A unique {String} to identify this gutter. # * `priority` (optional) A {Number} that determines stacking order between @@ -2829,14 +2830,20 @@ class TextEditor extends Model # window. (default: -100) # * `visible` (optional) {Boolean} specifying whether the gutter is visible # initially after being created. (default: true) + # + # Returns the newly-created {Gutter}. addGutter: (options) -> @gutterContainer.addGutter(options) - # Essential: Returns the {Array} of all gutters on this editor. + # Essential: Get this editor's gutters. + # + # Returns an {Array} of {Gutter}s. getGutters: -> @gutterContainer.getGutters() - # Essential: Returns the gutter with the given name, or null if it doesn't exist. + # Essential: Get the gutter with the given name. + # + # Returns a {Gutter}, or `null` if no gutter exists for the given name. gutterWithName: (name) -> @gutterContainer.gutterWithName(name) From 80fca8bd28bf90d757080f8918da7d21b6c1028d Mon Sep 17 00:00:00 2001 From: "Ryan P.C. McQuen" Date: Fri, 28 Aug 2015 07:06:48 -0700 Subject: [PATCH 166/198] Add that atom can be built with iojs 2.x Confirmed here: https://github.com/atom/atom/issues/8508 Signed-off-by: Ryan P.C. McQuen --- 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 fd53b9612..be3e02846 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/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.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 7201676b1..7273a29fd 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/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.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 b64f178cb..0ae063493 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/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.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/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x) + * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x) * [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp)) * [GitHub for Windows](http://windows.github.com/) From b2e8b05cbe0f68d4a2e5d3f2a0790ad9375d53c5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 10:19:40 -0600 Subject: [PATCH 167/198] :arrow_up: tabs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index adff56441..fcf8118ee 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "status-bar": "0.78.0", "styleguide": "0.44.0", "symbols-view": "0.103.0", - "tabs": "0.82.0", + "tabs": "0.84.0", "timecop": "0.31.0", "tree-view": "0.186.0", "update-package-dependencies": "0.10.0", From 567199a3d26c795ff29465f110109de7e0dcbc9c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 27 Aug 2015 18:57:06 -0600 Subject: [PATCH 168/198] Autoscroll the editor as a quadratic function of mouse cursor overshoot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was a spike and still needs tests, but it’s going to be way better. --- src/selection.coffee | 26 +++++++------- src/text-editor-component.coffee | 62 +++++++++++++++++++++++--------- src/text-editor.coffee | 6 ++-- 3 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/selection.coffee b/src/selection.coffee index 6c6609a38..0e565cf33 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -190,7 +190,7 @@ class Selection extends Model # position. # # * `position` An instance of {Point}, with a given `row` and `column`. - selectToScreenPosition: (position) -> + selectToScreenPosition: (position, options) -> position = Point.fromObject(position) @modifySelection => @@ -200,12 +200,12 @@ class Selection extends Model else @marker.setScreenRange([@initialScreenRange.start, position], reversed: false) else - @cursor.setScreenPosition(position) + @cursor.setScreenPosition(position, options) if @linewise - @expandOverLine() + @expandOverLine(options) else if @wordwise - @expandOverWord() + @expandOverWord(options) # Public: Selects the text from the current cursor position to a given buffer # position. @@ -311,28 +311,28 @@ class Selection extends Model # Public: Modifies the selection to encompass the current word. # # Returns a {Range}. - selectWord: -> - options = {} + selectWord: (options={}) -> options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace() if @cursor.isBetweenWordAndNonWord() options.includeNonWordCharacters = false - @setBufferRange(@cursor.getCurrentWordBufferRange(options)) + @setBufferRange(@cursor.getCurrentWordBufferRange(options), options) @wordwise = true @initialScreenRange = @getScreenRange() # Public: Expands the newest selection to include the entire word on which # the cursors rests. - expandOverWord: -> + expandOverWord: (options) -> @setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()), autoscroll: false) - @cursor.autoscroll() + @cursor.autoscroll() if options?.autoscroll ? true # Public: Selects an entire line in the buffer. # # * `row` The line {Number} to select (default: the row of the cursor). - selectLine: (row=@cursor.getBufferPosition().row) -> + selectLine: (row, options) -> + row ?= @cursor.getBufferPosition().row range = @editor.bufferRangeForBufferRow(row, includeNewline: true) - @setBufferRange(@getBufferRange().union(range), autoscroll: true) + @setBufferRange(@getBufferRange().union(range), options) @linewise = true @wordwise = false @initialScreenRange = @getScreenRange() @@ -341,10 +341,10 @@ class Selection extends Model # the cursor currently rests. # # It also includes the newline character. - expandOverLine: -> + expandOverLine: (options) -> range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true)) @setBufferRange(range, autoscroll: false) - @cursor.autoscroll() + @cursor.autoscroll() if options?.autoscroll ? true ### Section: Modifying the selected text diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 81f92476f..7382cebcc 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -395,16 +395,16 @@ class TextEditorComponent if cursorAtScreenPosition and @editor.hasMultipleCursors() cursorAtScreenPosition.destroy() else - @editor.addCursorAtScreenPosition(screenPosition) + @editor.addCursorAtScreenPosition(screenPosition, autoscroll: false) else - @editor.setCursorScreenPosition(screenPosition) + @editor.setCursorScreenPosition(screenPosition, autoscroll: false) when 2 - @editor.getLastSelection().selectWord() + @editor.getLastSelection().selectWord(autoscroll: false) when 3 - @editor.getLastSelection().selectLine() + @editor.getLastSelection().selectLine(null, autoscroll: false) @handleDragUntilMouseUp (screenPosition) => - @editor.selectToScreenPosition(screenPosition, true) + @editor.selectToScreenPosition(screenPosition, suppressSelectionMerge: true, autoscroll: false) onLineNumberGutterMouseDown: (event) => return unless event.button is 0 # only handle the left mouse button @@ -422,14 +422,14 @@ class TextEditorComponent clickedScreenRow = @screenPositionForMouseEvent(event).row clickedBufferRow = @editor.bufferRowForScreenRow(clickedScreenRow) initialScreenRange = @editor.screenRangeForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]]) - @editor.setSelectedScreenRange(initialScreenRange, preserveFolds: true) + @editor.setSelectedScreenRange(initialScreenRange, preserveFolds: true, autoscroll: false) @handleGutterDrag(initialScreenRange) onGutterMetaClick: (event) => clickedScreenRow = @screenPositionForMouseEvent(event).row clickedBufferRow = @editor.bufferRowForScreenRow(clickedScreenRow) initialScreenRange = @editor.screenRangeForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]]) - @editor.addSelectionForScreenRange(initialScreenRange, preserveFolds: true) + @editor.addSelectionForScreenRange(initialScreenRange, preserveFolds: true, autoscroll: false) @handleGutterDrag(initialScreenRange) onGutterShiftClick: (event) => @@ -439,9 +439,9 @@ class TextEditorComponent clickedLineScreenRange = @editor.screenRangeForBufferRange([[clickedBufferRow, 0], [clickedBufferRow + 1, 0]]) if clickedScreenRow < tailScreenPosition.row - @editor.selectToScreenPosition(clickedLineScreenRange.start, true) + @editor.selectToScreenPosition(clickedLineScreenRange.start, suppressSelectionMerge: true, autoscroll: false) else - @editor.selectToScreenPosition(clickedLineScreenRange.end, true) + @editor.selectToScreenPosition(clickedLineScreenRange.end, suppressSelectionMerge: true, autoscroll: false) @handleGutterDrag(new Range(tailScreenPosition, tailScreenPosition)) @@ -456,7 +456,6 @@ class TextEditorComponent endPosition = [dragRow + 1, 0] screenRange = new Range(endPosition, endPosition).union(initialRange) @editor.getLastSelection().setScreenRange(screenRange, reversed: false, autoscroll: false, preserveFolds: true) - @editor.getLastCursor().autoscroll() onStylesheetsChanged: (styleElement) => return unless @performedInitialMeasurement @@ -512,7 +511,9 @@ class TextEditorComponent animationLoop = => @requestAnimationFrame => if dragging and @mounted - screenPosition = @screenPositionForMouseEvent(lastMousePosition) + linesClientRect = @linesComponent.getDomNode().getBoundingClientRect() + autoscroll(lastMousePosition, linesClientRect) + screenPosition = @screenPositionForMouseEvent(lastMousePosition, linesClientRect) dragHandler(screenPosition) animationLoop() else if not @mounted @@ -543,6 +544,32 @@ class TextEditorComponent window.removeEventListener('mouseup', onMouseUp) willInsertTextSubscription.dispose() + autoscroll = (mouseClientPosition) => + editorClientRect = @domNode.getBoundingClientRect() + + if mouseClientPosition.clientY < editorClientRect.top + mouseYDelta = editorClientRect.top - mouseClientPosition.clientY + yDirection = -1 + else if mouseClientPosition.clientY > editorClientRect.bottom + mouseYDelta = mouseClientPosition.clientY - editorClientRect.bottom + yDirection = 1 + + if mouseClientPosition.clientX < editorClientRect.left + mouseXDelta = editorClientRect.left - mouseClientPosition.clientX + xDirection = -1 + else if mouseClientPosition.clientX > editorClientRect.right + mouseXDelta = mouseClientPosition.clientX - editorClientRect.right + xDirection = 1 + + if mouseYDelta? + @presenter.setScrollTop(@presenter.getScrollTop() + yDirection * scaleScrollDelta(mouseYDelta)) + + if mouseXDelta? + @presenter.setScrollLeft(@presenter.getScrollLeft() + xDirection * scaleScrollDelta(mouseXDelta)) + + scaleScrollDelta = (scrollDelta) -> + Math.pow(scrollDelta / 2, 3) / 280 + pasteSelectionClipboard = (event) => if event?.which is 2 and process.platform is 'linux' if selection = require('./safe-clipboard').readText('selection') @@ -748,17 +775,20 @@ class TextEditorComponent if scrollSensitivity = parseInt(scrollSensitivity) @scrollSensitivity = Math.abs(scrollSensitivity) / 100 - screenPositionForMouseEvent: (event) -> - pixelPosition = @pixelPositionForMouseEvent(event) + screenPositionForMouseEvent: (event, linesClientRect) -> + pixelPosition = @pixelPositionForMouseEvent(event, linesClientRect) @editor.screenPositionForPixelPosition(pixelPosition) - pixelPositionForMouseEvent: (event) -> + pixelPositionForMouseEvent: (event, linesClientRect) -> {clientX, clientY} = event - linesClientRect = @linesComponent.getDomNode().getBoundingClientRect() + linesClientRect ?= @linesComponent.getDomNode().getBoundingClientRect() top = clientY - linesClientRect.top + @presenter.scrollTop left = clientX - linesClientRect.left + @presenter.scrollLeft - {top, left} + bottom = linesClientRect.top + @presenter.scrollTop + linesClientRect.height - clientY + right = linesClientRect.left + @presenter.scrollLeft + linesClientRect.width - clientX + + {top, left, bottom, right} getModel: -> @editor diff --git a/src/text-editor.coffee b/src/text-editor.coffee index b8c2a76fb..e2e50f5fb 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1943,10 +1943,10 @@ class TextEditor extends Model # This method may merge selections that end up intesecting. # # * `position` An instance of {Point}, with a given `row` and `column`. - selectToScreenPosition: (position, suppressMerge) -> + selectToScreenPosition: (position, options) -> lastSelection = @getLastSelection() - lastSelection.selectToScreenPosition(position) - unless suppressMerge + lastSelection.selectToScreenPosition(position, options) + unless options?.suppressSelectionMerge @mergeIntersectingSelections(reversed: lastSelection.isReversed()) # Essential: Move the cursor of each selection one character upward while From 2a3a304410ba7d9f18be5e6e69042bb804bcd4e3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 12:41:58 -0600 Subject: [PATCH 169/198] Terminate drag when editor is destroyed Fixes #4562 --- src/text-editor-component.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 7382cebcc..fce46f35c 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -542,7 +542,7 @@ class TextEditorComponent dragging = false window.removeEventListener('mousemove', onMouseMove) window.removeEventListener('mouseup', onMouseUp) - willInsertTextSubscription.dispose() + disposables.dispose() autoscroll = (mouseClientPosition) => editorClientRect = @domNode.getBoundingClientRect() @@ -577,7 +577,9 @@ class TextEditorComponent window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) - willInsertTextSubscription = @editor.onWillInsertText(onMouseUp) + disposables = new CompositeDisposable + disposables.add(@editor.onWillInsertText(onMouseUp)) + disposables.add(@editor.onDidDestroy(stopDragging)) isVisible: -> @domNode.offsetHeight > 0 or @domNode.offsetWidth > 0 From 012f8e40fcc4adc5e7066aa97a356f7e837c5e88 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 12:56:59 -0600 Subject: [PATCH 170/198] Add specs for mouse-based autoscroll; fix a couple existing specs --- spec/text-editor-component-spec.coffee | 47 +++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 5c2bf079b..3feb70da7 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1850,6 +1850,40 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [10, 0]] + it "autoscrolls when the cursor exceeds the boundaries of the editor", -> + wrapperNode.style.height = '100px' + wrapperNode.style.width = '100px' + component.measureDimensions() + nextAnimationFrame() + + expect(editor.getScrollTop()).toBe(0) + expect(editor.getScrollLeft()).toBe(0) + + linesNode.dispatchEvent(buildMouseEvent('mousedown', {clientX: 0, clientY: 0}, which: 1)) + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 150, clientY: 50}, which: 1)) + nextAnimationFrame() + + expect(editor.getScrollTop()).toBe(0) + expect(editor.getScrollLeft()).toBeGreaterThan(0) + + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 150, clientY: 150}, which: 1)) + nextAnimationFrame() + expect(editor.getScrollTop()).toBeGreaterThan(0) + + previousScrollTop = editor.getScrollTop() + previousScrollLeft = editor.getScrollLeft() + + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: -20, clientY: 50}, which: 1)) + nextAnimationFrame() + + expect(editor.getScrollTop()).toBe(previousScrollTop) + expect(editor.getScrollLeft()).toBeLessThan(previousScrollLeft) + + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: -20, clientY: -20}, which: 1)) + nextAnimationFrame() + + expect(editor.getScrollTop()).toBeLessThan(previousScrollTop) + it "stops selecting if the mouse is dragged into the dev tools", -> linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1)) linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([6, 8]), which: 1)) @@ -1928,7 +1962,7 @@ describe "TextEditorComponent", -> linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([11, 11]), which: 1)) nextAnimationFrame() - expect(editor.getSelectedScreenRange()).toEqual [[5, 6], [11, 13]] + expect(editor.getSelectedScreenRange()).toEqual [[5, 6], [12, 2]] maximalScrollTop = editor.getScrollTop() @@ -1955,7 +1989,7 @@ describe "TextEditorComponent", -> linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([11, 11]), which: 1)) nextAnimationFrame() - expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [12, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [12, 2]] maximalScrollTop = editor.getScrollTop() @@ -2052,14 +2086,15 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getLastSelection().isReversed()).toBe false - it "autoscrolls to the cursor position, but not the entire selected range", -> + it "autoscrolls when the cursor exceeds the top or bottom of the editor", -> wrapperNode.style.height = 6 * lineHeightInPixels + 'px' component.measureDimensions() + nextAnimationFrame() expect(editor.getScrollTop()).toBe 0 gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2))) - gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6))) + gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(8))) nextAnimationFrame() expect(editor.getScrollTop()).toBeGreaterThan 0 @@ -2069,6 +2104,10 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getScrollTop()).toBe maxScrollTop + gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(2))) + nextAnimationFrame() + expect(editor.getScrollTop()).toBeLessThan maxScrollTop + it "stops selecting if a textInput event occurs during the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2))) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6))) From c7cc404c4232256542a42b792cb22e6d97804dc9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 Aug 2015 13:39:14 -0600 Subject: [PATCH 171/198] Simplify page-up/page-down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We simply move the cursor up or down by the number of whole lines that fit on screen, allowing autoscroll to adjust the scroll position. This causes there to be a margin under the location to which we move the cursor, but I think it’s better to provide context and keep the autoscroll experience consistent when using the keyboard. --- spec/text-editor-spec.coffee | 13 +------------ src/text-editor.coffee | 6 +----- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 739e3a3c4..7d9e17dc2 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -4535,26 +4535,21 @@ describe "TextEditor", -> expect(editor.getScrollBottom()).toBe (9 + editor.getVerticalScrollMargin()) * 10 describe ".pageUp/Down()", -> - it "scrolls one screen height up or down and moves the cursor one page length", -> + it "moves the cursor down one page length", -> editor.setLineHeightInPixels(10) editor.setHeight(50) - expect(editor.getScrollHeight()).toBe 130 expect(editor.getCursorBufferPosition().row).toBe 0 editor.pageDown() - expect(editor.getScrollTop()).toBe 50 expect(editor.getCursorBufferPosition().row).toBe 5 editor.pageDown() - expect(editor.getScrollTop()).toBe 80 expect(editor.getCursorBufferPosition().row).toBe 10 editor.pageUp() - expect(editor.getScrollTop()).toBe 30 expect(editor.getCursorBufferPosition().row).toBe 5 editor.pageUp() - expect(editor.getScrollTop()).toBe 0 expect(editor.getCursorBufferPosition().row).toBe 0 describe ".selectPageUp/Down()", -> @@ -4565,28 +4560,22 @@ describe "TextEditor", -> expect(editor.getCursorBufferPosition().row).toBe 0 editor.selectPageDown() - expect(editor.getScrollTop()).toBe 30 expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [5, 0]]] editor.selectPageDown() - expect(editor.getScrollTop()).toBe 80 expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [10, 0]]] editor.selectPageDown() - expect(editor.getScrollTop()).toBe 80 expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [12, 2]]] editor.moveToBottom() editor.selectPageUp() - expect(editor.getScrollTop()).toBe 50 expect(editor.getSelectedBufferRanges()).toEqual [[[7, 0], [12, 2]]] editor.selectPageUp() - expect(editor.getScrollTop()).toBe 0 expect(editor.getSelectedBufferRanges()).toEqual [[[2, 0], [12, 2]]] editor.selectPageUp() - expect(editor.getScrollTop()).toBe 0 expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [12, 2]]] describe '.get/setPlaceholderText()', -> diff --git a/src/text-editor.coffee b/src/text-editor.coffee index b8c2a76fb..1f5cc6363 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2898,14 +2898,10 @@ class TextEditor extends Model setVerticalScrollbarWidth: (width) -> @displayBuffer.setVerticalScrollbarWidth(width) pageUp: -> - newScrollTop = @getScrollTop() - @getHeight() @moveUp(@getRowsPerPage()) - @setScrollTop(newScrollTop) pageDown: -> - newScrollTop = @getScrollTop() + @getHeight() @moveDown(@getRowsPerPage()) - @setScrollTop(newScrollTop) selectPageUp: -> @selectUp(@getRowsPerPage()) @@ -2915,7 +2911,7 @@ class TextEditor extends Model # Returns the number of rows per page getRowsPerPage: -> - Math.max(1, Math.ceil(@getHeight() / @getLineHeightInPixels())) + Math.max(1, Math.floor(@getHeight() / @getLineHeightInPixels())) ### Section: Config From f3a90c2ad0319cbdd5fdca4671832652fbda8b80 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 28 Aug 2015 13:05:58 -0700 Subject: [PATCH 172/198] Pin to yargs@3.19.0 os-locale is not handling spawn errors Closes #8559 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fcf8118ee..96b5a801f 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "theorist": "^1.0.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", - "yargs": "^3.9" + "yargs": "3.19.0" }, "packageDependencies": { "atom-dark-syntax": "0.27.0", From f3b457d8bae178139843baad3e987566fe79ba42 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Fri, 28 Aug 2015 17:33:46 -0400 Subject: [PATCH 173/198] Add a notification when toggling the menu bar Refs #4289 --- src/window-event-handler.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 25baf0443..e5235ee7e 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -86,9 +86,13 @@ class WindowEventHandler @subscribeToCommand $(window), 'window:toggle-dev-tools', -> atom.toggleDevTools() if process.platform in ['win32', 'linux'] - @subscribeToCommand $(window), 'window:toggle-menu-bar', -> + @subscribeToCommand $(window), 'window:toggle-menu-bar', => atom.config.set('core.autoHideMenuBar', not atom.config.get('core.autoHideMenuBar')) + if atom.config.get('core.autoHideMenuBar') + detail = "To toggle, press the Alt key or execute the window:toggle-menu-bar command" + atom.notifications.addInfo('Menu bar hidden', {detail}) + @subscribeToCommand $(document), 'core:focus-next', @focusNext @subscribeToCommand $(document), 'core:focus-previous', @focusPrevious From 01b9c3a0b0b38e665f44b3dde377fd418e084916 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Fri, 28 Aug 2015 18:06:04 -0400 Subject: [PATCH 174/198] :shirt: No fat arrow --- src/window-event-handler.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index e5235ee7e..10ea89dca 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -86,7 +86,7 @@ class WindowEventHandler @subscribeToCommand $(window), 'window:toggle-dev-tools', -> atom.toggleDevTools() if process.platform in ['win32', 'linux'] - @subscribeToCommand $(window), 'window:toggle-menu-bar', => + @subscribeToCommand $(window), 'window:toggle-menu-bar', -> atom.config.set('core.autoHideMenuBar', not atom.config.get('core.autoHideMenuBar')) if atom.config.get('core.autoHideMenuBar') From 9a2b0211dd082a4c8fec5a5a37c56c56dfb598e0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 28 Aug 2015 16:42:14 -0700 Subject: [PATCH 175/198] Prepare 1.0.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96b5a801f..42b0e2ed7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.0.9", + "version": "1.0.10", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 273814507c5e3bd55b2e04933aa8964ba0c1f444 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sat, 29 Aug 2015 12:04:29 -0400 Subject: [PATCH 176/198] :arrow_up: language-html@0.41.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 42b0e2ed7..c7de53b4f 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "language-gfm": "0.80.0", "language-git": "0.10.0", "language-go": "0.37.0", - "language-html": "0.41.0", + "language-html": "0.41.2", "language-hyperlink": "0.14.0", "language-java": "0.16.0", "language-javascript": "0.90.0", From 70bcedba4df6c63c44566f584965f723ab0167d0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 29 Aug 2015 12:59:16 -0600 Subject: [PATCH 177/198] Revert "Nix arguments" This reverts commit f2a017d0136ba3717bdc5261279849824d706b8b. --- src/gutter.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gutter.coffee b/src/gutter.coffee index 160036e3e..4f09a27f2 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -75,6 +75,8 @@ class Gutter # is invalidated, or is destroyed, the decoration will be updated to reflect # the marker's state. # + # ## Arguments + # # * `marker` A {Marker} you want this decoration to follow. # * `decorationParams` An {Object} representing the decoration # * `class` This CSS class will be applied to the decorated line number. From e91241be524efb4f149b1f464de12591e11b7a36 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 29 Aug 2015 13:00:08 -0600 Subject: [PATCH 178/198] Revert "Remove arguments subheading" This reverts commit ba7af0b9cd3fe5f9c01bdc3a15628749e675740b. --- src/text-editor.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 1d538cf8b..9dcc7cb62 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1292,6 +1292,8 @@ class TextEditor extends Model # # ``` # + # ## Arguments + # # * `marker` A {Marker} you want this decoration to follow. # * `decorationParams` An {Object} representing the decoration e.g. # `{type: 'line-number', class: 'linter-error'}` From 618c299ef8f7257a93ca3508558d2c2858eb63ee Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sun, 30 Aug 2015 10:42:29 +0200 Subject: [PATCH 179/198] :arrow_up: settings-view@0.216.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7de53b4f..096a4bbe4 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "open-on-github": "0.38.0", "package-generator": "0.40.0", "release-notes": "0.53.0", - "settings-view": "0.215.0", + "settings-view": "0.216.0", "snippets": "0.95.0", "spell-check": "0.59.0", "status-bar": "0.78.0", From ca089d86fa072e58c80bcfa429bf92b6f5ba3021 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 31 Aug 2015 07:34:27 +0200 Subject: [PATCH 180/198] :arrow_up: language-javascript@0.92.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 096a4bbe4..4a94c1ba0 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "language-html": "0.41.2", "language-hyperlink": "0.14.0", "language-java": "0.16.0", - "language-javascript": "0.90.0", + "language-javascript": "0.92.0", "language-json": "0.16.0", "language-less": "0.28.2", "language-make": "0.17.0", From 507bb92391b3aa0bdc7cb5fadb1ab9a1273c98fe Mon Sep 17 00:00:00 2001 From: Benjamin Coe Date: Mon, 31 Aug 2015 00:20:19 -0700 Subject: [PATCH 181/198] :arrow_up: upgrade to yargs@3.23.0 no more spawn-sync --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a94c1ba0..1061f10aa 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "theorist": "^1.0.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", - "yargs": "3.19.0" + "yargs": "^3.23.0" }, "packageDependencies": { "atom-dark-syntax": "0.27.0", From 06fe06985f418c0bc5d0a332bb4dc4438058cf13 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 31 Aug 2015 09:06:37 -0700 Subject: [PATCH 182/198] :arrow_up: grunt-electron-installer@1.0.0 --- build/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/package.json b/build/package.json index 02ba4cfa1..3298b4a63 100644 --- a/build/package.json +++ b/build/package.json @@ -22,7 +22,7 @@ "grunt-contrib-less": "~0.8.0", "grunt-cson": "0.14.0", "grunt-download-atom-shell": "~0.15.1", - "grunt-electron-installer": "^0.37.0", + "grunt-electron-installer": "1.0.0", "grunt-lesslint": "0.17.0", "grunt-peg": "~1.1.0", "grunt-shell": "~0.3.1", From 9b719056e0db7cde0d395d370774da048ee967ec Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 31 Aug 2015 09:50:00 -0700 Subject: [PATCH 183/198] :arrow_up: status-bar@0.79 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1061f10aa..1678b99e8 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "settings-view": "0.216.0", "snippets": "0.95.0", "spell-check": "0.59.0", - "status-bar": "0.78.0", + "status-bar": "0.79.0", "styleguide": "0.44.0", "symbols-view": "0.103.0", "tabs": "0.84.0", From 8fa99a33beaee4c5776f301e227474c7fec60b41 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 31 Aug 2015 10:23:08 -0700 Subject: [PATCH 184/198] Append root to atom home value --- src/compile-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index 9e4d14e15..cead841c1 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -16,7 +16,7 @@ var cacheDirectory = null exports.setAtomHomeDirectory = function (atomHome) { var cacheDir = path.join(atomHome, 'compile-cache') if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { - cacheDir = path.join(cacheDirectory, 'root') + cacheDir = path.join(atomHome, 'root') } this.setCacheDirectory(cacheDir) } From 56f40d77145c3fde83746386c2639b31da88dfd2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 31 Aug 2015 10:29:36 -0700 Subject: [PATCH 185/198] Append to existing cache dir value --- src/compile-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index cead841c1..c427097e7 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -16,7 +16,7 @@ var cacheDirectory = null exports.setAtomHomeDirectory = function (atomHome) { var cacheDir = path.join(atomHome, 'compile-cache') if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { - cacheDir = path.join(atomHome, 'root') + cacheDir = path.join(cacheDir, 'root') } this.setCacheDirectory(cacheDir) } From 99d89fbc0462fd677c0c2a4c74fa045ff221c8c7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 31 Aug 2015 17:07:40 -0700 Subject: [PATCH 186/198] :arrow_up: language-gfm@0.81 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1678b99e8..8f0c39a9f 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "language-coffee-script": "0.41.0", "language-csharp": "0.7.0", "language-css": "0.33.0", - "language-gfm": "0.80.0", + "language-gfm": "0.81.0", "language-git": "0.10.0", "language-go": "0.37.0", "language-html": "0.41.2", From a32034978b5e0bb4f4a27f6dd44bbe034e7a6d86 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 31 Aug 2015 17:17:16 -0700 Subject: [PATCH 187/198] :arrow_up: symbols-view@0.104 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f0c39a9f..da1c2a26f 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "spell-check": "0.59.0", "status-bar": "0.79.0", "styleguide": "0.44.0", - "symbols-view": "0.103.0", + "symbols-view": "0.104.0", "tabs": "0.84.0", "timecop": "0.31.0", "tree-view": "0.186.0", From 25dfd620f2662c972a275dc22218626a09295fc0 Mon Sep 17 00:00:00 2001 From: Cosmic Web Services Date: Tue, 1 Sep 2015 09:07:01 -0400 Subject: [PATCH 188/198] Update windows.md --- docs/build-instructions/windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md index 0ae063493..9a433f0d0 100644 --- a/docs/build-instructions/windows.md +++ b/docs/build-instructions/windows.md @@ -16,7 +16,7 @@ `mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27` * [GitHub for Windows](http://windows.github.com/) -### On Windows 8 +### On Windows 8 or 10 * [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/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x) * [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp)) From 74f356f11d4fb97d21a03401979fab21b50c0210 Mon Sep 17 00:00:00 2001 From: Cosmic Web Services Date: Tue, 1 Sep 2015 09:20:46 -0400 Subject: [PATCH 189/198] Update windows.md --- docs/build-instructions/windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md index 9a433f0d0..76ea7a025 100644 --- a/docs/build-instructions/windows.md +++ b/docs/build-instructions/windows.md @@ -17,7 +17,7 @@ * [GitHub for Windows](http://windows.github.com/) ### On Windows 8 or 10 - * [Visual Studio Express 2013 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2) + * [Visual Studio Express 2013 or 2015 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2) * [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x) * [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp)) * [GitHub for Windows](http://windows.github.com/) From e1a5a99fda35f1967de8ab63d316cc56207798b4 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 2 Sep 2015 00:21:06 +0900 Subject: [PATCH 190/198] :arrow_up: atom-dark-ui@v0.51.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index da1c2a26f..f44ff79e5 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ }, "packageDependencies": { "atom-dark-syntax": "0.27.0", - "atom-dark-ui": "0.50.0", + "atom-dark-ui": "0.51.0", "atom-light-syntax": "0.28.0", "atom-light-ui": "0.43.0", "base16-tomorrow-dark-theme": "0.27.0", From f1276f59ded8ae5356dd0dff2bed5b1dc808e3c3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 1 Sep 2015 10:43:45 -0600 Subject: [PATCH 191/198] Autoscroll when mouse gets close to the edge of the editor scroll view Previously, we required the cursor to exceed the bounds of the editor, but that makes it impossible to scroll when the window is maximized. --- spec/text-editor-component-spec.coffee | 19 ++++++++++--------- src/text-editor-component.coffee | 22 +++++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 3feb70da7..7e1b2fd64 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1850,7 +1850,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [10, 0]] - it "autoscrolls when the cursor exceeds the boundaries of the editor", -> + it "autoscrolls when the cursor approaches the boundaries of the editor", -> wrapperNode.style.height = '100px' wrapperNode.style.width = '100px' component.measureDimensions() @@ -1860,26 +1860,26 @@ describe "TextEditorComponent", -> expect(editor.getScrollLeft()).toBe(0) linesNode.dispatchEvent(buildMouseEvent('mousedown', {clientX: 0, clientY: 0}, which: 1)) - linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 150, clientY: 50}, which: 1)) + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 50}, which: 1)) nextAnimationFrame() expect(editor.getScrollTop()).toBe(0) expect(editor.getScrollLeft()).toBeGreaterThan(0) - linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 150, clientY: 150}, which: 1)) + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 100}, which: 1)) nextAnimationFrame() expect(editor.getScrollTop()).toBeGreaterThan(0) previousScrollTop = editor.getScrollTop() previousScrollLeft = editor.getScrollLeft() - linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: -20, clientY: 50}, which: 1)) + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 50}, which: 1)) nextAnimationFrame() expect(editor.getScrollTop()).toBe(previousScrollTop) expect(editor.getScrollLeft()).toBeLessThan(previousScrollLeft) - linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: -20, clientY: -20}, which: 1)) + linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 10}, which: 1)) nextAnimationFrame() expect(editor.getScrollTop()).toBeLessThan(previousScrollTop) @@ -1995,7 +1995,7 @@ describe "TextEditorComponent", -> linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 4]), which: 1)) nextAnimationFrame() - expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [9, 0]] + expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [8, 0]] expect(editor.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression) linesNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenPosition([9, 3]), which: 1)) @@ -2086,7 +2086,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getLastSelection().isReversed()).toBe false - it "autoscrolls when the cursor exceeds the top or bottom of the editor", -> + it "autoscrolls when the cursor approaches the top or bottom of the editor", -> wrapperNode.style.height = 6 * lineHeightInPixels + 'px' component.measureDimensions() nextAnimationFrame() @@ -2100,11 +2100,12 @@ describe "TextEditorComponent", -> expect(editor.getScrollTop()).toBeGreaterThan 0 maxScrollTop = editor.getScrollTop() - gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(5))) + + gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(10))) nextAnimationFrame() expect(editor.getScrollTop()).toBe maxScrollTop - gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(2))) + gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(7))) nextAnimationFrame() expect(editor.getScrollTop()).toBeLessThan maxScrollTop diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index fce46f35c..8ff7a0d77 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -545,20 +545,24 @@ class TextEditorComponent disposables.dispose() autoscroll = (mouseClientPosition) => - editorClientRect = @domNode.getBoundingClientRect() + {top, bottom, left, right} = @scrollViewNode.getBoundingClientRect() + top += 30 + bottom -= 30 + left += 30 + right -= 30 - if mouseClientPosition.clientY < editorClientRect.top - mouseYDelta = editorClientRect.top - mouseClientPosition.clientY + if mouseClientPosition.clientY < top + mouseYDelta = top - mouseClientPosition.clientY yDirection = -1 - else if mouseClientPosition.clientY > editorClientRect.bottom - mouseYDelta = mouseClientPosition.clientY - editorClientRect.bottom + else if mouseClientPosition.clientY > bottom + mouseYDelta = mouseClientPosition.clientY - bottom yDirection = 1 - if mouseClientPosition.clientX < editorClientRect.left - mouseXDelta = editorClientRect.left - mouseClientPosition.clientX + if mouseClientPosition.clientX < left + mouseXDelta = left - mouseClientPosition.clientX xDirection = -1 - else if mouseClientPosition.clientX > editorClientRect.right - mouseXDelta = mouseClientPosition.clientX - editorClientRect.right + else if mouseClientPosition.clientX > right + mouseXDelta = mouseClientPosition.clientX - right xDirection = 1 if mouseYDelta? From f3def3a7edfa6a3ca72a4300aa50f5d7afd8fd3a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 1 Sep 2015 12:58:34 -0600 Subject: [PATCH 192/198] Terminate drag on any buffer change Not just text insertion. --- spec/text-editor-component-spec.coffee | 21 ++++++++++++++++----- src/text-editor-component.coffee | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 3feb70da7..d5bc97beb 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1898,16 +1898,13 @@ describe "TextEditorComponent", -> expect(nextAnimationFrame).toBe noAnimationFrame expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]] - it "stops selecting if a textInput event occurs during the drag", -> + it "stops selecting before the buffer is modified during the drag", -> linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1)) linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([6, 8]), which: 1)) nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]] - inputEvent = new Event('textInput') - inputEvent.data = 'x' - Object.defineProperty(inputEvent, 'target', get: -> componentNode.querySelector('.hidden-input')) - componentNode.dispatchEvent(inputEvent) + editor.insertText('x') nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[2, 5], [2, 5]] @@ -1916,6 +1913,20 @@ describe "TextEditorComponent", -> expect(nextAnimationFrame).toBe noAnimationFrame expect(editor.getSelectedScreenRange()).toEqual [[2, 5], [2, 5]] + linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1)) + linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([5, 4]), which: 1)) + nextAnimationFrame() + expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [5, 4]] + + editor.delete() + nextAnimationFrame() + + expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [2, 4]] + + linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 0]), which: 1)) + expect(nextAnimationFrame).toBe noAnimationFrame + expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [2, 4]] + describe "when the command key is held down", -> it "adds a new selection and selects to the nearest screen position, then merges intersecting selections when the mouse button is released", -> editor.setSelectedScreenRange([[4, 4], [4, 9]]) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index fce46f35c..2604b5d0d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -578,7 +578,7 @@ class TextEditorComponent window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) disposables = new CompositeDisposable - disposables.add(@editor.onWillInsertText(onMouseUp)) + disposables.add(@editor.getBuffer().onWillChange(onMouseUp)) disposables.add(@editor.onDidDestroy(stopDragging)) isVisible: -> From 5be3d4f82f298f257b1f55767a29aa5d371dae67 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 31 Aug 2015 17:12:08 -0700 Subject: [PATCH 193/198] Normalize drive letter case on windows --- src/browser/atom-application.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 1142303cf..e7b387897 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -66,7 +66,11 @@ class AtomApplication {@resourcePath, @version, @devMode, @safeMode, @socketPath} = options # Normalize to make sure drive letter case is consistent on Windows - @resourcePath = path.normalize(@resourcePath) if @resourcePath + if @resourcePath + if process.platform is 'win32' + @resourcePath = @resourcePath.replace /^([a-z]):/, ([driveLetter]) -> + "#{driveLetter.toUpperCase()}:" + @resourcePath = path.normalize(@resourcePath) global.atomApplication = this From 61f7de736f6b0882fa0a41c1bdc7f205c7c2f0ce Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 1 Sep 2015 11:54:58 -0700 Subject: [PATCH 194/198] Normalize resource path drive letters in main --- src/browser/atom-application.coffee | 7 ------- src/browser/atom-window.coffee | 3 --- src/browser/main.coffee | 19 ++++++++++++------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index e7b387897..54f444d0e 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -65,13 +65,6 @@ class AtomApplication constructor: (options) -> {@resourcePath, @version, @devMode, @safeMode, @socketPath} = options - # Normalize to make sure drive letter case is consistent on Windows - if @resourcePath - if process.platform is 'win32' - @resourcePath = @resourcePath.replace /^([a-z]):/, ([driveLetter]) -> - "#{driveLetter.toUpperCase()}:" - @resourcePath = path.normalize(@resourcePath) - global.atomApplication = this @pidsToOpenWindows = {} diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index 5f9219ead..99b28444f 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -23,9 +23,6 @@ class AtomWindow locationsToOpen ?= [{pathToOpen}] if pathToOpen locationsToOpen ?= [] - # Normalize to make sure drive letter case is consistent on Windows - @resourcePath = path.normalize(@resourcePath) if @resourcePath - options = show: false title: 'Atom' diff --git a/src/browser/main.coffee b/src/browser/main.coffee index fac1191a7..659c97776 100644 --- a/src/browser/main.coffee +++ b/src/browser/main.coffee @@ -54,17 +54,20 @@ start = -> else path.resolve(pathToOpen) - if args.devMode - AtomApplication = require path.join(args.resourcePath, 'src', 'browser', 'atom-application') - else - AtomApplication = require './atom-application' + AtomApplication = require path.join(args.resourcePath, 'src', 'browser', 'atom-application') AtomApplication.open(args) console.log("App load time: #{Date.now() - global.shellStartTime}ms") unless args.test -global.devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getHomeDir(), 'github', 'atom') -# Normalize to make sure drive letter case is consistent on Windows -global.devResourcePath = path.normalize(global.devResourcePath) if global.devResourcePath +normalizeDriveLetterName = (filePath) -> + if process.platform is 'win32' + filePath.replace /^([a-z]):/, ([driveLetter]) -> driveLetter.toUpperCase() + ":" + else + filePath + +global.devResourcePath = normalizeDriveLetterName( + process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getHomeDir(), 'github', 'atom') +) setupCrashReporter = -> crashReporter.start(productName: 'Atom', companyName: 'GitHub') @@ -164,6 +167,8 @@ parseCommandLine = -> # explicitly pass it by command line, see http://git.io/YC8_Ew. process.env.PATH = args['path-environment'] if args['path-environment'] + resourcePath = normalizeDriveLetterName(resourcePath) + {resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, socketPath, profileStartup} From a0f5a6612c73b49296d738b6b04e72d809e48dcd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 1 Sep 2015 16:44:52 -0700 Subject: [PATCH 195/198] Prepare 1.0.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f44ff79e5..bbdcf1fc3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.0.10", + "version": "1.0.11", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From d9410716d2c39fa4d62b2e1ab2fe1251a5805487 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 1 Sep 2015 16:32:50 -0700 Subject: [PATCH 196/198] Don't attempt to use source maps w/ no cache directory This error will only happen when the compile-cache file is compiled twice, which seems to be happening on windows when running specs. This doesn't fix that problem, but prevents further errors from cascading in a confusing way. --- src/compile-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index c427097e7..e10bf6803 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -108,7 +108,7 @@ require('source-map-support').install({ // source-map-support module, but we've overridden it to read the javascript // code from our cache directory. retrieveSourceMap: function (filePath) { - if (!fs.isFileSync(filePath)) { + if (!cacheDirectory || !fs.isFileSync(filePath)) { return null } From 77704e5d219c280c82219d2d8afd7b230099b992 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 1 Sep 2015 17:41:55 -0700 Subject: [PATCH 197/198] :arrow_up: whitespace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bbdcf1fc3..1b38bd04e 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "tree-view": "0.186.0", "update-package-dependencies": "0.10.0", "welcome": "0.30.0", - "whitespace": "0.30.0", + "whitespace": "0.31.0", "wrap-guide": "0.35.0", "language-c": "0.47.1", "language-clojure": "0.16.0", From a235fcb8aaab772d40e6083937a16dde3626ded6 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 1 Sep 2015 21:28:16 -0700 Subject: [PATCH 198/198] Update notification-manager.coffee Fix a broken link in the docs page for https://atom.io/docs/api/v1.0.10/NotificationManager. --- src/notification-manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notification-manager.coffee b/src/notification-manager.coffee index f4ebd97db..8f790e0c6 100644 --- a/src/notification-manager.coffee +++ b/src/notification-manager.coffee @@ -80,7 +80,7 @@ class NotificationManager # Public: Get all the notifications. # - # Returns an {Array} of {Notifications}s. + # Returns an {Array} of {Notification}s. getNotifications: -> @notifications.slice() ###