Merge branch 'master' into indent-soft-wrap

This commit is contained in:
Antonio Scandurra
2015-02-21 12:14:05 +01:00
68 changed files with 1652 additions and 980 deletions

View File

@@ -1 +1 @@
v0.10.33
v0.12.0

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "0.138.0"
"atom-package-manager": "0.140.0"
}
}

View File

@@ -220,7 +220,6 @@ module.exports = (grunt) ->
grunt.registerTask('compile', ['coffee', 'prebuild-less', 'cson', 'peg'])
grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint'])
grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
grunt.registerTask('docs', ['markdown:guides', 'build-docs'])
ciTasks = ['output-disk-space', 'download-atom-shell', 'download-atom-shell-chromedriver', 'build']
ciTasks.push('dump-symbols') if process.platform isnt 'win32'

View File

@@ -12,7 +12,7 @@
"fs-plus": "2.x",
"github-releases": "~0.2.0",
"grunt": "~0.4.1",
"grunt-atom-shell-installer": "^0.21.0",
"grunt-atom-shell-installer": "^0.23.0",
"grunt-cli": "~0.1.9",
"grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git#cfb99aa99811d52687969532bd5a98011ed95bfe",
"grunt-contrib-coffee": "~0.12.0",
@@ -26,7 +26,7 @@
"harmony-collections": "~0.3.8",
"legal-eagle": "~0.9.0",
"minidump": "~0.8",
"npm": "~1.4.5",
"npm": "2.5.1",
"rcedit": "~0.3.0",
"request": "~2.27.0",
"rimraf": "~2.2.2",

View File

@@ -7,7 +7,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++ toolchain
* [Git](http://git-scm.com/)
* [Node.js](http://nodejs.org/download/) v0.10.x
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x)
* [npm](https://www.npmjs.com/) v1.4.x (bundled with Node.js)
* `npm -v` to check the version.
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2.
@@ -18,6 +18,9 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* `sudo apt-get install build-essential git libgnome-keyring-dev fakeroot`
* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
* Make sure the command `node` is available after Node.js installation (some sytems install it as `nodejs`).
* Use `which node` to check if it is available.
* Use `sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10` to update it.
### Fedora / CentOS / RHEL

View File

@@ -3,7 +3,7 @@
## Requirements
* OS X 10.8 or later
* [node.js](http://nodejs.org/download/) v0.10.x
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x)
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)
## Instructions

View File

@@ -5,7 +5,7 @@
### On Windows 7
* [Visual C++ 2010 Express](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_4)
* [Visual Studio 2010 Service Pack 1](http://www.microsoft.com/en-us/download/details.aspx?id=23691)
* [node.js](http://nodejs.org/download/) v0.10.x
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x)
* For 64-bit builds of node and native modules you **must** have the
[Windows 7 64-bit SDK](http://www.microsoft.com/en-us/download/details.aspx?id=8279).
You may also need the [compiler update for the Windows SDK 7.1](http://www.microsoft.com/en-us/download/details.aspx?id=4422)
@@ -18,7 +18,7 @@
### On Windows 8
* [Visual Studio Express 2013 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2)
* [node.js](http://nodejs.org/download/) v0.10.x
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x)
* [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp))
* [GitHub for Windows](http://windows.github.com/)

View File

@@ -7,6 +7,7 @@
{ label: 'VERSION', enabled: false }
{ label: 'Restart and Install Update', command: 'application:install-update', visible: false}
{ label: 'Check for Update', command: 'application:check-for-update', visible: false}
{ label: 'Checking for Update', enabled: false, visible: false}
{ label: 'Downloading Update', enabled: false, visible: false}
{ type: 'separator' }
{ label: 'Preferences...', command: 'application:show-settings' }

View File

@@ -166,6 +166,7 @@
{ label: 'VERSION', enabled: false }
{ label: 'Restart and Install Update', command: 'application:install-update', visible: false}
{ label: 'Check for Update', command: 'application:check-for-update', visible: false}
{ label: 'Checking for Update', enabled: false, visible: false}
{ label: 'Downloading Update', enabled: false, visible: false}
{ type: 'separator' }
{ label: '&Documentation', command: 'application:open-documentation' }

View File

@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "0.180.0",
"version": "0.183.0",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@@ -19,10 +19,10 @@
],
"atomShellVersion": "0.21.0",
"dependencies": {
"6to5-core": "^3.0.14",
"async": "0.2.6",
"atom-keymap": "^3.1.2",
"atom-space-pen-views": "^2.0.4",
"babel-core": "^4.0.2",
"bootstrap": "git+https://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
"clear-cut": "0.4.0",
"coffee-cash": "0.7.0",
@@ -30,25 +30,25 @@
"coffeestack": "^1.1.1",
"color": "^0.7.3",
"delegato": "^1",
"emissary": "^1.3.1",
"emissary": "^1.3.3",
"event-kit": "^1.0.2",
"first-mate": "^3.0.0",
"fs-plus": "^2.5",
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
"git-utils": "^3.0.0",
"grim": "1.1.2",
"grim": "1.2",
"jasmine-json": "~0.0",
"jasmine-tagged": "^1.1.4",
"jquery": "^2.1.1",
"less-cache": "0.21",
"less-cache": "0.22",
"marked": "^0.3",
"mixto": "^1",
"nslog": "^2.0.0",
"oniguruma": "^4.0.0",
"optimist": "0.4.0",
"pathwatcher": "^3.1.1",
"property-accessors": "^1",
"pathwatcher": "^3.3.1",
"property-accessors": "^1.1.3",
"q": "^1.1.2",
"random-words": "0.0.1",
"react-atom-fork": "^0.11.5",
@@ -64,10 +64,9 @@
"space-pen": "3.8.2",
"stacktrace-parser": "0.1.1",
"temp": "0.8.1",
"text-buffer": "^4.1.4",
"text-buffer": "^4.1.5",
"theorist": "^1.0.2",
"underscore-plus": "^1.6.6",
"vm-compatibility-layer": "0.1.0"
"underscore-plus": "^1.6.6"
},
"packageDependencies": {
"atom-dark-syntax": "0.26.0",
@@ -86,54 +85,54 @@
"autocomplete": "0.44.0",
"autoflow": "0.22.0",
"autosave": "0.20.0",
"background-tips": "0.22.0",
"background-tips": "0.23.0",
"bookmarks": "0.35.0",
"bracket-matcher": "0.71.0",
"command-palette": "0.34.0",
"deprecation-cop": "0.36.0",
"deprecation-cop": "0.37.0",
"dev-live-reload": "0.41.0",
"encoding-selector": "0.18.0",
"exception-reporting": "0.24.0",
"find-and-replace": "0.157.0",
"fuzzy-finder": "0.66.0",
"git-diff": "0.52.0",
"fuzzy-finder": "0.68.0",
"git-diff": "0.53.0",
"go-to-line": "0.30.0",
"grammar-selector": "0.45.0",
"image-view": "0.49.0",
"incompatible-packages": "0.22.0",
"keybinding-resolver": "0.28.0",
"keybinding-resolver": "0.29.0",
"link": "0.30.0",
"markdown-preview": "0.137.0",
"metrics": "0.43.0",
"notifications": "0.27.0",
"metrics": "0.45.0",
"notifications": "0.28.0",
"open-on-github": "0.32.0",
"package-generator": "0.38.0",
"release-notes": "0.50.0",
"settings-view": "0.181.0",
"snippets": "0.74.0",
"spell-check": "0.54.0",
"release-notes": "0.51.0",
"settings-view": "0.183.0",
"snippets": "0.76.0",
"spell-check": "0.55.0",
"status-bar": "0.60.0",
"styleguide": "0.44.0",
"symbols-view": "0.83.0",
"tabs": "0.67.0",
"timecop": "0.30.0",
"tree-view": "0.155.0",
"tree-view": "0.160.0",
"update-package-dependencies": "0.8.0",
"welcome": "0.21.0",
"welcome": "0.24.0",
"whitespace": "0.29.0",
"wrap-guide": "0.31.0",
"language-c": "0.38.0",
"language-c": "0.40.0",
"language-clojure": "0.12.0",
"language-coffee-script": "0.39.0",
"language-csharp": "0.5.0",
"language-css": "0.28.0",
"language-gfm": "0.63.0",
"language-gfm": "0.64.0",
"language-git": "0.10.0",
"language-go": "0.21.0",
"language-html": "0.29.0",
"language-hyperlink": "0.12.2",
"language-java": "0.14.0",
"language-javascript": "0.56.0",
"language-javascript": "0.57.0",
"language-json": "0.12.0",
"language-less": "0.24.0",
"language-make": "0.13.0",
@@ -144,7 +143,7 @@
"language-property-list": "0.8.0",
"language-python": "0.32.0",
"language-ruby": "0.48.0",
"language-ruby-on-rails": "0.18.0",
"language-ruby-on-rails": "0.19.0",
"language-sass": "0.34.0",
"language-shellscript": "0.12.0",
"language-source": "0.9.0",

View File

@@ -1,6 +1,6 @@
Package: <%= name %>
Version: <%= version %>
Depends: git, gconf2, gconf-service, libgtk2.0-0, libudev0 | libudev1, libgcrypt11, libnotify4, libxtst6, libnss3, python, gvfs-bin, xdg-utils
Depends: git, gconf2, gconf-service, libgtk2.0-0, libudev0 | libudev1, libgcrypt11 | libgcrypt20, libnotify4, libxtst6, libnss3, python, gvfs-bin, xdg-utils
Suggests: libgnome-keyring0, gir1.2-gnomekeyring-1.0
Section: <%= section %>
Priority: optional

View File

@@ -64,18 +64,16 @@ function bootstrap() {
var buildInstallCommand = initialNpmCommand + npmFlags + 'install';
var buildInstallOptions = {cwd: path.resolve(__dirname, '..', 'build')};
var apmInstallCommand = npmPath + npmFlags + 'install';
var apmInstallCommand = npmPath + npmFlags + '--target=0.10.35 ' + 'install';
var apmInstallOptions = {cwd: apmInstallPath};
var moduleInstallCommand = apmPath + ' install' + apmFlags;
var dedupeApmCommand = apmPath + ' dedupe' + apmFlags;
var dedupeNpmCommand = npmPath + npmFlags + 'dedupe';
if (process.argv.indexOf('--no-quiet') === -1) {
buildInstallCommand += ' --loglevel error';
apmInstallCommand += ' --loglevel error';
moduleInstallCommand += ' --loglevel error';
dedupeApmCommand += ' --quiet';
dedupeNpmCommand += ' --quiet';
buildInstallOptions.ignoreStdout = true;
apmInstallOptions.ignoreStdout = true;
}
@@ -99,12 +97,6 @@ function bootstrap() {
apmPath + ' clean' + apmFlags,
moduleInstallCommand,
dedupeApmCommand + ' ' + packagesToDedupe.join(' '),
{
command: dedupeNpmCommand + ' request semver',
options: {
cwd: path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager')
}
},
];
process.chdir(path.dirname(__dirname));

View File

@@ -1,8 +1,8 @@
to5 = require '../src/6to5'
babel = require '../src/babel'
crypto = require 'crypto'
describe "6to5 transpiler support", ->
describe "::create6to5VersionAndOptionsDigest", ->
describe "Babel transpiler support", ->
describe "::createBabelVersionAndOptionsDigest", ->
it "returns a digest for the library version and specified options", ->
defaultOptions =
blacklist: [
@@ -16,26 +16,36 @@ describe "6to5 transpiler support", ->
sourceMap: 'inline'
version = '3.0.14'
shasum = crypto.createHash('sha1')
shasum.update('6to5-core', 'utf8')
shasum.update('babel-core', 'utf8')
shasum.update('\0', 'utf8')
shasum.update(version, 'utf8')
shasum.update('\0', 'utf8')
shasum.update('{"blacklist": ["useStrict",],"experimental": true,"optional": ["asyncToGenerator",],"reactCompat": true,"sourceMap": "inline",}')
expectedDigest = shasum.digest('hex')
observedDigest = to5.create6to5VersionAndOptionsDigest(version, defaultOptions)
observedDigest = babel.createBabelVersionAndOptionsDigest(version, defaultOptions)
expect(observedDigest).toEqual expectedDigest
describe "when a .js file starts with 'use babel';", ->
it "transpiles it using babel", ->
transpiled = require('./fixtures/babel/babel-single-quotes.js')
expect(transpiled(3)).toBe 4
describe "when a .js file starts with 'use 6to5';", ->
it "transpiles it using 6to5", ->
transpiled = require('./fixtures/6to5/single-quotes.js')
transpiled = require('./fixtures/babel/6to5-single-quotes.js')
expect(transpiled(3)).toBe 4
describe 'when a .js file starts with "use babel";', ->
it "transpiles it using babel", ->
transpiled = require('./fixtures/babel/babel-double-quotes.js')
expect(transpiled(3)).toBe 4
describe 'when a .js file starts with "use 6to5";', ->
it "transpiles it using 6to5", ->
transpiled = require('./fixtures/6to5/double-quotes.js')
it "transpiles it using babel", ->
transpiled = require('./fixtures/babel/6to5-double-quotes.js')
expect(transpiled(3)).toBe 4
describe "when a .js file does not start with 'use 6to6';", ->
it "does not transpile it using 6to5", ->
expect(-> require('./fixtures/6to5/invalid.js')).toThrow()
it "does not transpile it using babel", ->
expect(-> require('./fixtures/babel/invalid.js')).toThrow()

View File

@@ -2,27 +2,27 @@ path = require 'path'
CSON = require 'season'
CoffeeCache = require 'coffee-cash'
to5 = require '../src/6to5'
babel = require '../src/babel'
CompileCache = require '../src/compile-cache'
describe "Compile Cache", ->
describe ".addPathToCache(filePath)", ->
it "adds the path to the correct CSON, CoffeeScript, or 6to5 cache", ->
it "adds the path to the correct CSON, CoffeeScript, or babel cache", ->
spyOn(CSON, 'readFileSync').andCallThrough()
spyOn(CoffeeCache, 'addPathToCache').andCallThrough()
spyOn(to5, 'addPathToCache').andCallThrough()
spyOn(babel, 'addPathToCache').andCallThrough()
CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'cson.cson'))
expect(CSON.readFileSync.callCount).toBe 1
expect(CoffeeCache.addPathToCache.callCount).toBe 0
expect(to5.addPathToCache.callCount).toBe 0
expect(babel.addPathToCache.callCount).toBe 0
CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'coffee.coffee'))
expect(CSON.readFileSync.callCount).toBe 1
expect(CoffeeCache.addPathToCache.callCount).toBe 1
expect(to5.addPathToCache.callCount).toBe 0
expect(babel.addPathToCache.callCount).toBe 0
CompileCache.addPathToCache(path.join(__dirname, 'fixtures', '6to5', 'double-quotes.js'))
CompileCache.addPathToCache(path.join(__dirname, 'fixtures', 'babel', 'babel-double-quotes.js'))
expect(CSON.readFileSync.callCount).toBe 1
expect(CoffeeCache.addPathToCache.callCount).toBe 1
expect(to5.addPathToCache.callCount).toBe 1
expect(babel.addPathToCache.callCount).toBe 1

View File

@@ -0,0 +1,40 @@
DefaultDirectoryProvider = require "../src/default-directory-provider"
path = require "path"
temp = require "temp"
describe "DefaultDirectoryProvider", ->
describe ".directoryForURISync(uri)", ->
it "returns a Directory with a path that matches the uri", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
directory = provider.directoryForURISync(tmp)
expect(directory.getPath()).toEqual tmp
it "normalizes its input before creating a Directory for it", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
nonNormalizedPath = tmp + path.sep + ".." + path.sep + path.basename(tmp)
expect(tmp.contains("..")).toBe false
expect(nonNormalizedPath.contains("..")).toBe true
directory = provider.directoryForURISync(nonNormalizedPath)
expect(directory.getPath()).toEqual tmp
it "creates a Directory for its parent dir when passed a file", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
file = path.join(tmp, "example.txt")
fs.writeFileSync(file, "data")
directory = provider.directoryForURISync(file)
expect(directory.getPath()).toEqual tmp
describe ".directoryForURI(uri)", ->
it "returns a Promise that resolves to a Directory with a path that matches the uri", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
waitsForPromise ->
provider.directoryForURI(tmp).then (directory) ->
expect(directory.getPath()).toEqual tmp

View File

@@ -0,0 +1,3 @@
"use babel";
module.exports = v => v + 1

View File

@@ -0,0 +1,3 @@
'use babel';
module.exports = v => v + 1

View File

@@ -0,0 +1,3 @@
{Git} = require 'atom'
module.exports = ->

View File

@@ -1,11 +1,12 @@
path = require 'path'
fs = require 'fs-plus'
temp = require 'temp'
{Directory} = require 'pathwatcher'
GitRepository = require '../src/git-repository'
GitRepositoryProvider = require '../src/git-repository-provider'
describe "GitRepositoryProvider", ->
describe ".repositoryForDirectory(directory)", ->
describe "when specified a Directory with a Git repository", ->
it "returns a Promise that resolves to a GitRepository", ->
waitsForPromise ->
@@ -37,6 +38,19 @@ describe "GitRepositoryProvider", ->
it "returns a Promise that resolves to null", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
directory = new Directory '/tmp'
directory = new Directory temp.mkdirSync('dir')
provider.repositoryForDirectory(directory).then (result) ->
expect(result).toBe null
describe "when specified a Directory with an invalid Git repository", ->
it "returns a Promise that resolves to null", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
dirPath = temp.mkdirSync('dir')
fs.writeFileSync(path.join(dirPath, '.git', 'objects'), '')
fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), '')
fs.writeFileSync(path.join(dirPath, '.git', 'refs'), '')
directory = new Directory dirPath
provider.repositoryForDirectory(directory).then (result) ->
expect(result).toBe null

