mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Merge branch 'master' into indent-soft-wrap
This commit is contained in:
@@ -1 +1 @@
|
||||
v0.10.33
|
||||
v0.12.0
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.138.0"
|
||||
"atom-package-manager": "0.140.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/)
|
||||
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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' }
|
||||
|
||||
53
package.json
53
package.json
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
40
spec/default-directory-provider-spec.coffee
Normal file
40
spec/default-directory-provider-spec.coffee
Normal 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
|
||||
3
spec/fixtures/babel/babel-double-quotes.js
vendored
Normal file
3
spec/fixtures/babel/babel-double-quotes.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use babel";
|
||||
|
||||
module.exports = v => v + 1
|
||||
3
spec/fixtures/babel/babel-single-quotes.js
vendored
Normal file
3
spec/fixtures/babel/babel-single-quotes.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'use babel';
|
||||
|
||||
module.exports = v => v + 1
|
||||
3
spec/fixtures/task-handler-with-deprecations.coffee
vendored
Normal file
3
spec/fixtures/task-handler-with-deprecations.coffee
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{Git} = require 'atom'
|
||||
|
||||
module.exports = ->
|
||||
@@ -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
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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})"
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
@@ -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
|
||||
|
||||
35
src/default-directory-provider.coffee
Normal file
35
src/default-directory-provider.coffee
Normal 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))
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
@@ -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(' ', maxLineNumberDigits - lineNumber.length)
|
||||
padding = _.multiplyString(' ', 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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ' '
|
||||
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@import "ui-variables";
|
||||
|
||||
.icon:before {
|
||||
.icon::before {
|
||||
margin-right: @component-icon-padding;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ body {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
content: "\02022";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.loading-message {
|
||||
.octicon(hourglass);
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
font-size: 1.1em;
|
||||
width: 1.1em;
|
||||
height: 1.1em;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user