View File

@@ -89,23 +89,26 @@ buildAtomClient = (args, env) ->
done()
module.exports = (args, env, fn) ->
chromedriver = spawn(ChromedriverPath, [
"--verbose",
"--port=#{ChromedriverPort}",
"--url-base=/wd/hub"
])
[chromedriver, chromedriverLogs, chromedriverExit] = []
waits(50)
runs ->
chromedriver = spawn(ChromedriverPath, [
"--verbose",
"--port=#{ChromedriverPort}",
"--url-base=/wd/hub"
])
chromedriverLogs = []
chromedriverExit = new Promise (resolve) ->
errorCode = null
chromedriver.on "exit", (code, signal) ->
errorCode = code unless signal?
chromedriver.stderr.on "data", (log) ->
chromedriverLogs.push(log.toString())
chromedriver.stderr.on "close", ->
resolve(errorCode)
chromedriverLogs = []
chromedriverExit = new Promise (resolve) ->
errorCode = null
chromedriver.on "exit", (code, signal) ->
errorCode = code unless signal?
chromedriver.stderr.on "data", (log) ->
chromedriverLogs.push(log.toString())
chromedriver.stderr.on "close", ->
resolve(errorCode)
waits(100)
waitsFor("webdriver to finish", (done) ->
finish = once ->

View File

@@ -86,7 +86,7 @@ describe "Starting Atom", ->
.waitForPaneItemCount(1, 5000)
it "allows multiple project directories to be passed as separate arguments", ->
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) ->
runAtom [tempDirPath, otherTempDirPath, "--multi-folder"], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.then((exists) -> expect(exists).toBe true)
@@ -100,3 +100,51 @@ describe "Starting Atom", ->
.waitForPaneItemCount(1, 5000)
.execute(-> atom.project.getPaths())
.then(({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath]))
it "opens each path in its own window unless the --multi-folder flag is passed", ->
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) ->
projectPaths = []
client
.waitForWindowCount(2, 5000)
.windowHandles()
.then ({value: windowHandles}) ->
@window(windowHandles[0])
.execute(-> atom.project.getPaths())
.then ({value}) ->
expect(value).toHaveLength(1)
projectPaths.push(value[0])
.window(windowHandles[1])
.execute(-> atom.project.getPaths())
.then ({value}) ->
expect(value).toHaveLength(1)
projectPaths.push(value[0])
.then ->
expect(projectPaths.sort()).toEqual([tempDirPath, otherTempDirPath].sort())
it "opens the path in the current window if it doesn't have a project path yet", ->
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace")
.startAnotherAtom([tempDirPath], ATOM_HOME: AtomHome)
.waitUntil((->
@title()
.then(({value}) -> value.indexOf(path.basename(tempDirPath)) >= 0)), 5000)
.waitForWindowCount(1, 5000)
it "always opens with a single untitled buffer when launched w/ no path", ->
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
# Opening with no file paths always creates a new window, even if
# existing windows have no project paths.
.waitForNewWindow(->
@startAnotherAtom([], ATOM_HOME: AtomHome)
, 5000)

View File

@@ -13,6 +13,104 @@ describe "Project", ->
beforeEach ->
atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')])
describe "constructor", ->
it "enables a custom DirectoryProvider to supersede the DefaultDirectoryProvider", ->
remotePath = "ssh://foreign-directory:8080/"
class DummyDirectory
constructor: (@path) ->
getPath: -> @path
getFile: -> existsSync: -> false
getSubdirectory: -> existsSync: -> false
isRoot: -> true
off: ->
contains: (filePath) -> filePath.startsWith(remotePath)
directoryProvider =
directoryForURISync: (uri) ->
if uri.startsWith("ssh://")
new DummyDirectory(uri)
else
null
directoryForURI: (uri) -> throw new Error("This should not be called.")
atom.packages.serviceHub.provide(
"atom.directory-provider", "0.1.0", directoryProvider)
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 "tries to update repositories when a new RepositoryProvider is registered", ->
tmp = temp.mkdirSync('atom-project')
atom.project.setPaths([tmp])
expect(atom.project.getRepositories()).toEqual [null]
expect(atom.project.repositoryProviders.length).toEqual 1
# Register a new RepositoryProvider.
dummyRepository = destroy: ->
repositoryProvider =
repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository)
repositoryForDirectorySync: (directory) -> dummyRepository
atom.packages.serviceHub.provide(
"atom.repository-provider", "0.1.0", repositoryProvider)
expect(atom.project.repositoryProviders.length).toBe 2
expect(atom.project.getRepositories()).toEqual [dummyRepository]
it "does not update @repositories if every path has a Repository", ->
repositories = atom.project.getRepositories()
expect(repositories.length).toEqual 1
[repository] = repositories
expect(repository).toBeTruthy()
# Register a new RepositoryProvider.
dummyRepository = destroy: ->
repositoryProvider =
repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository)
repositoryForDirectorySync: (directory) -> dummyRepository
atom.packages.serviceHub.provide(
"atom.repository-provider", "0.1.0", repositoryProvider)
expect(atom.project.getRepositories()).toBe repositories
describe "serialization", ->
deserializedProject = null
@@ -296,12 +394,46 @@ describe "Project", ->
expect(atom.project.getPaths()).toEqual([oldPath])
expect(onDidChangePathsSpy).not.toHaveBeenCalled()
describe ".removePath(path)", ->
onDidChangePathsSpy = null
beforeEach ->
onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths listener')
atom.project.onDidChangePaths(onDidChangePathsSpy)
it "removes the directory and repository for the path", ->
result = atom.project.removePath(atom.project.getPaths()[0])
expect(atom.project.getDirectories()).toEqual([])
expect(atom.project.getRepositories()).toEqual([])
expect(atom.project.getPaths()).toEqual([])
expect(result).toBe true
expect(onDidChangePathsSpy).toHaveBeenCalled()
it "does nothing if the path is not one of the project's root paths", ->
originalPaths = atom.project.getPaths()
result = atom.project.removePath(originalPaths[0] + "xyz")
expect(result).toBe false
expect(atom.project.getPaths()).toEqual(originalPaths)
expect(onDidChangePathsSpy).not.toHaveBeenCalled()
it "doesn't destroy the repository if it is shared by another root directory", ->
atom.project.setPaths([__dirname, path.join(__dirname, "..", "src")])
atom.project.removePath(__dirname)
expect(atom.project.getPaths()).toEqual([path.join(__dirname, "..", "src")])
expect(atom.project.getRepositories()[0].isSubmodule("src")).toBe false
describe ".relativize(path)", ->
it "returns the path, relative to whichever root directory it is inside of", ->
atom.project.addPath(temp.mkdirSync("another-path"))
rootPath = atom.project.getPaths()[0]
childPath = path.join(rootPath, "some", "child", "directory")
expect(atom.project.relativize(childPath)).toBe path.join("some", "child", "directory")
rootPath = atom.project.getPaths()[1]
childPath = path.join(rootPath, "some", "child", "directory")
expect(atom.project.relativize(childPath)).toBe path.join("some", "child", "directory")
it "returns the given path if it is not in any of the root directories", ->
randomPath = path.join("some", "random", "path")
expect(atom.project.relativize(randomPath)).toBe randomPath

View File

@@ -76,12 +76,13 @@ beforeEach ->
$.fx.off = true
documentTitle = null
projectPath = specProjectPath ? path.join(@specDirectory, 'fixtures')
atom.packages.serviceHub = new ServiceHub
atom.project = new Project(paths: [projectPath])
atom.workspace = new Workspace()
atom.packages.serviceHub = new ServiceHub
atom.keymaps.keyBindings = _.clone(keyBindingsToRestore)
atom.commands.restoreSnapshot(commandsToRestore)
atom.styles.restoreSnapshot(styleElementsToRestore)
atom.views.clearDocumentRequests()
atom.workspaceViewParentSelector = '#jasmine-content'

View File

@@ -1,4 +1,5 @@
Task = require '../src/task'
Grim = require 'grim'
describe "Task", ->
describe "@once(taskPath, args..., callback)", ->
@@ -43,3 +44,16 @@ describe "Task", ->
runs ->
expect(eventSpy).not.toHaveBeenCalled()
it "reports deprecations in tasks", ->
jasmine.snapshotDeprecations()
handlerPath = require.resolve('./fixtures/task-handler-with-deprecations')
task = new Task(handlerPath)
waitsFor (done) -> task.start(done)
runs ->
deprecations = Grim.getDeprecations()
expect(deprecations.length).toBe 1
expect(deprecations[0].getStacks()[0][1].fileName).toBe handlerPath
jasmine.restoreDeprecationsSnapshot()

View File

@@ -46,7 +46,7 @@ describe "TextEditorComponent", ->
lineHeightInPixels = editor.getLineHeightInPixels()
charWidth = editor.getDefaultCharWidth()
componentNode = component.getDOMNode()
componentNode = component.domNode
verticalScrollbarNode = componentNode.querySelector('.vertical-scrollbar')
horizontalScrollbarNode = componentNode.querySelector('.horizontal-scrollbar')
@@ -193,7 +193,8 @@ describe "TextEditorComponent", ->
expect(linesNode.style.backgroundColor).toBe backgroundColor
wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)'
advanceClock(component.domPollingInterval)
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
@@ -466,11 +467,6 @@ describe "TextEditorComponent", ->
expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy()
describe "gutter rendering", ->
[gutter] = []
beforeEach ->
{gutter} = component.refs
it "renders the currently-visible line numbers", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
@@ -567,32 +563,32 @@ describe "TextEditorComponent", ->
# favor gutter color if it's assigned
gutterNode.style.backgroundColor = 'rgb(255, 0, 0)'
advanceClock(component.domPollingInterval)
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
it "hides or shows the gutter based on the '::isGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", ->
expect(component.refs.gutter?).toBe true
expect(component.gutterComponent?).toBe true
editor.setGutterVisible(false)
nextAnimationFrame()
expect(component.refs.gutter?).toBe false
expect(componentNode.querySelector('.gutter')).toBeNull()
atom.config.set("editor.showLineNumbers", false)
expect(nextAnimationFrame).toBe noAnimationFrame
nextAnimationFrame()
expect(component.refs.gutter?).toBe false
expect(componentNode.querySelector('.gutter')).toBeNull()
editor.setGutterVisible(true)
expect(nextAnimationFrame).toBe noAnimationFrame
nextAnimationFrame()
expect(component.refs.gutter?).toBe false
expect(componentNode.querySelector('.gutter')).toBeNull()
atom.config.set("editor.showLineNumbers", true)
nextAnimationFrame()
expect(component.refs.gutter?).toBe true
expect(componentNode.querySelector('.gutter')).toBeDefined()
expect(component.lineNumberNodeForScreenRow(3)?).toBe true
describe "fold decorations", ->
@@ -706,13 +702,13 @@ describe "TextEditorComponent", ->
cursorNodes = componentNode.querySelectorAll('.cursor')
expect(cursorNodes.length).toBe 2
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)"
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)"
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
wrapperView.on 'cursor:moved', cursorMovedListener = jasmine.createSpy('cursorMovedListener')
cursor3.setScreenPosition([4, 11], autoscroll: false)
nextAnimationFrame()
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)"
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)"
expect(cursorMovedListener).toHaveBeenCalled()
cursor3.destroy()
@@ -800,11 +796,11 @@ describe "TextEditorComponent", ->
expect(cursorsNode.classList.contains('blink-off')).toBe false
advanceClock(component.props.cursorBlinkPeriod / 2)
advanceClock(component.cursorBlinkPeriod / 2)
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe true
advanceClock(component.props.cursorBlinkPeriod / 2)
advanceClock(component.cursorBlinkPeriod / 2)
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe false
@@ -813,8 +809,8 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe false
advanceClock(component.props.cursorBlinkResumeDelay)
advanceClock(component.props.cursorBlinkPeriod / 2)
advanceClock(component.cursorBlinkResumeDelay)
advanceClock(component.cursorBlinkPeriod / 2)
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe true
@@ -1501,12 +1497,14 @@ describe "TextEditorComponent", ->
expect(inputNode.offsetLeft).toBe 0
# In bounds and focused
inputNode.focus() # updates via state change
wrapperNode.focus() # updates via state change
nextAnimationFrame()
expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - editor.getScrollTop()
expect(inputNode.offsetLeft).toBe (4 * charWidth) - editor.getScrollLeft()
# In bounds, not focused
inputNode.blur() # updates via state change
nextAnimationFrame()
expect(inputNode.offsetTop).toBe 0
expect(inputNode.offsetLeft).toBe 0
@@ -1518,6 +1516,7 @@ describe "TextEditorComponent", ->
# Out of bounds, focused
inputNode.focus() # updates via state change
nextAnimationFrame()
expect(inputNode.offsetTop).toBe 0
expect(inputNode.offsetLeft).toBe 0
@@ -1839,9 +1838,11 @@ describe "TextEditorComponent", ->
it "adds the 'is-focused' class to the editor when the hidden input is focused", ->
expect(document.activeElement).toBe document.body
inputNode.focus()
nextAnimationFrame()
expect(componentNode.classList.contains('is-focused')).toBe true
expect(wrapperView.hasClass('is-focused')).toBe true
inputNode.blur()
nextAnimationFrame()
expect(componentNode.classList.contains('is-focused')).toBe false
expect(wrapperView.hasClass('is-focused')).toBe false
@@ -2351,11 +2352,11 @@ describe "TextEditorComponent", ->
wrapperView.appendTo(hiddenParent)
{component} = wrapperView
componentNode = component.getDOMNode()
componentNode = component.domNode
expect(componentNode.querySelectorAll('.line').length).toBe 0
hiddenParent.style.display = 'block'
advanceClock(component.domPollingInterval)
advanceClock(atom.views.documentPollingInterval)
expect(componentNode.querySelectorAll('.line').length).toBeGreaterThan 0
@@ -2465,14 +2466,15 @@ describe "TextEditorComponent", ->
expect(parseInt(newHeight)).toBeLessThan wrapperNode.offsetHeight
wrapperNode.style.height = newHeight
advanceClock(component.domPollingInterval)
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1)
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
advanceClock(component.domPollingInterval)
nextAnimationFrame()
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame() # won't poll until cursor blinks
nextAnimationFrame() # handle update requested by poll
expect(componentNode.querySelector('.line').textContent).toBe "var quicksort "
it "accounts for the scroll view's padding when determining the wrap location", ->
@@ -2480,7 +2482,7 @@ describe "TextEditorComponent", ->
scrollViewNode.style.paddingLeft = 20 + 'px'
componentNode.style.width = 30 * charWidth + 'px'
advanceClock(component.domPollingInterval)
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(component.lineNodeForScreenRow(0).textContent).toBe "var quicksort = "
@@ -2558,7 +2560,7 @@ describe "TextEditorComponent", ->
expect(wrapperNode.classList.contains('mini')).toBe true
it "does not have an opaque background on lines", ->
expect(component.refs.lines.getDOMNode().getAttribute('style')).not.toContain 'background-color'
expect(component.linesComponent.domNode.getAttribute('style')).not.toContain 'background-color'
it "does not render invisible characters", ->
atom.config.set('editor.invisibles', eol: 'E')

View File

@@ -46,12 +46,12 @@ describe "TextEditorElement", ->
jasmine.attachToDOM(element)
component = element.component
expect(component.isMounted()).toBe true
expect(component.mounted).toBe true
element.remove()
expect(component.isMounted()).toBe false
expect(component.mounted).toBe false
jasmine.attachToDOM(element)
expect(element.component.isMounted()).toBe true
expect(element.component.mounted).toBe true
describe "when the editor.useShadowDOM config option is false", ->
it "mounts the react component and unmounts when removed from the dom", ->
@@ -61,9 +61,9 @@ describe "TextEditorElement", ->
jasmine.attachToDOM(element)
component = element.component
expect(component.isMounted()).toBe true
expect(component.mounted).toBe true
element.getModel().destroy()
expect(component.isMounted()).toBe false
expect(component.mounted).toBe false
describe "focus and blur handling", ->
describe "when the editor.useShadowDOM config option is true", ->

View File

@@ -77,6 +77,18 @@ describe "TextEditorPresenter", ->
presenter.setExplicitHeight((editor.getLineCount() * 10) - 1)
expect(state.horizontalScrollbar.visible).toBe true
it "is false if the editor is mini", ->
presenter = buildPresenter
explicitHeight: editor.getLineCount() * 10
contentFrameWidth: editor.getMaxScreenLineLength() * 10 - 10
baseCharacterWidth: 10
expect(presenter.state.horizontalScrollbar.visible).toBe true
editor.setMini(true)
expect(presenter.state.horizontalScrollbar.visible).toBe false
editor.setMini(false)
expect(presenter.state.horizontalScrollbar.visible).toBe true
describe ".height", ->
it "tracks the value of ::horizontalScrollbarHeight", ->
presenter = buildPresenter(horizontalScrollbarHeight: 10)
@@ -311,6 +323,69 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.contentHeight - presenter.clientHeight
describe ".hiddenInput", ->
describe ".top/.left", ->
it "is positioned over the last cursor it is in view and the editor is focused", ->
editor.setCursorBufferPosition([3, 6])
presenter = buildPresenter(focused: false, explicitHeight: 50, contentFrameWidth: 300, horizontalScrollbarHeight: 0, verticalScrollbarWidth: 0)
expectValues presenter.state.hiddenInput, {top: 0, left: 0}
expectStateUpdate presenter, -> presenter.setFocused(true)
expectValues presenter.state.hiddenInput, {top: 3 * 10, left: 6 * 10}
expectStateUpdate presenter, -> presenter.setScrollTop(15)
expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: 6 * 10}
expectStateUpdate presenter, -> presenter.setScrollLeft(35)
expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: (6 * 10) - 35}
expectStateUpdate presenter, -> presenter.setScrollTop(40)
expectValues presenter.state.hiddenInput, {top: 0, left: (6 * 10) - 35}
expectStateUpdate presenter, -> presenter.setScrollLeft(70)
expectValues presenter.state.hiddenInput, {top: 0, left: 0}
expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43])
expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10}
newCursor = null
expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10])
expectValues presenter.state.hiddenInput, {top: (6 * 10) - 40, left: (10 * 10) - 70}
expectStateUpdate presenter, -> newCursor.destroy()
expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10}
expectStateUpdate presenter, -> presenter.setFocused(false)
expectValues presenter.state.hiddenInput, {top: 0, left: 0}
describe ".height", ->
it "is assigned based on the line height", ->
presenter = buildPresenter()
expect(presenter.state.hiddenInput.height).toBe 10
expectStateUpdate presenter, -> presenter.setLineHeight(20)
expect(presenter.state.hiddenInput.height).toBe 20
describe ".width", ->
it "is assigned based on the width of the character following the cursor", ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs ->
editor.setCursorBufferPosition([3, 6])
presenter = buildPresenter()
expect(presenter.state.hiddenInput.width).toBe 10
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
expect(presenter.state.hiddenInput.width).toBe 15
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
expect(presenter.state.hiddenInput.width).toBe 20
it "is 2px at the end of lines", ->
presenter = buildPresenter()
editor.setCursorBufferPosition([3, Infinity])
expect(presenter.state.hiddenInput.width).toBe 2
describe ".content", ->
describe ".scrollingVertically", ->
it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", ->
@@ -1895,6 +1970,42 @@ describe "TextEditorPresenter", ->
editor.undo()
expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe false
describe ".visible", ->
it "is true iff the editor isn't mini, ::isGutterVisible is true on the editor, and 'editor.showLineNumbers' is enabled in config", ->
presenter = buildPresenter()
expect(editor.isGutterVisible()).toBe true
expect(presenter.state.gutter.visible).toBe true
expectStateUpdate presenter, -> editor.setMini(true)
expect(presenter.state.gutter.visible).toBe false
expectStateUpdate presenter, -> editor.setMini(false)
expect(presenter.state.gutter.visible).toBe true
expectStateUpdate presenter, -> editor.setGutterVisible(false)
expect(presenter.state.gutter.visible).toBe false
expectStateUpdate presenter, -> editor.setGutterVisible(true)
expect(presenter.state.gutter.visible).toBe true
expectStateUpdate presenter, -> atom.config.set('editor.showLineNumbers', false)
expect(presenter.state.gutter.visible).toBe false
it "updates when the editor's grammar changes", ->
presenter = buildPresenter()
atom.config.set('editor.showLineNumbers', false, scopeSelector: '.source.js')
expect(presenter.state.gutter.visible).toBe true
stateUpdated = false
presenter.onDidUpdateState -> stateUpdated = true
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs ->
expect(stateUpdated).toBe true
expect(presenter.state.gutter.visible).toBe false
describe ".height", ->
it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", ->
presenter = buildPresenter(explicitHeight: null, autoHeight: true)
@@ -1912,16 +2023,30 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n")
expect(presenter.state.height).toBe editor.getScreenLineCount() * 20
describe "when the model and view measurements are mutated randomly", ->
describe ".focused", ->
it "tracks the value of ::focused", ->
presenter = buildPresenter(focused: false)
expect(presenter.state.focused).toBe false
expectStateUpdate presenter, -> presenter.setFocused(true)
expect(presenter.state.focused).toBe true
expectStateUpdate presenter, -> presenter.setFocused(false)
expect(presenter.state.focused).toBe false
# disabled until we fix an issue with display buffer markers not updating when
# they are moved on screen but not in the buffer
xdescribe "when the model and view measurements are mutated randomly", ->
[editor, buffer, presenterParams, presenter, statements] = []
recordStatement = (statement) -> statements.push(statement)
it "correctly maintains the presenter state", ->
_.times 20, ->
waits(0)
runs ->
performSetup()
performRandomInitialization(recordStatement)
_.times 20, ->
performRandomAction (statement) -> statements.push(statement)
performRandomAction recordStatement
expectValidState()
performTeardown()
@@ -1937,18 +2062,25 @@ describe "TextEditorPresenter", ->
editor.setEditorWidthInChars(80)
presenterParams =
model: editor
explicitHeight: 50
contentFrameWidth: 300
scrollTop: 0
scrollLeft: 0
lineHeight: 10
baseCharacterWidth: 10
lineOverdrawMargin: 1
horizontalScrollbarHeight: 5
verticalScrollbarWidth: 5
presenter = new TextEditorPresenter(presenterParams)
statements = []
performRandomInitialization = (log) ->
actions = _.shuffle([
changeScrollLeft
changeScrollTop
changeExplicitHeight
changeContentFrameWidth
changeLineHeight
changeBaseCharacterWidth
changeHorizontalScrollbarHeight
changeVerticalScrollbarWidth
])
for action in actions
action(log)
expectValidState()
performTeardown = ->
buffer.destroy()
@@ -1989,14 +2121,16 @@ describe "TextEditorPresenter", ->
])(log)
changeScrollTop = (log) ->
scrollHeight = presenterParams.lineHeight * editor.getScreenLineCount()
newScrollTop = Math.max(0, _.random(0, scrollHeight - presenterParams.explicitHeight))
scrollHeight = (presenterParams.lineHeight ? 10) * editor.getScreenLineCount()
explicitHeight = (presenterParams.explicitHeight ? 500)
newScrollTop = Math.max(0, _.random(0, scrollHeight - explicitHeight))
log "presenter.setScrollTop(#{newScrollTop})"
presenter.setScrollTop(newScrollTop)
changeScrollLeft = (log) ->
scrollWidth = presenter.scrollWidth
newScrollLeft = Math.max(0, _.random(0, scrollWidth - presenterParams.contentFrameWidth))
scrollWidth = presenter.scrollWidth ? 300
contentFrameWidth = presenter.contentFrameWidth ? 200
newScrollLeft = Math.max(0, _.random(0, scrollWidth - contentFrameWidth))
log """
presenterParams.scrollLeft = #{newScrollLeft}
presenter.setScrollLeft(#{newScrollLeft})
@@ -2005,7 +2139,7 @@ describe "TextEditorPresenter", ->
presenter.setScrollLeft(newScrollLeft)
changeExplicitHeight = (log) ->
scrollHeight = presenterParams.lineHeight * editor.getScreenLineCount()
scrollHeight = (presenterParams.lineHeight ? 10) * editor.getScreenLineCount()
newExplicitHeight = _.random(30, scrollHeight * 1.5)
log """
presenterParams.explicitHeight = #{newExplicitHeight}
@@ -2015,7 +2149,7 @@ describe "TextEditorPresenter", ->
presenter.setExplicitHeight(newExplicitHeight)
changeContentFrameWidth = (log) ->
scrollWidth = presenter.scrollWidth
scrollWidth = presenter.scrollWidth ? 300
newContentFrameWidth = _.random(100, scrollWidth * 1.5)
log """
presenterParams.contentFrameWidth = #{newContentFrameWidth}
@@ -2024,6 +2158,42 @@ describe "TextEditorPresenter", ->
presenterParams.contentFrameWidth = newContentFrameWidth
presenter.setContentFrameWidth(newContentFrameWidth)
changeLineHeight = (log) ->
newLineHeight = _.random(5, 15)
log """
presenterParams.lineHeight = #{newLineHeight}
presenter.setLineHeight(#{newLineHeight})
"""
presenterParams.lineHeight = newLineHeight
presenter.setLineHeight(newLineHeight)
changeBaseCharacterWidth = (log) ->
newBaseCharacterWidth = _.random(5, 15)
log """
presenterParams.baseCharacterWidth = #{newBaseCharacterWidth}
presenter.setBaseCharacterWidth(#{newBaseCharacterWidth})
"""
presenterParams.baseCharacterWidth = newBaseCharacterWidth
presenter.setBaseCharacterWidth(newBaseCharacterWidth)
changeHorizontalScrollbarHeight = (log) ->
newHorizontalScrollbarHeight = _.random(2, 15)
log """
presenterParams.horizontalScrollbarHeight = #{newHorizontalScrollbarHeight}
presenter.setHorizontalScrollbarHeight(#{newHorizontalScrollbarHeight})
"""
presenterParams.horizontalScrollbarHeight = newHorizontalScrollbarHeight
presenter.setHorizontalScrollbarHeight(newHorizontalScrollbarHeight)
changeVerticalScrollbarWidth = (log) ->
newVerticalScrollbarWidth = _.random(2, 15)
log """
presenterParams.verticalScrollbarWidth = #{newVerticalScrollbarWidth}
presenter.setVerticalScrollbarWidth(#{newVerticalScrollbarWidth})
"""
presenterParams.verticalScrollbarWidth = newVerticalScrollbarWidth
presenter.setVerticalScrollbarWidth(newVerticalScrollbarWidth)
toggleSoftWrap = (log) ->
softWrapped = not editor.isSoftWrapped()
log "editor.setSoftWrapped(#{softWrapped})"

View File

@@ -85,3 +85,78 @@ describe "ViewRegistry", ->
expect(registry.getView(new TestModel) instanceof TestView).toBe true
disposable.dispose()
expect(-> registry.getView(new TestModel)).toThrow()
describe "::updateDocument(fn) and ::readDocument(fn)", ->
frameRequests = null
beforeEach ->
frameRequests = []
spyOn(window, 'requestAnimationFrame').andCallFake (fn) -> frameRequests.push(fn)
it "performs all pending writes before all pending reads on the next animation frame", ->
events = []
registry.updateDocument -> events.push('write 1')
registry.readDocument -> events.push('read 1')
registry.readDocument -> events.push('read 2')
registry.updateDocument -> events.push('write 2')
expect(events).toEqual []
expect(frameRequests.length).toBe 1
frameRequests[0]()
expect(events).toEqual ['write 1', 'write 2', 'read 1', 'read 2']
frameRequests = []
events = []
disposable = registry.updateDocument -> events.push('write 3')
registry.updateDocument -> events.push('write 4')
registry.readDocument -> events.push('read 3')
disposable.dispose()
expect(frameRequests.length).toBe 1
frameRequests[0]()
expect(events).toEqual ['write 4', 'read 3']
it "pauses DOM polling when reads or writes are pending", ->
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
spyOn(window, 'clearInterval').andCallFake(fakeClearInterval)
events = []
registry.pollDocument -> events.push('poll')
registry.updateDocument -> events.push('write')
registry.readDocument -> events.push('read')
advanceClock(registry.documentPollingInterval)
expect(events).toEqual []
frameRequests[0]()
expect(events).toEqual ['write', 'read', 'poll']
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['write', 'read', 'poll', 'poll']
describe "::pollDocument(fn)", ->
it "calls all registered reader functions on an interval until they are disabled via a returned disposable", ->
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
events = []
disposable1 = registry.pollDocument -> events.push('poll 1')
disposable2 = registry.pollDocument -> events.push('poll 2')
expect(events).toEqual []
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['poll 1', 'poll 2']
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2']
disposable1.dispose()
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2']
disposable2.dispose()
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2']

View File

@@ -96,7 +96,7 @@ class Atom extends Model
filename = 'spec'
when 'editor'
{initialPaths} = @getLoadSettings()
if initialPaths
if initialPaths?.length > 0
sha1 = crypto.createHash('sha1').update(initialPaths.join("\n")).digest('hex')
filename = "editor-#{sha1}"
@@ -803,7 +803,7 @@ class Atom extends Model
# require completes.
#
# * `id` The {String} module name or path.
# * `globals` An optinal {Object} to set as globals during require.
# * `globals` An optional {Object} to set as globals during require.
requireWithGlobals: (id, globals={}) ->
existingGlobals = {}
for key, value of globals

View File

@@ -1,5 +1,5 @@
###
Cache for source code transpiled by 6to5.
Cache for source code transpiled by Babel.
Inspired by https://github.com/atom/atom/blob/6b963a562f8d495fbebe6abdbafbc7caf705f2c3/src/coffee-cache.coffee.
###
@@ -7,7 +7,7 @@ Inspired by https://github.com/atom/atom/blob/6b963a562f8d495fbebe6abdbafbc7caf7
crypto = require 'crypto'
fs = require 'fs-plus'
path = require 'path'
to5 = null # Defer until used
babel = null # Defer until used
stats =
hits: 0
@@ -18,11 +18,6 @@ defaultOptions =
# when the source map is inlined.
sourceMap: 'inline'
# Because Atom is currently packaged with a fork of React v0.11,
# it makes sense to use the --react-compat option so the React
# JSX transformer produces pre-v0.12 code.
reactCompat: true
# Blacklisted features do not get transpiled. Features that are
# natively supported in the target environment should be listed
# here. Because Atom uses a bleeding edge version of Node/io.js,
@@ -33,13 +28,18 @@ defaultOptions =
]
# Includes support for es7 features listed at:
# http://6to5.org/docs/usage/transformers/#es7-experimental-.
# http://babeljs.io/docs/usage/transformers/#es7-experimental-.
experimental: true
optional: [
# Target a version of the regenerator runtime that
# supports yield so the transpiled code is cleaner/smaller.
'asyncToGenerator'
# Because Atom is currently packaged with a fork of React v0.11,
# it makes sense to use the reactCompat transform so the React
# JSX transformer produces pre-v0.12 code.
'reactCompat'
]
###
@@ -79,10 +79,10 @@ updateDigestForJsonValue = (shasum, value) ->
shasum.update(',', 'utf8')
shasum.update('}', 'utf8')
create6to5VersionAndOptionsDigest = (version, options) ->
createBabelVersionAndOptionsDigest = (version, options) ->
shasum = crypto.createHash('sha1')
# Include the version of 6to5 in the hash.
shasum.update('6to5-core', 'utf8')
# Include the version of babel in the hash.
shasum.update('babel-core', 'utf8')
shasum.update('\0', 'utf8')
shasum.update(version, 'utf8')
shasum.update('\0', 'utf8')
@@ -96,8 +96,8 @@ getCachePath = (sourceCode) ->
digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex')
unless jsCacheDir?
to5Version = require('6to5-core/package.json').version
jsCacheDir = path.join(cacheDir, create6to5VersionAndOptionsDigest(to5Version, defaultOptions))
to5Version = require('babel-core/package.json').version
jsCacheDir = path.join(cacheDir, createBabelVersionAndOptionsDigest(to5Version, defaultOptions))
path.join(jsCacheDir, "#{digest}.js")
@@ -109,7 +109,7 @@ getCachedJavaScript = (cachePath) ->
return cachedJavaScript
null
# Returns the 6to5 options that should be used to transpile filePath.
# Returns the babel options that should be used to transpile filePath.
createOptions = (filePath) ->
options = filename: filePath
for key, value of defaultOptions
@@ -118,8 +118,8 @@ createOptions = (filePath) ->
transpile = (sourceCode, filePath, cachePath) ->
options = createOptions(filePath)
to5 ?= require '6to5-core'
js = to5.transform(sourceCode, options).code
babel ?= require 'babel-core'
js = babel.transform(sourceCode, options).code
stats.misses++
try
@@ -132,8 +132,10 @@ transpile = (sourceCode, filePath, cachePath) ->
# either generated on the fly or pulled from cache.
loadFile = (module, filePath) ->
sourceCode = fs.readFileSync(filePath, 'utf8')
unless sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'")
return module._compile(sourceCode, filePath)
return module._compile(sourceCode, filePath) unless sourceCode.startsWith('"use 6to5"') or
sourceCode.startsWith("'use 6to5'") or
sourceCode.startsWith('"use babel"') or
sourceCode.startsWith("'use babel'")
cachePath = getCachePath(sourceCode)
js = getCachedJavaScript(cachePath) ? transpile(sourceCode, filePath, cachePath)
@@ -158,7 +160,7 @@ module.exports =
getCacheHits: -> stats.hits
# Visible for testing.
create6to5VersionAndOptionsDigest: create6to5VersionAndOptionsDigest
createBabelVersionAndOptionsDigest: createBabelVersionAndOptionsDigest
addPathToCache: (filePath) ->
return if path.extname(filePath) isnt '.js'

View File

@@ -92,19 +92,23 @@ class ApplicationMenu
# Sets the proper visible state the update menu items
showUpdateMenuItem: (state) ->
checkForUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Check for Update')
checkingForUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Checking for Update')
downloadingUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Downloading Update')
installUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label == 'Restart and Install Update')
return unless checkForUpdateItem? and downloadingUpdateItem? and installUpdateItem?
return unless checkForUpdateItem? and checkingForUpdateItem? and downloadingUpdateItem? and installUpdateItem?
checkForUpdateItem.visible = false
checkingForUpdateItem.visible = false
downloadingUpdateItem.visible = false
installUpdateItem.visible = false
switch state
when 'idle', 'error', 'no-update-available'
checkForUpdateItem.visible = true
when 'checking', 'downloading'
when 'checking'
checkingForUpdateItem.visible = true
when 'downloading'
downloadingUpdateItem.visible = true
when 'update-available'
installUpdateItem.visible = true

View File

@@ -60,7 +60,7 @@ class AtomApplication
exit: (status) -> app.exit(status)
constructor: (options) ->
{@resourcePath, @version, @devMode, @safeMode, @socketPath} = options
{@resourcePath, @version, @devMode, @safeMode, @socketPath, @enableMultiFolderProject} = options
# Normalize to make sure drive letter case is consistent on Windows
@resourcePath = path.normalize(@resourcePath) if @resourcePath
@@ -348,15 +348,24 @@ class AtomApplication
# :windowDimensions - Object with height and width keys.
# :window - {AtomWindow} to open file paths in.
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) ->
if pathsToOpen?.length > 1 and not @enableMultiFolderProject
for pathToOpen in pathsToOpen
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window})
return
pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen)
locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen)
unless pidToKillWhenClosed or newWindow
existingWindow = @windowForPaths(pathsToOpen, devMode)
# Default to using the specified window or the last focused window
if pathsToOpen.every((pathToOpen) -> fs.statSyncNoException(pathToOpen).isFile?())
existingWindow ?= window ? @lastFocusedWindow
# Default to using the specified window or the last focused window
currentWindow = window ? @lastFocusedWindow
stats = (fs.statSyncNoException(pathToOpen) for pathToOpen in pathsToOpen)
existingWindow ?= currentWindow if (
stats.every((stat) -> stat.isFile?()) or
stats.some((stat) -> stat.isDirectory?()) and not currentWindow?.hasProjectPath()
)
if existingWindow?
openedWindow = existingWindow

View File

@@ -18,8 +18,9 @@ class AtomWindow
isSpec: null
constructor: (settings={}) ->
{@resourcePath, pathToOpen, @locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
@locationsToOpen ?= [{pathToOpen}] if pathToOpen
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
locationsToOpen ?= [{pathToOpen}] if pathToOpen
locationsToOpen ?= []
# Normalize to make sure drive letter case is consistent on Windows
@resourcePath = path.normalize(@resourcePath) if @resourcePath
@@ -52,12 +53,15 @@ class AtomWindow
@constructor.includeShellLoadTime = false
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
loadSettings.initialPaths = for {pathToOpen} in (@locationsToOpen ? [])
if fs.statSyncNoException(pathToOpen).isFile?()
path.dirname(pathToOpen)
else
pathToOpen
loadSettings.initialPaths =
for {pathToOpen} in locationsToOpen when pathToOpen
if fs.statSyncNoException(pathToOpen).isFile?()
path.dirname(pathToOpen)
else
pathToOpen
loadSettings.initialPaths.sort()
@projectPaths = loadSettings.initialPaths
@browserWindow.loadSettings = loadSettings
@browserWindow.once 'window:loaded', =>
@@ -69,7 +73,7 @@ class AtomWindow
@browserWindow.loadUrl @getUrl(loadSettings)
@browserWindow.focusOnWebView() if @isSpec
@openLocations(@locationsToOpen) unless @isSpecWindow()
@openLocations(locationsToOpen) unless @isSpecWindow()
getUrl: (loadSettingsObj) ->
# Ignore the windowState when passing loadSettings via URL, since it could

View File

@@ -118,6 +118,7 @@ parseCommandLine = ->
options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
options.string('socket-path')
options.boolean('multi-folder')
args = options.argv
if args.help
@@ -139,6 +140,7 @@ parseCommandLine = ->
pidToKillWhenClosed = args['pid'] if args['wait']
logFile = args['log-file']
socketPath = args['socket-path']
enableMultiFolderProject = args['multi-folder']
if args['resource-path']
devMode = true
@@ -163,6 +165,7 @@ parseCommandLine = ->
# explicitly pass it by command line, see http://git.io/YC8_Ew.
process.env.PATH = args['path-environment'] if args['path-environment']
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, socketPath}
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed,
devMode, safeMode, newWindow, specDirectory, logFile, socketPath, enableMultiFolderProject}
start()

View File

@@ -1,7 +1,7 @@
path = require 'path'
CSON = require 'season'
CoffeeCache = require 'coffee-cash'
to5 = require './6to5'
babel = require './babel'
# This file is required directly by apm so that files can be cached during
# package install so that the first package load in Atom doesn't have to
@@ -15,7 +15,7 @@ exports.addPathToCache = (filePath, atomHome) ->
CoffeeCache.setCacheDirectory(path.join(cacheDir, 'coffee'))
CSON.setCacheDir(path.join(cacheDir, 'cson'))
to5.setCacheDirectory(path.join(cacheDir, 'js', '6to5'))
babel.setCacheDirectory(path.join(cacheDir, 'js', 'babel'))
switch path.extname(filePath)
when '.coffee'
@@ -23,4 +23,4 @@ exports.addPathToCache = (filePath, atomHome) ->
when '.cson'
CSON.readFileSync(filePath)
when '.js'
to5.addPathToCache(filePath)
babel.addPathToCache(filePath)

View File

@@ -162,10 +162,6 @@ module.exports =
default: 300
minimum: 0
description: 'Time interval in milliseconds within which operations will be grouped together in the undo history'
useHardwareAcceleration:
type: 'boolean'
default: true
description: 'Disabling will improve editor font rendering but reduce scrolling performance.'
useShadowDOM:
type: 'boolean'
default: true

View File

@@ -1,14 +0,0 @@
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{isEqualForProperties} = require 'underscore-plus'
module.exports =
CursorComponent = React.createClass
displayName: 'CursorComponent'
render: ->
{pixelRect} = @props
{top, left, height, width} = pixelRect
WebkitTransform = "translate(#{left}px, #{top}px)"
div className: 'cursor', style: {height, width, WebkitTransform}

View File

@@ -1,19 +1,54 @@
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{debounce, toArray, isEqualForProperties, isEqual} = require 'underscore-plus'
SubscriberMixin = require './subscriber-mixin'
CursorComponent = require './cursor-component'
module.exports =
CursorsComponent = React.createClass
displayName: 'CursorsComponent'
class CursorsComponent
oldState: null
render: ->
{presenter} = @props
constructor: (@presenter) ->
@cursorNodesById = {}
@domNode = document.createElement('div')
@domNode.classList.add('cursors')
@updateSync()
className = 'cursors'
className += ' blink-off' if presenter.state.content.blinkCursorsOff
updateSync: ->
newState = @presenter.state.content
@oldState ?= {cursors: {}}
div {className},
for key, pixelRect of presenter.state.content.cursors
CursorComponent({key, pixelRect})
# update blink class
if newState.blinkCursorsOff isnt @oldState.blinkCursorsOff
if newState.blinkCursorsOff
@domNode.classList.add 'blink-off'
else
@domNode.classList.remove 'blink-off'
@oldState.blinkCursorsOff = newState.blinkCursorsOff
# remove cursors
for id of @oldState.cursors
unless newState.cursors[id]?
@cursorNodesById[id].remove()
delete @cursorNodesById[id]
delete @oldState.cursors[id]
# add or update cursors
for id, cursorState of newState.cursors
unless @oldState.cursors[id]?
cursorNode = document.createElement('div')
cursorNode.classList.add('cursor')
@cursorNodesById[id] = cursorNode
@domNode.appendChild(cursorNode)
@updateCursorNode(id, cursorState)
updateCursorNode: (id, newCursorState) ->
cursorNode = @cursorNodesById[id]
oldCursorState = (@oldState.cursors[id] ?= {})
if newCursorState.top isnt oldCursorState.top or newCursorState.left isnt oldCursorState.left
cursorNode.style['-webkit-transform'] = "translate(#{newCursorState.left}px, #{newCursorState.top}px)"
oldCursorState.top = newCursorState.top
oldCursorState.left = newCursorState.left
if newCursorState.height isnt oldCursorState.height
cursorNode.style.height = newCursorState.height + 'px'
oldCursorState.height = newCursorState.height
if newCursorState.width isnt oldCursorState.width
cursorNode.style.width = newCursorState.width + 'px'
oldCursorState.width = newCursorState.width

View File

@@ -0,0 +1,35 @@
{Directory} = require 'pathwatcher'
fs = require 'fs-plus'
path = require 'path'
module.exports =
class DefaultDirectoryProvider
# Public: Create a Directory that corresponds to the specified URI.
#
# * `uri` {String} The path to the directory to add. This is guaranteed not to
# be contained by a {Directory} in `atom.project`.
#
# Returns:
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURISync: (uri) ->
projectPath = path.normalize(uri)
directoryPath = if fs.isDirectorySync(projectPath)
projectPath
else
path.dirname(projectPath)
new Directory(directoryPath)
# Public: Create a Directory that corresponds to the specified URI.
#
# * `uri` {String} The path to the directory to add. This is guaranteed not to
# be contained by a {Directory} in `atom.project`.
#
# Returns a Promise that resolves to:
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURI: (uri) ->
Promise.resolve(@directoryForURISync(uri))

View File

@@ -11,7 +11,7 @@ findGitDirectorySync = (directory) ->
# can return cached values rather than always returning new objects:
# getParent(), getFile(), getSubdirectory().
gitDir = directory.getSubdirectory('.git')
if directoryExistsSync(gitDir) and isValidGitDirectorySync gitDir
if gitDir.existsSync() and isValidGitDirectorySync gitDir
gitDir
else if directory.isRoot()
return null
@@ -26,19 +26,9 @@ isValidGitDirectorySync = (directory) ->
# To decide whether a directory has a valid .git folder, we use
# the heuristic adopted by the valid_repository_path() function defined in
# node_modules/git-utils/deps/libgit2/src/repository.c.
return directoryExistsSync(directory.getSubdirectory('objects')) and
directory.getFile('HEAD').exists() and
directoryExistsSync(directory.getSubdirectory('refs'))
# Returns a boolean indicating whether the specified directory exists.
#
# * `directory` {Directory} to check for existence.
directoryExistsSync = (directory) ->
# TODO: Directory should have its own existsSync() method. Currently, File has
# an exists() method, which is synchronous, so it may be tricky to achieve
# consistency between the File and Directory APIs. Once Directory has its own
# method, this function should be replaced with direct calls to existsSync().
return fs.existsSync(directory.getPath())
return directory.getSubdirectory('objects').existsSync() and
directory.getFile('HEAD').existsSync() and
directory.getSubdirectory('refs').existsSync()
# Provider that conforms to the atom.repository-provider@0.1.0 service.
module.exports =
@@ -72,6 +62,7 @@ class GitRepositoryProvider
repo = @pathToRepository[gitDirPath]
unless repo
repo = GitRepository.open(gitDirPath, project: @project)
return null unless repo
repo.onDidDestroy(=> delete @pathToRepository[gitDirPath])
@pathToRepository[gitDirPath] = repo
repo.refreshIndex()

View File

@@ -59,7 +59,7 @@ class GitRepository
# Public: Creates a new GitRepository instance.
#
# * `path` The {String} path to the Git repository to open.
# * `options` An optinal {Object} with the following keys:
# * `options` An optional {Object} with the following keys:
# * `refreshOnWindowFocus` A {Boolean}, `true` to refresh the index and
# statuses when the window is focused.
#

View File

@@ -1,62 +1,47 @@
_ = require 'underscore-plus'
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{isEqual, isEqualForProperties, multiplyString, toArray} = _
Decoration = require './decoration'
SubscriberMixin = require './subscriber-mixin'
WrapperDiv = document.createElement('div')
module.exports =
GutterComponent = React.createClass
displayName: 'GutterComponent'
mixins: [SubscriberMixin]
maxLineNumberDigits: null
class GutterComponent
dummyLineNumberNode: null
measuredWidth: null
render: ->
{presenter} = @props
@newState = presenter.state.gutter
@oldState ?= {lineNumbers: {}}
{scrollHeight, backgroundColor} = @newState
div className: 'gutter',
div className: 'line-numbers', ref: 'lineNumbers', style:
height: scrollHeight
WebkitTransform: @getTransform()
backgroundColor: backgroundColor
getTransform: ->
{useHardwareAcceleration} = @props
{scrollTop} = @newState
if useHardwareAcceleration
"translate3d(0px, #{-scrollTop}px, 0px)"
else
"translate(0px, #{-scrollTop}px)"
componentWillMount: ->
constructor: ({@presenter, @onMouseDown, @editor}) ->
@lineNumberNodesById = {}
componentDidMount: ->
{@maxLineNumberDigits} = @newState
@appendDummyLineNumber()
@updateLineNumbers()
@domNode = document.createElement('div')
@domNode.classList.add('gutter')
@lineNumbersNode = document.createElement('div')
@lineNumbersNode.classList.add('line-numbers')
@domNode.appendChild(@lineNumbersNode)
node = @getDOMNode()
node.addEventListener 'click', @onClick
node.addEventListener 'mousedown', @onMouseDown
@domNode.addEventListener 'click', @onClick
@domNode.addEventListener 'mousedown', @onMouseDown
componentDidUpdate: (oldProps) ->
{maxLineNumberDigits} = @newState
unless maxLineNumberDigits is @maxLineNumberDigits
@maxLineNumberDigits = maxLineNumberDigits
@updateSync()
updateSync: ->
@newState = @presenter.state.gutter
@oldState ?= {lineNumbers: {}}
@appendDummyLineNumber() unless @dummyLineNumberNode?
if @newState.scrollHeight isnt @oldState.scrollHeight
@lineNumbersNode.style.height = @newState.scrollHeight + 'px'
@oldState.scrollHeight = @newState.scrollHeight
if @newState.scrollTop isnt @oldState.scrollTop
@lineNumbersNode.style['-webkit-transform'] = "translate3d(0px, #{-@newState.scrollTop}px, 0px)"
@oldState.scrollTop = @newState.scrollTop
if @newState.backgroundColor isnt @oldState.backgroundColor
@lineNumbersNode.style.backgroundColor = @newState.backgroundColor
@oldState.backgroundColor = @newState.backgroundColor
if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits
@updateDummyLineNumber()
node.remove() for id, node of @lineNumberNodesById
@oldState = {lineNumbers: {}}
@oldState = {maxLineNumberDigits: @newState.maxLineNumberDigits, lineNumbers: {}}
@lineNumberNodesById = {}
@updateLineNumbers()
@@ -66,7 +51,7 @@ GutterComponent = React.createClass
appendDummyLineNumber: ->
WrapperDiv.innerHTML = @buildLineNumberHTML({bufferRow: -1})
@dummyLineNumberNode = WrapperDiv.children[0]
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
@lineNumbersNode.appendChild(@dummyLineNumberNode)
updateDummyLineNumber: ->
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false)
@@ -87,9 +72,9 @@ GutterComponent = React.createClass
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = toArray(WrapperDiv.children)
newLineNumberNodes = _.toArray(WrapperDiv.children)
node = @refs.lineNumbers.getDOMNode()
node = @lineNumbersNode
for id, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
@@ -120,7 +105,7 @@ GutterComponent = React.createClass
else
lineNumber = (bufferRow + 1).toString()
padding = multiplyString('&nbsp;', maxLineNumberDigits - lineNumber.length)
padding = _.multiplyString('&nbsp;', maxLineNumberDigits - lineNumber.length)
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
@@ -151,21 +136,20 @@ GutterComponent = React.createClass
return @lineNumberNodesById[id]
null
onMouseDown: (event) ->
onMouseDown: (event) =>
{target} = event
lineNumber = target.parentNode
unless target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
@props.onMouseDown(event)
@onMouseDown(event)
onClick: (event) ->
{editor} = @props
onClick: (event) =>
{target} = event
lineNumber = target.parentNode
if target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
bufferRow = parseInt(lineNumber.getAttribute('data-buffer-row'))
if lineNumber.classList.contains('folded')
editor.unfoldBufferRow(bufferRow)
@editor.unfoldBufferRow(bufferRow)
else
editor.foldBufferRow(bufferRow)
@editor.foldBufferRow(bufferRow)

View File

@@ -1,50 +0,0 @@
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{isEqualForProperties} = require 'underscore-plus'
module.exports =
HighlightComponent = React.createClass
displayName: 'HighlightComponent'
currentFlashCount: 0
currentFlashClass: null
render: ->
{state} = @props
className = 'highlight'
className += " #{state.class}" if state.class?
div {className},
for region, i in state.regions
regionClassName = 'region'
regionClassName += " #{state.deprecatedRegionClass}" if state.deprecatedRegionClass?
div className: regionClassName, key: i, style: region
componentDidMount: ->
@flashIfRequested()
componentDidUpdate: ->
@flashIfRequested()
flashIfRequested: ->
if @props.state.flashCount > @currentFlashCount
@currentFlashCount = @props.state.flashCount
node = @getDOMNode()
{flashClass, flashDuration} = @props.state
addFlashClass = =>
node.classList.add(flashClass)
@currentFlashClass = flashClass
@flashTimeoutId = setTimeout(removeFlashClass, flashDuration)
removeFlashClass = =>
node.classList.remove(@currentFlashClass)
@currentFlashClass = null
clearTimeout(@flashTimeoutId)
if @currentFlashClass?
removeFlashClass()
requestAnimationFrame(addFlashClass)
else
addFlashClass()

View File

@@ -1,25 +1,107 @@
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{isEqualForProperties} = require 'underscore-plus'
HighlightComponent = require './highlight-component'
RegionStyleProperties = ['top', 'left', 'right', 'width', 'height']
module.exports =
HighlightsComponent = React.createClass
displayName: 'HighlightsComponent'
class HighlightsComponent
oldState: null
render: ->
div className: 'highlights',
@renderHighlights()
constructor: (@presenter) ->
@highlightNodesById = {}
@regionNodesByHighlightId = {}
renderHighlights: ->
{presenter} = @props
highlightComponents = []
for key, state of presenter.state.content.highlights
highlightComponents.push(HighlightComponent({key, state}))
highlightComponents
@domNode = document.createElement('div')
@domNode.classList.add('highlights')
componentDidMount: ->
if atom.config.get('editor.useShadowDOM')
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', '.underlayer')
@getDOMNode().appendChild(insertionPoint)
@domNode.appendChild(insertionPoint)
updateSync: ->
newState = @presenter.state.content.highlights
@oldState ?= {}
# remove highlights
for id of @oldState
unless newState[id]?
@highlightNodesById[id].remove()
delete @highlightNodesById[id]
delete @regionNodesByHighlightId[id]
delete @oldState[id]
# add or update highlights
for id, highlightState of newState
unless @oldState[id]?
highlightNode = document.createElement('div')
highlightNode.classList.add('highlight')
@highlightNodesById[id] = highlightNode
@regionNodesByHighlightId[id] = {}
@domNode.appendChild(highlightNode)
@updateHighlightNode(id, highlightState)
updateHighlightNode: (id, newHighlightState) ->
highlightNode = @highlightNodesById[id]
oldHighlightState = (@oldState[id] ?= {regions: [], flashCount: 0})
# update class
if newHighlightState.class isnt oldHighlightState.class
highlightNode.classList.remove(oldHighlightState.class) if oldHighlightState.class?
highlightNode.classList.add(newHighlightState.class)
oldHighlightState.class = newHighlightState.class
@updateHighlightRegions(id, newHighlightState)
@flashHighlightNodeIfRequested(id, newHighlightState)
updateHighlightRegions: (id, newHighlightState) ->
oldHighlightState = @oldState[id]
highlightNode = @highlightNodesById[id]
# remove regions
while oldHighlightState.regions.length > newHighlightState.regions.length
oldHighlightState.regions.pop()
@regionNodesByHighlightId[id][oldHighlightState.regions.length].remove()
delete @regionNodesByHighlightId[id][oldHighlightState.regions.length]
# add or update regions
for newRegionState, i in newHighlightState.regions
unless oldHighlightState.regions[i]?
oldHighlightState.regions[i] = {}
regionNode = document.createElement('div')
regionNode.classList.add('region')
regionNode.classList.add(newHighlightState.deprecatedRegionClass) if newHighlightState.deprecatedRegionClass?
@regionNodesByHighlightId[id][i] = regionNode
highlightNode.appendChild(regionNode)
oldRegionState = oldHighlightState.regions[i]
regionNode = @regionNodesByHighlightId[id][i]
for property in RegionStyleProperties
if newRegionState[property] isnt oldRegionState[property]
oldRegionState[property] = newRegionState[property]
if newRegionState[property]?
regionNode.style[property] = newRegionState[property] + 'px'
else
regionNode.style[property] = ''
flashHighlightNodeIfRequested: (id, newHighlightState) ->
oldHighlightState = @oldState[id]
return unless newHighlightState.flashCount > oldHighlightState.flashCount
highlightNode = @highlightNodesById[id]
addFlashClass = =>
highlightNode.classList.add(newHighlightState.flashClass)
oldHighlightState.flashClass = newHighlightState.flashClass
@flashTimeoutId = setTimeout(removeFlashClass, newHighlightState.flashDuration)
removeFlashClass = =>
highlightNode.classList.remove(oldHighlightState.flashClass)
oldHighlightState.flashClass = null
clearTimeout(@flashTimeoutId)
if oldHighlightState.flashClass?
removeFlashClass()
requestAnimationFrame(addFlashClass)
else
addFlashClass()
oldHighlightState.flashCount = newHighlightState.flashCount

View File

@@ -1,39 +1,29 @@
{last, isEqual} = require 'underscore-plus'
React = require 'react-atom-fork'
{input} = require 'reactionary-atom-fork'
module.exports =
InputComponent = React.createClass
displayName: 'InputComponent'
class InputComponent
constructor: (@presenter) ->
@domNode = document.createElement('input')
@domNode.classList.add('hidden-input')
@domNode.setAttribute('data-react-skip-selection-restoration', true)
@domNode.style['-webkit-transform'] = 'translateZ(0)'
@domNode.addEventListener 'paste', (event) => event.preventDefault()
@updateSync()
render: ->
{className, style} = @props
updateSync: ->
@oldState ?= {}
newState = @presenter.state.hiddenInput
input {className, style, 'data-react-skip-selection-restoration': true}
if newState.top isnt @oldState.top
@domNode.style.top = newState.top + 'px'
@oldState.top = newState.top
getInitialState: ->
{lastChar: ''}
if newState.left isnt @oldState.left
@domNode.style.left = newState.left + 'px'
@oldState.left = newState.left
componentDidMount: ->
node = @getDOMNode()
node.addEventListener 'paste', @onPaste
node.addEventListener 'compositionupdate', @onCompositionUpdate
if newState.width isnt @oldState.width
@domNode.style.width = newState.width + 'px'
@oldState.width = newState.width
# Don't let text accumulate in the input forever, but avoid excessive reflows
componentDidUpdate: ->
if @lastValueLength > 500 and not @isPressAndHoldCharacter(@state.lastChar)
@getDOMNode().value = ''
@lastValueLength = 0
# This should actually consult the property lists in /System/Library/Input Methods/PressAndHold.app
isPressAndHoldCharacter: (char) ->
@state.lastChar.match /[aeiouAEIOU]/
shouldComponentUpdate: (newProps) ->
not isEqual(newProps.style, @props.style)
onPaste: (e) ->
e.preventDefault()
focus: ->
@getDOMNode().focus()
if newState.height isnt @oldState.height
@domNode.style.height = newState.height + 'px'
@oldState.height = newState.height

View File

@@ -1,7 +1,5 @@
_ = require 'underscore-plus'
React = require 'react-atom-fork'
{div, span} = require 'reactionary-atom-fork'
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
{toArray} = require 'underscore-plus'
{$$} = require 'space-pen'
CursorsComponent = require './cursors-component'
@@ -12,73 +10,85 @@ DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibi
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
WrapperDiv = document.createElement('div')
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
clone
module.exports =
LinesComponent = React.createClass
displayName: 'LinesComponent'
class LinesComponent
placeholderTextDiv: null
render: ->
{editor, presenter} = @props
@oldState ?= {lines: {}}
@newState = presenter.state.content
{scrollHeight, scrollWidth, backgroundColor, placeholderText} = @newState
style =
height: scrollHeight
width: scrollWidth
WebkitTransform: @getTransform()
backgroundColor: backgroundColor
div {className: 'lines', style},
div className: 'placeholder-text', placeholderText if placeholderText?
CursorsComponent {presenter}
HighlightsComponent {presenter}
getTransform: ->
{scrollTop, scrollLeft} = @newState
{useHardwareAcceleration} = @props
if useHardwareAcceleration
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
else
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
componentWillMount: ->
constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@renderedDecorationsByLineId = {}
componentDidMount: ->
if @props.useShadowDOM
@domNode = document.createElement('div')
@domNode.classList.add('lines')
@cursorsComponent = new CursorsComponent(@presenter)
@domNode.appendChild(@cursorsComponent.domNode)
@highlightsComponent = new HighlightsComponent(@presenter)
@domNode.appendChild(@highlightsComponent.domNode)
if @useShadowDOM
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', '.overlayer')
@getDOMNode().appendChild(insertionPoint)
@domNode.appendChild(insertionPoint)
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@overlayManager = new OverlayManager(@props.hostElement)
@getDOMNode().appendChild(insertionPoint)
@overlayManager = new OverlayManager(@hostElement)
@domNode.appendChild(insertionPoint)
else
@overlayManager = new OverlayManager(@getDOMNode())
@overlayManager = new OverlayManager(@domNode)
componentDidUpdate: ->
{visible, presenter} = @props
@updateSync(visible)
updateSync: ->
@newState = @presenter.state.content
@oldState ?= {lines: {}}
if @newState.scrollHeight isnt @oldState.scrollHeight
@domNode.style.height = @newState.scrollHeight + 'px'
@oldState.scrollHeight = @newState.scrollHeight
if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft
@domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, #{-@newState.scrollTop}px, 0px)"
@oldState.scrollTop = @newState.scrollTop
@oldState.scrollLeft = @newState.scrollLeft
if @newState.backgroundColor isnt @oldState.backgroundColor
@domNode.style.backgroundColor = @newState.backgroundColor
@oldState.backgroundColor = @newState.backgroundColor
if @newState.placeholderText isnt @oldState.placeholderText
@placeholderTextDiv?.remove()
if @newState.placeholderText?
@placeholderTextDiv = document.createElement('div')
@placeholderTextDiv.classList.add('placeholder-text')
@placeholderTextDiv.textContent = @newState.placeholderText
@domNode.appendChild(@placeholderTextDiv)
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
@updateLineNodes()
@measureCharactersInNewLines() if visible and not @newState.scrollingVertically
@overlayManager?.render(@props)
if @newState.scrollWidth isnt @oldState.scrollWidth
@domNode.style.width = @newState.scrollWidth + 'px'
@oldState.scrollWidth = @newState.scrollWidth
@cursorsComponent.updateSync()
@highlightsComponent.updateSync()
@overlayManager?.render(@presenter)
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
clearScreenRowCaches: ->
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
removeLineNodes: ->
@removeLineNode(id) for id of @oldState.lines
@@ -90,8 +100,6 @@ LinesComponent = React.createClass
delete @oldState.lines[id]
updateLineNodes: ->
{presenter} = @props
for id of @oldState.lines
unless @newState.lines.hasOwnProperty(id)
@removeLineNode(id)
@@ -109,20 +117,18 @@ LinesComponent = React.createClass
newLinesHTML += @buildLineHTML(id)
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldState.lines[id] = _.clone(lineState)
@oldState.lines[id] = cloneObject(lineState)
return unless newLineIds?
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = toArray(WrapperDiv.children)
node = @getDOMNode()
newLineNodes = _.toArray(WrapperDiv.children)
for id, i in newLineIds
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
node.appendChild(lineNode)
@domNode.appendChild(lineNode)
buildLineHTML: (id) ->
{presenter} = @props
{scrollWidth} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
@@ -167,7 +173,6 @@ LinesComponent = React.createClass
@buildEndOfLineHTML(id) or '&nbsp;'
buildLineInnerHTML: (id) ->
{editor} = @props
{indentGuidesVisible} = @newState
{tokens, text, isOnlyWhitespace} = @newState.lines[id]
innerHTML = ""
@@ -217,13 +222,16 @@ LinesComponent = React.createClass
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
updateLineNode: (id) ->
{scrollWidth} = @newState
{screenRow, top} = @newState.lines[id]
oldLineState = @oldState.lines[id]
newLineState = @newState.lines[id]
lineNode = @lineNodesByLineId[id]
newDecorationClasses = @newState.lines[id].decorationClasses
oldDecorationClasses = @oldState.lines[id].decorationClasses
if @newState.scrollWidth isnt @oldState.scrollWidth
lineNode.style.width = @newState.scrollWidth + 'px'
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses
if oldDecorationClasses?
for decorationClass in oldDecorationClasses
@@ -235,36 +243,37 @@ LinesComponent = React.createClass
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
lineNode.classList.add(decorationClass)
lineNode.style.width = scrollWidth + 'px'
lineNode.style.top = top + 'px'
lineNode.dataset.screenRow = screenRow
@screenRowsByLineId[id] = screenRow
@lineIdsByScreenRow[screenRow] = id
oldLineState.decorationClasses = newLineState.decorationClasses
if newLineState.top isnt oldLineState.top
lineNode.style.top = newLineState.top + 'px'
oldLineState.top = newLineState.cop
if newLineState.screenRow isnt oldLineState.screenRow
lineNode.dataset.screenRow = newLineState.screenRow
oldLineState.screenRow = newLineState.screenRow
@lineIdsByScreenRow[newLineState.screenRow] = id
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
measureLineHeightAndDefaultCharWidth: ->
node = @getDOMNode()
node.appendChild(DummyLineNode)
@domNode.appendChild(DummyLineNode)
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
node.removeChild(DummyLineNode)
@domNode.removeChild(DummyLineNode)
{editor, presenter} = @props
presenter.setLineHeight(lineHeightInPixels)
presenter.setBaseCharacterWidth(charWidth)
@presenter.setLineHeight(lineHeightInPixels)
@presenter.setBaseCharacterWidth(charWidth)
remeasureCharacterWidths: ->
return unless @props.presenter.baseCharacterWidth
return unless @presenter.baseCharacterWidth
@clearScopedCharWidths()
@measureCharactersInNewLines()
measureCharactersInNewLines: ->
{presenter} = @props
presenter.batchCharacterMeasurement =>
@presenter.batchCharacterMeasurement =>
for id, lineState of @oldState.lines
unless @measuredLines.has(id)
lineNode = @lineNodesByLineId[id]
@@ -272,13 +281,12 @@ LinesComponent = React.createClass
return
measureCharactersInLine: (tokenizedLine, lineNode) ->
{editor} = @props
rangeForMeasurement = null
iterator = null
charIndex = 0
for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens
charWidths = editor.getScopedCharWidths(scopes)
charWidths = @presenter.getScopedCharacterWidths(scopes)
valueIndex = 0
while valueIndex < value.length
@@ -310,7 +318,7 @@ LinesComponent = React.createClass
rangeForMeasurement.setStart(textNode, i)
rangeForMeasurement.setEnd(textNode, i + charLength)
charWidth = rangeForMeasurement.getBoundingClientRect().width
@props.presenter.setScopedCharacterWidth(scopes, char, charWidth)
@presenter.setScopedCharacterWidth(scopes, char, charWidth)
charIndex += charLength
@@ -318,5 +326,4 @@ LinesComponent = React.createClass
clearScopedCharWidths: ->
@measuredLines.clear()
@props.editor.clearScopedCharWidths()
@props.presenter.clearScopedCharacterWidths()
@presenter.clearScopedCharacterWidths()

View File

@@ -3,16 +3,14 @@ class OverlayManager
constructor: (@container) ->
@overlayNodesById = {}
render: (props) ->
{presenter} = props
render: (presenter) ->
for decorationId, {pixelPosition, item} of presenter.state.content.overlays
@renderOverlay(presenter, decorationId, item, pixelPosition)
for id, overlayNode of @overlayNodesById
unless presenter.state.content.overlays.hasOwnProperty(id)
overlayNode.remove()
delete @overlayNodesById[id]
overlayNode.remove()
return

View File

@@ -8,9 +8,9 @@ Q = require 'q'
{Model} = require 'theorist'
{Subscriber} = require 'emissary'
{Emitter} = require 'event-kit'
DefaultDirectoryProvider = require './default-directory-provider'
Serializable = require 'serializable'
TextBuffer = require 'text-buffer'
{Directory} = require 'pathwatcher'
Grim = require 'grim'
TextEditor = require './text-editor'
@@ -41,6 +41,14 @@ class Project extends Model
@rootDirectories = []
@repositories = []
@directoryProviders = [new DefaultDirectoryProvider()]
atom.packages.serviceHub.consume(
'atom.directory-provider',
'^0.1.0',
# New providers are added to the front of @directoryProviders because
# DefaultDirectoryProvider is a catch-all that will always provide a Directory.
(provider) => @directoryProviders.unshift(provider))
# Mapping from the real path of a {Directory} to a {Promise} that resolves
# to either a {Repository} or null. Ideally, the {Directory} would be used
# as the key; however, there can be multiple {Directory} objects created for
@@ -53,7 +61,15 @@ class Project extends Model
atom.packages.serviceHub.consume(
'atom.repository-provider',
'^0.1.0',
(provider) => @repositoryProviders.push(provider))
(provider) =>
@repositoryProviders.push(provider)
# If a path in getPaths() does not have a corresponding Repository, try
# to assign one by running through setPaths() again now that
# @repositoryProviders has been updated.
if null in @repositories
@setPaths(@getPaths())
)
@subscribeToBuffer(buffer) for buffer in @buffers
@@ -151,7 +167,7 @@ class Project extends Model
# Public: Get an {Array} of {String}s containing the paths of the project's
# directories.
getPaths: -> rootDirectory.path for rootDirectory in @rootDirectories
getPaths: -> rootDirectory.getPath() for rootDirectory in @rootDirectories
getPath: ->
Grim.deprecate("Use ::getPaths instead")
@getPaths()[0]
@@ -174,22 +190,22 @@ class Project extends Model
Grim.deprecate("Use ::setPaths instead")
@setPaths([path])
# Public: Add a path the project's list of root paths
# Public: Add a path to the project's list of root paths
#
# * `projectPath` {String} The path to the directory to add.
addPath: (projectPath, options) ->
projectPath = path.normalize(projectPath)
for directory in @getDirectories()
# Apparently a Directory does not believe it can contain itself, so we
# must also check whether the paths match.
return if directory.contains(projectPath) or directory.getPath() is projectPath
directoryPath = if fs.isDirectorySync(projectPath)
projectPath
else
path.dirname(projectPath)
return if @getPaths().some (existingPath) ->
(directoryPath is existingPath) or
(directoryPath.indexOf(path.join(existingPath, path.sep)) is 0)
directory = new Directory(directoryPath)
directory = null
for provider in @directoryProviders
break if directory = provider.directoryForURISync?(projectPath)
if directory is null
# This should never happen because DefaultDirectoryProvider should always
# return a Directory.
throw new Error(projectPath + ' could not be resolved to a directory')
@rootDirectories.push(directory)
repo = null
@@ -201,6 +217,29 @@ class Project extends Model
@emit "path-changed"
@emitter.emit 'did-change-paths', @getPaths()
# Public: remove a path from the project's list of root paths.
#
# * `projectPath` {String} The path to remove.
removePath: (projectPath) ->
projectPath = path.normalize(projectPath)
indexToRemove = null
for directory, i in @rootDirectories
if directory.getPath() is projectPath
indexToRemove = i
break
if indexToRemove?
[removedDirectory] = @rootDirectories.splice(indexToRemove, 1)
[removedRepository] = @repositories.splice(indexToRemove, 1)
removedDirectory.off()
removedRepository?.destroy() unless removedRepository in @repositories
@emit "path-changed"
@emitter.emit "did-change-paths", @getPaths()
true
else
false
# Public: Get an {Array} of {Directory}s associated with this project.
getDirectories: ->
@rootDirectories
@@ -233,8 +272,8 @@ class Project extends Model
relativize: (fullPath) ->
return fullPath if fullPath?.match(/[A-Za-z0-9+-.]+:\/\//) # leave path alone if it has a scheme
for rootDirectory in @rootDirectories
if (relativePath = rootDirectory.relativize(fullPath))?
return relativePath
relativePath = rootDirectory.relativize(fullPath)
return relativePath if relativePath isnt fullPath
fullPath
# Public: Determines whether the given path (real or symbolic) is inside the

View File

@@ -1,69 +1,74 @@
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{extend, isEqualForProperties} = require 'underscore-plus'
module.exports =
ScrollbarComponent = React.createClass
displayName: 'ScrollbarComponent'
class ScrollbarComponent
constructor: ({@presenter, @orientation, @onScroll}) ->
@domNode = document.createElement('div')
@domNode.classList.add "#{@orientation}-scrollbar"
@domNode.style['-webkit-transform'] = 'translateZ(0)' # See atom/atom#3559
@domNode.style.left = 0 if @orientation is 'horizontal'
render: ->
{presenter, orientation, className, useHardwareAcceleration} = @props
@contentNode = document.createElement('div')
@contentNode.classList.add "scrollbar-content"
@domNode.appendChild(@contentNode)
switch orientation
@domNode.addEventListener 'scroll', @onScrollCallback
@updateSync()
updateSync: ->
@oldState ?= {}
switch @orientation
when 'vertical'
@newState = presenter.state.verticalScrollbar
@newState = @presenter.state.verticalScrollbar
@updateVertical()
when 'horizontal'
@newState = presenter.state.horizontalScrollbar
@newState = @presenter.state.horizontalScrollbar
@updateHorizontal()
style = {}
if @newState.visible isnt @oldState.visible
if @newState.visible
@domNode.style.display = ''
else
@domNode.style.display = 'none'
@oldState.visible = @newState.visible
style.display = 'none' unless @newState.visible
style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559
switch orientation
updateVertical: ->
if @newState.width isnt @oldState.width
@domNode.style.width = @newState.width + 'px'
@oldState.width = @newState.width
if @newState.bottom isnt @oldState.bottom
@domNode.style.bottom = @newState.bottom + 'px'
@oldState.bottom = @newState.bottom
if @newState.scrollHeight isnt @oldState.scrollHeight
@contentNode.style.height = @newState.scrollHeight + 'px'
@oldState.scrollHeight = @newState.scrollHeight
if @newState.scrollTop isnt @oldState.scrollTop
@domNode.scrollTop = @newState.scrollTop
@oldState.scrollTop = @newState.scrollTop
updateHorizontal: ->
if @newState.height isnt @oldState.height
@domNode.style.height = @newState.height + 'px'
@oldState.height = @newState.height
if @newState.right isnt @oldState.right
@domNode.style.right = @newState.right + 'px'
@oldState.right = @newState.right
if @newState.scrollWidth isnt @oldState.scrollWidth
@contentNode.style.width = @newState.scrollWidth + 'px'
@oldState.scrollWidth = @newState.scrollWidth
if @newState.scrollLeft isnt @oldState.scrollLeft
@domNode.scrollLeft = @newState.scrollLeft
@oldState.scrollLeft = @newState.scrollLeft
onScrollCallback: =>
switch @orientation
when 'vertical'
style.width = @newState.width
style.bottom = @newState.bottom
@onScroll(@domNode.scrollTop)
when 'horizontal'
style.left = 0
style.right = @newState.right
style.height = @newState.height
div {className, style},
switch orientation
when 'vertical'
div className: 'scrollbar-content', style: {height: @newState.scrollHeight}
when 'horizontal'
div className: 'scrollbar-content', style: {width: @newState.scrollWidth}
componentDidMount: ->
{orientation} = @props
unless orientation is 'vertical' or orientation is 'horizontal'
throw new Error("Must specify an orientation property of 'vertical' or 'horizontal'")
@getDOMNode().addEventListener 'scroll', @onScroll
componentWillUnmount: ->
@getDOMNode().removeEventListener 'scroll', @onScroll
componentDidUpdate: ->
{orientation} = @props
node = @getDOMNode()
switch orientation
when 'vertical'
node.scrollTop = @newState.scrollTop
when 'horizontal'
node.scrollLeft = @newState.scrollLeft
onScroll: ->
{orientation, onScroll} = @props
node = @getDOMNode()
switch orientation
when 'vertical'
scrollTop = node.scrollTop
onScroll(scrollTop)
when 'horizontal'
scrollLeft = node.scrollLeft
onScroll(scrollLeft)
@onScroll(@domNode.scrollLeft)

View File

@@ -1,25 +1,37 @@
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{isEqualForProperties} = require 'underscore-plus'
module.exports =
ScrollbarCornerComponent = React.createClass
displayName: 'ScrollbarCornerComponent'
class ScrollbarCornerComponent
constructor: (@presenter) ->
@domNode = document.createElement('div')
@domNode.classList.add('scrollbar-corner')
render: ->
{presenter, measuringScrollbars} = @props
@contentNode = document.createElement('div')
@domNode.appendChild(@contentNode)
visible = presenter.state.horizontalScrollbar.visible and presenter.state.verticalScrollbar.visible
width = presenter.state.verticalScrollbar.width
height = presenter.state.horizontalScrollbar.height
@updateSync()
if measuringScrollbars
height = 25
width = 25
updateSync: ->
@oldState ?= {}
@newState ?= {}
display = 'none' unless visible
newHorizontalState = @presenter.state.horizontalScrollbar
newVerticalState = @presenter.state.verticalScrollbar
@newState.visible = newHorizontalState.visible and newVerticalState.visible
@newState.height = newHorizontalState.height
@newState.width = newVerticalState.width
div className: 'scrollbar-corner', style: {display, width, height},
div style:
height: height + 1
width: width + 1
if @newState.visible isnt @oldState.visible
if @newState.visible
@domNode.style.display = ''
else
@domNode.style.display = 'none'
@oldState.visible = @newState.visible
if @newState.height isnt @oldState.height
@domNode.style.height = @newState.height + 'px'
@contentNode.style.height = @newState.height + 1 + 'px'
@oldState.height = @newState.height
if @newState.width isnt @oldState.width
@domNode.style.width = @newState.width + 'px'
@contentNode.style.width = @newState.width + 1 + 'px'
@oldState.width = @newState.width

View File

@@ -41,6 +41,14 @@ handleEvents = ->
result = handler.bind({async})(args...)
emit('task:completed', result) unless isAsync
setupDeprecations = ->
Grim = require 'grim'
Grim.on 'updated', ->
deprecations = Grim.getDeprecations().map (deprecation) -> deprecation.serialize()
Grim.clearDeprecations()
emit('task:deprecations', deprecations)
setupGlobals()
handleEvents()
setupDeprecations()
handler = require(taskPath)

View File

@@ -1,6 +1,7 @@
_ = require 'underscore-plus'
{fork} = require 'child_process'
{Emitter} = require 'emissary'
Grim = require 'grim'
# Extended: Run a node script in a separate process.
#
@@ -87,6 +88,9 @@ class Task
@on "task:log", -> console.log(arguments...)
@on "task:warn", -> console.warn(arguments...)
@on "task:error", -> console.error(arguments...)
@on "task:deprecations", (deprecations) ->
Grim.addSerializedDeprecation(deprecation) for deprecation in deprecations
return
@on "task:completed", (args...) => @callback?(args...)
@handleEvents()

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
{Emitter} = require 'event-kit'
{View, $, callRemoveHooks} = require 'space-pen'
React = require 'react-atom-fork'
Path = require 'path'
{defaults} = require 'underscore-plus'
TextBuffer = require 'text-buffer'
@@ -61,7 +60,7 @@ class TextEditorElement extends HTMLElement
attachedCallback: ->
@buildModel() unless @getModel()?
@mountComponent() unless @component?.isMounted()
@mountComponent() unless @component?
@component.checkForVisibilityChange()
if this is document.activeElement
@focused()
@@ -105,7 +104,7 @@ class TextEditorElement extends HTMLElement
))
mountComponent: ->
@componentDescriptor ?= TextEditorComponent(
@component = new TextEditorComponent(
hostElement: this
rootElement: @rootElement
stylesElement: @stylesElement
@@ -113,27 +112,28 @@ class TextEditorElement extends HTMLElement
lineOverdrawMargin: @lineOverdrawMargin
useShadowDOM: @useShadowDOM
)
@component = React.renderComponent(@componentDescriptor, @rootElement)
@rootElement.appendChild(@component.domNode)
if @useShadowDOM
@shadowRoot.addEventListener('blur', @shadowRootBlurred.bind(this), true)
else
inputNode = @component.refs.input.getDOMNode()
inputNode = @component.hiddenInputComponent.domNode
inputNode.addEventListener 'focus', @focused.bind(this)
inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false))
unmountComponent: ->
return unless @component?.isMounted()
callRemoveHooks(this)
React.unmountComponentAtNode(@rootElement)
@component = null
if @component?
@component.destroy()
@component.domNode.remove()
@component = null
focused: ->
@component?.focused()
blurred: (event) ->
unless @useShadowDOM
if event.relatedTarget is @component?.refs.input?.getDOMNode()
if event.relatedTarget is @component.hiddenInputComponent.domNode
event.stopImmediatePropagation()
return

View File

@@ -14,7 +14,7 @@ class TextEditorPresenter
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@@ -56,13 +56,18 @@ class TextEditorPresenter
@updateLinesState()
@updateGutterState()
@updateLineNumbersState()
@disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this))
@disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this))
@disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this))
@disposables.add @model.onDidChangeMini =>
@updateScrollbarDimensions()
@updateScrollbarsState()
@updateContentState()
@updateDecorations()
@updateLinesState()
@updateGutterState()
@updateLineNumbersState()
@disposables.add @model.onDidChangeGutterVisible =>
@updateGutterState()
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
@disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this))
@@ -71,19 +76,41 @@ class TextEditorPresenter
@observeCursor(cursor) for cursor in @model.getCursors()
observeConfig: ->
@scrollPastEnd = atom.config.get('editor.scrollPastEnd')
configParams = {scope: @model.getRootScopeDescriptor()}
@disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this)
@disposables.add atom.config.onDidChange 'editor.scrollPastEnd', scope: @model.getRootScopeDescriptor(), ({newValue}) =>
@scrollPastEnd = atom.config.get('editor.scrollPastEnd', configParams)
@showLineNumbers = atom.config.get('editor.showLineNumbers', configParams)
@showIndentGuide = atom.config.get('editor.showIndentGuide', configParams)
if @configDisposables?
@configDisposables?.dispose()
@disposables.remove(@configDisposables)
@configDisposables = new CompositeDisposable
@disposables.add(@configDisposables)
@configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) =>
@showIndentGuide = newValue
@updateContentState()
@configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) =>
@scrollPastEnd = newValue
@updateScrollHeight()
@updateVerticalScrollState()
@updateScrollbarsState()
@configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) =>
@showLineNumbers = newValue
@updateGutterState()
didChangeGrammar: ->
@observeConfig()
@updateContentState()
@updateGutterState()
buildState: ->
@state =
horizontalScrollbar: {}
verticalScrollbar: {}
hiddenInput: {}
content:
scrollingVertically: false
blinkCursorsOff: false
@@ -100,10 +127,12 @@ class TextEditorPresenter
@updateStartRow()
@updateEndRow()
@updateFocusedState()
@updateHeightState()
@updateVerticalScrollState()
@updateHorizontalScrollState()
@updateScrollbarsState()
@updateHiddenInputState()
@updateContentState()
@updateDecorations()
@updateLinesState()
@@ -112,6 +141,9 @@ class TextEditorPresenter
@updateGutterState()
@updateLineNumbersState()
updateFocusedState: ->
@state.focused = @focused
updateHeightState: ->
if @autoHeight
@state.height = @contentHeight
@@ -151,10 +183,29 @@ class TextEditorPresenter
@emitter.emit 'did-update-state'
updateHiddenInputState: ->
return unless lastCursor = @model.getLastCursor()
{top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange())
if @focused
top -= @scrollTop
left -= @scrollLeft
@state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0)
@state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0)
else
@state.hiddenInput.top = 0
@state.hiddenInput.left = 0
@state.hiddenInput.height = height
@state.hiddenInput.width = Math.max(width, 2)
@emitter.emit 'did-update-state'
updateContentState: ->
@state.content.scrollWidth = @scrollWidth
@state.content.scrollLeft = @scrollLeft
@state.content.indentGuidesVisible = not @model.isMini() and atom.config.get('editor.showIndentGuide', scope: @model.getRootScopeDescriptor())
@state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide
@state.content.backgroundColor = if @model.isMini() then null else @backgroundColor
@state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null
@emitter.emit 'did-update-state'
@@ -242,6 +293,7 @@ class TextEditorPresenter
@emitter.emit "did-update-state"
updateGutterState: ->
@state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers
@state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length
@state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)"
@gutterBackgroundColor
@@ -330,14 +382,14 @@ class TextEditorPresenter
@updateScrollTop()
updateContentDimensions: ->
return unless @lineHeight? and @baseCharacterWidth?
if @lineHeight?
oldContentHeight = @contentHeight
@contentHeight = @lineHeight * @model.getScreenLineCount()
oldContentHeight = @contentHeight
@contentHeight = @lineHeight * @model.getScreenLineCount()
oldContentWidth = @contentWidth
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
if @baseCharacterWidth?
oldContentWidth = @contentWidth
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
if @contentHeight isnt oldContentHeight
@updateHeight()
@@ -395,12 +447,14 @@ class TextEditorPresenter
clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @measuredHorizontalScrollbarHeight
horizontalScrollbarVisible =
@contentWidth > clientWidthWithoutVerticalScrollbar or
@contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar
not @model.isMini() and
(@contentWidth > clientWidthWithoutVerticalScrollbar or
@contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar)
verticalScrollbarVisible =
@contentHeight > clientHeightWithoutHorizontalScrollbar or
@contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar
not @model.isMini() and
(@contentHeight > clientHeightWithoutHorizontalScrollbar or
@contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar)
horizontalScrollbarHeight =
if horizontalScrollbarVisible
@@ -444,16 +498,23 @@ class TextEditorPresenter
getCursorBlinkResumeDelay: -> @cursorBlinkResumeDelay
setFocused: (focused) ->
unless @focused is focused
@focused = focused
@updateFocusedState()
@updateHiddenInputState()
setScrollTop: (scrollTop) ->
scrollTop = @constrainScrollTop(scrollTop)
unless @scrollTop is scrollTop
unless @scrollTop is scrollTop or Number.isNaN(scrollTop)
@scrollTop = scrollTop
@model.setScrollTop(scrollTop)
@updateStartRow()
@updateEndRow()
@didStartScrolling()
@updateVerticalScrollState()
@updateHiddenInputState()
@updateDecorations()
@updateLinesState()
@updateCursorsState()
@@ -478,11 +539,12 @@ class TextEditorPresenter
setScrollLeft: (scrollLeft) ->
scrollLeft = @constrainScrollLeft(scrollLeft)
unless @scrollLeft is scrollLeft
unless @scrollLeft is scrollLeft or Number.isNaN(scrollLeft)
oldScrollLeft = @scrollLeft
@scrollLeft = scrollLeft
@model.setScrollLeft(scrollLeft)
@updateHorizontalScrollState()
@updateHiddenInputState()
@updateCursorsState() unless oldScrollLeft?
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
@@ -493,6 +555,7 @@ class TextEditorPresenter
@updateScrollbarDimensions()
@updateScrollbarsState()
@updateVerticalScrollState()
@updateHorizontalScrollState()
@updateCursorsState() unless oldHorizontalScrollbarHeight?
setVerticalScrollbarWidth: (verticalScrollbarWidth) ->
@@ -502,6 +565,7 @@ class TextEditorPresenter
@model.setVerticalScrollbarWidth(verticalScrollbarWidth)
@updateScrollbarDimensions()
@updateScrollbarsState()
@updateVerticalScrollState()
@updateHorizontalScrollState()
@updateCursorsState() unless oldVerticalScrollbarWidth?
@@ -567,7 +631,10 @@ class TextEditorPresenter
@updateStartRow()
@updateEndRow()
@updateHeightState()
@updateHorizontalScrollState()
@updateVerticalScrollState()
@updateScrollbarsState()
@updateHiddenInputState()
@updateDecorations()
@updateLinesState()
@updateCursorsState()
@@ -613,6 +680,9 @@ class TextEditorPresenter
@updateContentDimensions()
@updateHorizontalScrollState()
@updateVerticalScrollState()
@updateScrollbarsState()
@updateHiddenInputState()
@updateContentState()
@updateDecorations()
@updateLinesState()
@@ -621,6 +691,7 @@ class TextEditorPresenter
clearScopedCharacterWidths: ->
@characterWidthsByScope = {}
@model.clearScopedCharWidths()
hasPixelPositionRequirements: ->
@lineHeight? and @baseCharacterWidth?
@@ -875,6 +946,7 @@ class TextEditorPresenter
observeCursor: (cursor) ->
didChangePositionDisposable = cursor.onDidChangePosition =>
@updateHiddenInputState() if cursor.isLastCursor()
@pauseCursorBlinking()
@updateCursorsState()
@@ -884,6 +956,7 @@ class TextEditorPresenter
@disposables.remove(didChangePositionDisposable)
@disposables.remove(didChangeVisibilityDisposable)
@disposables.remove(didDestroyDisposable)
@updateHiddenInputState()
@updateCursorsState()
@disposables.add(didChangePositionDisposable)
@@ -892,6 +965,7 @@ class TextEditorPresenter
didAddCursor: (cursor) ->
@observeCursor(cursor)
@updateHiddenInputState()
@pauseCursorBlinking()
@updateCursorsState()

View File

@@ -126,7 +126,7 @@ class TextEditorView extends View
Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0]
Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1]
Object.defineProperty @::, 'active', get: -> @is(@getPaneView()?.activeView)
Object.defineProperty @::, 'isFocused', get: -> document.activeElement is @element or document.activeElement is @element.component?.refs.input.getDOMNode()
Object.defineProperty @::, 'isFocused', get: -> document.activeElement is @element or document.activeElement is @element.component?.hiddenInputComponent?.domNode
Object.defineProperty @::, 'mini', get: -> @model?.isMini()
Object.defineProperty @::, 'component', get: -> @element?.component

View File

@@ -42,9 +42,17 @@ Grim = require 'grim'
# ```
module.exports =
class ViewRegistry
documentPollingInterval: 200
documentUpdateRequested: false
performDocumentPollAfterUpdate: false
pollIntervalHandle: null
constructor: ->
@views = new WeakMap
@providers = []
@documentWriters = []
@documentReaders = []
@documentPollers = []
# Essential: Add a provider that will be used to construct views in the
# workspace's view layer based on model objects in its model layer.
@@ -150,3 +158,53 @@ class ViewRegistry
findProvider: (object) ->
find @providers, ({modelConstructor}) -> object instanceof modelConstructor
updateDocument: (fn) ->
@documentWriters.push(fn)
@requestDocumentUpdate()
new Disposable =>
@documentWriters = @documentWriters.filter (writer) -> writer isnt fn
readDocument: (fn) ->
@documentReaders.push(fn)
@requestDocumentUpdate()
new Disposable =>
@documentReaders = @documentReaders.filter (reader) -> reader isnt fn
pollDocument: (fn) ->
@startPollingDocument() if @documentPollers.length is 0
@documentPollers.push(fn)
new Disposable =>
@documentPollers = @documentPollers.filter (poller) -> poller isnt fn
@stopPollingDocument() if @documentPollers.length is 0
clearDocumentRequests: ->
@documentReaders = []
@documentWriters = []
@documentPollers = []
@documentUpdateRequested = false
requestDocumentUpdate: ->
unless @documentUpdateRequested
@documentUpdateRequested = true
requestAnimationFrame(@performDocumentUpdate)
performDocumentUpdate: =>
@documentUpdateRequested = false
writer() while writer = @documentWriters.shift()
reader() while reader = @documentReaders.shift()
@performDocumentPoll() if @performDocumentPollAfterUpdate
startPollingDocument: ->
@pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval)
stopPollingDocument: ->
window.clearInterval(@pollIntervalHandle)
performDocumentPoll: =>
if @documentUpdateRequested
@performDocumentPollAfterUpdate = true
else
@performDocumentPollAfterUpdate = false
poller() for poller in @documentPollers
return

View File

@@ -1,6 +1,6 @@
@import "ui-variables";
.icon:before {
.icon::before {
margin-right: @component-icon-padding;
}

View File

@@ -44,11 +44,10 @@ window.onload = function() {
extra: {_version: loadSettings.appVersion}
});
require('vm-compatibility-layer');
setupVmCompatibility();
setupCsonCache(cacheDir);
setupSourceMapCache(cacheDir);
setup6to5(cacheDir);
setupBabel(cacheDir);
require(loadSettings.bootstrapScript);
require('ipc').sendChannel('window-command', 'window:loaded');
@@ -91,10 +90,10 @@ var setupAtomHome = function() {
}
}
var setup6to5 = function(cacheDir) {
var to5 = require('../src/6to5');
to5.setCacheDirectory(path.join(cacheDir, 'js', '6to5'));
to5.register();
var setupBabel = function(cacheDir) {
var babel = require('../src/babel');
babel.setCacheDirectory(path.join(cacheDir, 'js', 'babel'));
babel.register();
}
var setupCsonCache = function(cacheDir) {
@@ -104,3 +103,9 @@ var setupCsonCache = function(cacheDir) {
var setupSourceMapCache = function(cacheDir) {
require('coffeestack').setCacheDirectory(path.join(cacheDir, 'coffee', 'source-maps'));
}
var setupVmCompatibility = function() {
var vm = require('vm');
if (!vm.Script.createContext)
vm.Script.createContext = vm.createContext;
}

View File

@@ -68,7 +68,7 @@ body {
color: #eee;
}
&:before {
&::before {
content: "\02022";
}
}

View File

@@ -25,10 +25,10 @@
white-space: nowrap;
}
// The background highlight uses :before rather than the item background so
// The background highlight uses ::before rather than the item background so
// it can span the entire width of the parent container rather than the size
// of the list item.
.selected:before {
.selected::before {
content: '';
background-color: @background-color-selected;
position: absolute;
@@ -42,7 +42,7 @@
position: relative;
}
.icon:before {
.icon::before {
margin-right: @component-icon-padding;
position: relative;
top: 1px;
@@ -73,7 +73,7 @@
// Nested items always get disclosure arrows
.list-nested-item > .list-item {
.octicon(chevron-down, @disclosure-arrow-size);
&:before{
&::before{
position: relative;
top: -1px;
margin-right: @component-icon-padding;
@@ -81,7 +81,7 @@
}
.list-nested-item.collapsed > .list-item {
.octicon(chevron-right, @disclosure-arrow-size);
&:before{
&::before{
left: 1px;
}
}

View File

@@ -6,7 +6,7 @@
.loading-message {
.octicon(hourglass);
&:before {
&::before {
font-size: 1.1em;
width: 1.1em;
height: 1.1em;

View File

@@ -62,7 +62,7 @@ atom-text-editor {
opacity: .6;
padding: 0 .4em;
&:before {
&::before {
text-align: center;
}
}
@@ -84,7 +84,7 @@ atom-text-editor {
visibility: visible;
&:before {
&::before {
position: relative;
left: -.1em;
}
@@ -129,7 +129,7 @@ atom-text-editor {
.line {
white-space: pre;
&.cursor-line .fold-marker:after {
&.cursor-line .fold-marker::after {
opacity: 1;
}
}
@@ -137,7 +137,7 @@ atom-text-editor {
.fold-marker {
cursor: default;
&:after {
&::after {
.icon(0.8em, inline);
content: @ellipsis;

View File

@@ -44,7 +44,7 @@
opacity: .6;
padding: 0 .4em;
&:before {
&::before {
text-align: center;
}
}
@@ -66,7 +66,7 @@
visibility: visible;
&:before {
&::before {
position: relative;
left: -.1em;
}
@@ -111,7 +111,7 @@
.line {
white-space: pre;
&.cursor-line .fold-marker:after {
&.cursor-line .fold-marker::after {
opacity: 1;
}
}
@@ -119,7 +119,7 @@
.fold-marker {
cursor: default;
&:after {
&::after {
.icon(0.8em, inline);
content: @ellipsis;

View File

@@ -18,7 +18,7 @@
.octicon(@name, @size: 16px) {
@import "octicon-utf-codes.less";
&:before {
&::before {
.icon(@size);
content: @@name
}
@@ -26,7 +26,7 @@
.mega-octicon(@name, @size: 32px) {
@import "octicon-utf-codes.less";
&:before {
&::before {
.icon(@size);
content: @@name
}