mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Merge branch 'master' into mq-replace-optimist
This commit is contained in:
@@ -2,7 +2,11 @@ git:
|
||||
depth: 10
|
||||
|
||||
env:
|
||||
- NODE_VERSION=0.12
|
||||
global:
|
||||
- ATOM_ACCESS_TOKEN=da809a6077bb1b0aa7c5623f7b2d5f1fec2faae4
|
||||
|
||||
matrix:
|
||||
- NODE_VERSION=0.12
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
@@ -7,6 +7,9 @@ Atom is a hackable text editor for the 21st century, built on [Electron](https:/
|
||||
|
||||
Visit [atom.io](https://atom.io) to learn more or visit the [Atom forum](https://discuss.atom.io).
|
||||
|
||||
Follow [@AtomEditor](https://twitter.com/atomeditor) on Twitter for important
|
||||
announcements.
|
||||
|
||||
Visit [issue #3684](https://github.com/atom/atom/issues/3684) to learn more
|
||||
about the Atom 1.0 roadmap.
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.161.0"
|
||||
"atom-package-manager": "0.167.0"
|
||||
}
|
||||
}
|
||||
|
||||
2
atom.sh
2
atom.sh
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$(uname)" == 'Darwin' ]; then
|
||||
OS='Mac'
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"asar": "^0.4.4",
|
||||
"asar": "^0.5.0",
|
||||
"async": "~0.2.9",
|
||||
"donna": "1.0.10",
|
||||
"formidable": "~1.0.14",
|
||||
|
||||
@@ -2,6 +2,7 @@ path = require 'path'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
_ = require 'underscore-plus'
|
||||
normalizePackageData = require 'normalize-package-data'
|
||||
|
||||
OtherPlatforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32'].filter (platform) -> platform isnt process.platform
|
||||
|
||||
@@ -32,6 +33,7 @@ module.exports = (grunt) ->
|
||||
|
||||
modulesDirectory = path.join(appDir, 'node_modules')
|
||||
packages = {}
|
||||
invalidPackages = false
|
||||
|
||||
for moduleDirectory in fs.listSync(modulesDirectory)
|
||||
continue if path.basename(moduleDirectory) is '.bin'
|
||||
@@ -40,6 +42,13 @@ module.exports = (grunt) ->
|
||||
metadata = grunt.file.readJSON(metadataPath)
|
||||
continue unless metadata?.engines?.atom?
|
||||
|
||||
reportPackageError = (msg) ->
|
||||
invalidPackages = true
|
||||
grunt.log.error("#{metadata.name}: #{msg}")
|
||||
normalizePackageData metadata, reportPackageError, true
|
||||
if metadata.repository?.type is 'git'
|
||||
metadata.repository.url = metadata.repository.url?.replace(/^git\+/, '')
|
||||
|
||||
moduleCache = metadata._atomModuleCache ? {}
|
||||
|
||||
rm metadataPath
|
||||
@@ -79,3 +88,4 @@ module.exports = (grunt) ->
|
||||
metadata._atomKeymaps = getKeymaps(appDir)
|
||||
|
||||
grunt.file.write(path.join(appDir, 'package.json'), JSON.stringify(metadata))
|
||||
not invalidPackages
|
||||
|
||||
@@ -14,6 +14,7 @@ module.exports = (grunt) ->
|
||||
'ctags-darwin'
|
||||
'ctags-linux'
|
||||
'ctags-win32.exe'
|
||||
'**/node_modules/spellchecker/**'
|
||||
]
|
||||
unpack = "{#{unpack.join(',')}}"
|
||||
|
||||
|
||||
@@ -54,14 +54,14 @@ module.exports = (grunt) ->
|
||||
if process.platform in ['darwin', 'linux']
|
||||
options =
|
||||
cmd: appPath
|
||||
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"]
|
||||
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", '--one']
|
||||
opts:
|
||||
cwd: packagePath
|
||||
env: _.extend({}, process.env, ATOM_PATH: rootDir)
|
||||
else if process.platform is 'win32'
|
||||
options =
|
||||
cmd: process.env.comspec
|
||||
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", "--log-file=ci.log"]
|
||||
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", "--log-file=ci.log", '--one']
|
||||
opts:
|
||||
cwd: packagePath
|
||||
env: _.extend({}, process.env, ATOM_PATH: rootDir)
|
||||
|
||||
@@ -38,7 +38,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
|
||||
### openSUSE
|
||||
|
||||
* `sudo zypper install nodejs make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools`
|
||||
* `sudo zypper install nodejs nodejs-devel make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools`
|
||||
|
||||
## Instructions
|
||||
|
||||
|
||||
@@ -77,6 +77,15 @@
|
||||
'ctrl-k ctrl-down': 'window:focus-pane-below'
|
||||
'ctrl-k ctrl-left': 'window:focus-pane-on-left'
|
||||
'ctrl-k ctrl-right': 'window:focus-pane-on-right'
|
||||
'alt-1': 'pane:show-item-1'
|
||||
'alt-2': 'pane:show-item-2'
|
||||
'alt-3': 'pane:show-item-3'
|
||||
'alt-4': 'pane:show-item-4'
|
||||
'alt-5': 'pane:show-item-5'
|
||||
'alt-6': 'pane:show-item-6'
|
||||
'alt-7': 'pane:show-item-7'
|
||||
'alt-8': 'pane:show-item-8'
|
||||
'alt-9': 'pane:show-item-9'
|
||||
|
||||
'atom-workspace atom-text-editor':
|
||||
# Platform Bindings
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
{ label: 'Lower Case', command: 'editor:lower-case' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Delete to End of Word', command: 'editor:delete-to-end-of-word' }
|
||||
{ label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' }
|
||||
{ label: 'Delete to Next Word Boundary', command: 'editor:delete-to-next-word-boundary' }
|
||||
{ label: 'Delete Line', command: 'editor:delete-line' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Transpose', command: 'editor:transpose' }
|
||||
@@ -168,6 +170,10 @@
|
||||
]
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Increase Font Size', command: 'window:increase-font-size' }
|
||||
{ label: 'Decrease Font Size', command: 'window:decrease-font-size' }
|
||||
{ label: 'Reset Font Size', command: 'window:reset-font-size' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Toggle Soft Wrap', command: 'editor:toggle-soft-wrap' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
{ label: '&Lower Case', command: 'editor:lower-case' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Delete to End of &Word', command: 'editor:delete-to-end-of-word' }
|
||||
{ label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' }
|
||||
{ label: 'Delete to Next Word Boundary', command: 'editor:delete-to-next-word-boundary' }
|
||||
{ label: '&Delete Line', command: 'editor:delete-line' }
|
||||
{ type: 'separator' }
|
||||
{ label: '&Transpose', command: 'editor:transpose' }
|
||||
@@ -125,6 +127,10 @@
|
||||
]
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ label: '&Increase Font Size', command: 'window:increase-font-size' }
|
||||
{ label: '&Decrease Font Size', command: 'window:decrease-font-size' }
|
||||
{ label: 'Re&set Font Size', command: 'window:reset-font-size' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Toggle Soft &Wrap', command: 'editor:toggle-soft-wrap' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@
|
||||
{ label: '&Lower Case', command: 'editor:lower-case' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Delete to End of &Word', command: 'editor:delete-to-end-of-word' }
|
||||
{ label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' }
|
||||
{ label: 'Delete to Next Word Boundary', command: 'editor:delete-to-next-word-boundary' }
|
||||
{ label: '&Delete Line', command: 'editor:delete-line' }
|
||||
{ type: 'separator' }
|
||||
{ label: '&Transpose', command: 'editor:transpose' }
|
||||
@@ -124,6 +126,10 @@
|
||||
]
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ label: '&Increase Font Size', command: 'window:increase-font-size' }
|
||||
{ label: '&Decrease Font Size', command: 'window:decrease-font-size' }
|
||||
{ label: 'Re&set Font Size', command: 'window:reset-font-size' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Toggle Soft &Wrap', command: 'editor:toggle-soft-wrap' }
|
||||
]
|
||||
}
|
||||
|
||||
126
package.json
126
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.195.0",
|
||||
"version": "0.200.0",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -27,23 +27,24 @@
|
||||
"clear-cut": "^2.0.1",
|
||||
"coffee-cash": "0.8.0",
|
||||
"coffee-script": "1.8.0",
|
||||
"coffeestack": "^1.1.1",
|
||||
"coffeestack": "^1.1.2",
|
||||
"color": "^0.7.3",
|
||||
"delegato": "^1",
|
||||
"emissary": "^1.3.3",
|
||||
"event-kit": "^1.1",
|
||||
"event-kit": "^1.1.1",
|
||||
"first-mate": "^3.1",
|
||||
"fs-plus": "^2.7.1",
|
||||
"fs-plus": "^2.8.0",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
"git-utils": "^3.0.0",
|
||||
"grim": "1.2.2",
|
||||
"grim": "1.4.1",
|
||||
"jasmine-json": "~0.0",
|
||||
"jasmine-tagged": "^1.1.4",
|
||||
"jquery": "^2.1.1",
|
||||
"less-cache": "0.22",
|
||||
"marked": "^0.3.3",
|
||||
"mixto": "^1",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"nslog": "^2.0.0",
|
||||
"oniguruma": "^4.1",
|
||||
"pathwatcher": "^4.4",
|
||||
@@ -63,96 +64,99 @@
|
||||
"space-pen": "3.8.2",
|
||||
"stacktrace-parser": "0.1.1",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "^5.2",
|
||||
"text-buffer": "6.0.0",
|
||||
"theorist": "^1.0.2",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"yargs": ">=3.7.2 <4.0"
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.26.0",
|
||||
"atom-dark-syntax": "0.27.0",
|
||||
"atom-dark-ui": "0.49.0",
|
||||
"atom-light-syntax": "0.26.0",
|
||||
"atom-light-syntax": "0.28.0",
|
||||
"atom-light-ui": "0.41.0",
|
||||
"base16-tomorrow-dark-theme": "0.25.0",
|
||||
"base16-tomorrow-light-theme": "0.8.0",
|
||||
"one-dark-ui": "0.6.3",
|
||||
"one-dark-syntax": "0.3.0",
|
||||
"one-light-syntax": "0.4.0",
|
||||
"one-light-ui": "0.5.3",
|
||||
"solarized-dark-syntax": "0.32.0",
|
||||
"solarized-light-syntax": "0.19.0",
|
||||
"archive-view": "0.55.0",
|
||||
"autocomplete": "0.44.0",
|
||||
"autoflow": "0.22.0",
|
||||
"base16-tomorrow-dark-theme": "0.26.0",
|
||||
"base16-tomorrow-light-theme": "0.9.0",
|
||||
"one-dark-ui": "0.8.1",
|
||||
"one-dark-syntax": "0.5.0",
|
||||
"one-light-syntax": "0.6.0",
|
||||
"one-light-ui": "0.8.1",
|
||||
"solarized-dark-syntax": "0.35.0",
|
||||
"solarized-light-syntax": "0.21.0",
|
||||
"archive-view": "0.57.0",
|
||||
"autocomplete-atom-api": "0.9.0",
|
||||
"autocomplete-css": "0.7.2",
|
||||
"autocomplete-html": "0.7.2",
|
||||
"autocomplete-plus": "2.17.0",
|
||||
"autocomplete-snippets": "1.6.1",
|
||||
"autoflow": "0.23.0",
|
||||
"autosave": "0.20.0",
|
||||
"background-tips": "0.24.0",
|
||||
"bookmarks": "0.35.0",
|
||||
"bracket-matcher": "0.73.0",
|
||||
"command-palette": "0.35.0",
|
||||
"deprecation-cop": "0.40.0",
|
||||
"dev-live-reload": "0.45.0",
|
||||
"encoding-selector": "0.19.0",
|
||||
"bracket-matcher": "0.74.0",
|
||||
"command-palette": "0.36.0",
|
||||
"deprecation-cop": "0.48.0",
|
||||
"dev-live-reload": "0.46.0",
|
||||
"encoding-selector": "0.20.0",
|
||||
"exception-reporting": "0.24.0",
|
||||
"feedback": "0.38.0",
|
||||
"find-and-replace": "0.160.0",
|
||||
"fuzzy-finder": "0.81.0",
|
||||
"git-diff": "0.54.0",
|
||||
"find-and-replace": "0.166.0",
|
||||
"fuzzy-finder": "0.85.0",
|
||||
"git-diff": "0.55.0",
|
||||
"go-to-line": "0.30.0",
|
||||
"grammar-selector": "0.46.0",
|
||||
"grammar-selector": "0.47.0",
|
||||
"image-view": "0.54.0",
|
||||
"incompatible-packages": "0.24.0",
|
||||
"keybinding-resolver": "0.31.0",
|
||||
"keybinding-resolver": "0.32.0",
|
||||
"link": "0.30.0",
|
||||
"markdown-preview": "0.148.0",
|
||||
"metrics": "0.45.0",
|
||||
"notifications": "0.41.0",
|
||||
"open-on-github": "0.36.0",
|
||||
"package-generator": "0.38.0",
|
||||
"markdown-preview": "0.149.0",
|
||||
"metrics": "0.48.0",
|
||||
"notifications": "0.47.0",
|
||||
"open-on-github": "0.37.0",
|
||||
"package-generator": "0.39.0",
|
||||
"release-notes": "0.52.0",
|
||||
"settings-view": "0.195.0",
|
||||
"snippets": "0.88.0",
|
||||
"spell-check": "0.55.0",
|
||||
"status-bar": "0.69.0",
|
||||
"settings-view": "0.199.0",
|
||||
"snippets": "0.89.0",
|
||||
"spell-check": "0.58.0",
|
||||
"status-bar": "0.72.0",
|
||||
"styleguide": "0.44.0",
|
||||
"symbols-view": "0.94.0",
|
||||
"tabs": "0.67.0",
|
||||
"symbols-view": "0.97.0",
|
||||
"tabs": "0.68.0",
|
||||
"timecop": "0.31.0",
|
||||
"tree-view": "0.171.0",
|
||||
"update-package-dependencies": "0.9.0",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.27.0",
|
||||
"whitespace": "0.29.0",
|
||||
"wrap-guide": "0.32.0",
|
||||
"wrap-guide": "0.33.0",
|
||||
"language-c": "0.44.0",
|
||||
"language-clojure": "0.14.0",
|
||||
"language-coffee-script": "0.39.0",
|
||||
"language-clojure": "0.15.0",
|
||||
"language-coffee-script": "0.40.0",
|
||||
"language-csharp": "0.5.0",
|
||||
"language-css": "0.28.0",
|
||||
"language-gfm": "0.69.0",
|
||||
"language-css": "0.29.0",
|
||||
"language-gfm": "0.75.0",
|
||||
"language-git": "0.10.0",
|
||||
"language-go": "0.25.0",
|
||||
"language-html": "0.32.0",
|
||||
"language-hyperlink": "0.12.2",
|
||||
"language-java": "0.14.0",
|
||||
"language-javascript": "0.74.0",
|
||||
"language-go": "0.26.0",
|
||||
"language-html": "0.37.0",
|
||||
"language-hyperlink": "0.13.0",
|
||||
"language-java": "0.15.0",
|
||||
"language-javascript": "0.77.0",
|
||||
"language-json": "0.14.0",
|
||||
"language-less": "0.26.0",
|
||||
"language-less": "0.27.0",
|
||||
"language-make": "0.14.0",
|
||||
"language-mustache": "0.11.0",
|
||||
"language-objective-c": "0.15.0",
|
||||
"language-perl": "0.23.0",
|
||||
"language-php": "0.22.0",
|
||||
"language-perl": "0.24.0",
|
||||
"language-php": "0.23.0",
|
||||
"language-property-list": "0.8.0",
|
||||
"language-python": "0.34.0",
|
||||
"language-ruby": "0.52.0",
|
||||
"language-python": "0.35.0",
|
||||
"language-ruby": "0.53.0",
|
||||
"language-ruby-on-rails": "0.21.0",
|
||||
"language-sass": "0.36.0",
|
||||
"language-shellscript": "0.13.0",
|
||||
"language-sass": "0.38.0",
|
||||
"language-shellscript": "0.14.0",
|
||||
"language-source": "0.9.0",
|
||||
"language-sql": "0.15.0",
|
||||
"language-text": "0.6.0",
|
||||
"language-todo": "0.18.0",
|
||||
"language-toml": "0.15.0",
|
||||
"language-todo": "0.21.0",
|
||||
"language-toml": "0.16.0",
|
||||
"language-xml": "0.28.0",
|
||||
"language-yaml": "0.22.0"
|
||||
},
|
||||
|
||||
@@ -13,21 +13,44 @@ describe "BufferedProcess", ->
|
||||
window.onerror = oldOnError
|
||||
|
||||
describe "when there is an error handler specified", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
process = new BufferedProcess
|
||||
command: 'bad-command-nope'
|
||||
args: ['nothing']
|
||||
options: {}
|
||||
describe "when an error event is emitted by the process", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
process = new BufferedProcess
|
||||
command: 'bad-command-nope'
|
||||
args: ['nothing']
|
||||
options: {}
|
||||
|
||||
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
|
||||
process.onWillThrowError(errorSpy)
|
||||
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
|
||||
process.onWillThrowError(errorSpy)
|
||||
|
||||
waitsFor -> errorSpy.callCount > 0
|
||||
waitsFor -> errorSpy.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(window.onerror).not.toHaveBeenCalled()
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope ENOENT'
|
||||
runs ->
|
||||
expect(window.onerror).not.toHaveBeenCalled()
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope ENOENT'
|
||||
|
||||
describe "when an error is thrown spawning the process", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
spyOn(ChildProcess, 'spawn').andCallFake ->
|
||||
error = new Error('Something is really wrong')
|
||||
error.code = 'EAGAIN'
|
||||
throw error
|
||||
|
||||
process = new BufferedProcess
|
||||
command: 'ls'
|
||||
args: []
|
||||
options: {}
|
||||
|
||||
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
|
||||
process.onWillThrowError(errorSpy)
|
||||
|
||||
waitsFor -> errorSpy.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(window.onerror).not.toHaveBeenCalled()
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'Something is really wrong'
|
||||
|
||||
describe "when there is not an error handler specified", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
@@ -73,3 +96,21 @@ describe "BufferedProcess", ->
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/c'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"dir"'
|
||||
|
||||
it "calls the specified stdout, stderr, and exit callbacks ", ->
|
||||
stdout = ''
|
||||
stderr = ''
|
||||
exitCallback = jasmine.createSpy('exit callback')
|
||||
process = new BufferedProcess
|
||||
command: atom.packages.getApmPath()
|
||||
args: ['-h']
|
||||
options: {}
|
||||
stdout: (lines) -> stdout += lines
|
||||
stderr: (lines) -> stderr += lines
|
||||
exit: exitCallback
|
||||
|
||||
waitsFor -> exitCallback.callCount is 1
|
||||
|
||||
runs ->
|
||||
expect(stderr).toContain 'apm - Atom Package Manager'
|
||||
expect(stdout).toEqual ''
|
||||
|
||||
@@ -30,15 +30,12 @@ describe "CustomGutterComponent", ->
|
||||
|
||||
buildTestState = (customDecorations) ->
|
||||
mockTestState =
|
||||
gutters:
|
||||
content: if customDecorations then customDecorations else {}
|
||||
styles:
|
||||
scrollHeight: 100
|
||||
scrollTop: 10
|
||||
backgroundColor: 'black'
|
||||
sortedDescriptions: [{gutter, visible: true}]
|
||||
customDecorations: customDecorations
|
||||
lineNumberGutter:
|
||||
maxLineNumberDigits: 10
|
||||
lineNumbers: {}
|
||||
|
||||
mockTestState
|
||||
|
||||
it "sets the custom-decoration wrapper's scrollHeight, scrollTop, and background color", ->
|
||||
@@ -53,7 +50,7 @@ describe "CustomGutterComponent", ->
|
||||
expect(decorationsWrapperNode.style.backgroundColor).not.toBe ''
|
||||
|
||||
it "creates a new DOM node for a new decoration and adds it to the gutter at the right place", ->
|
||||
customDecorations = 'test-gutter':
|
||||
customDecorations =
|
||||
'decoration-id-1':
|
||||
top: 0
|
||||
height: 10
|
||||
@@ -75,7 +72,7 @@ describe "CustomGutterComponent", ->
|
||||
expect(decorationItem).toBe decorationItem1
|
||||
|
||||
it "updates the existing DOM node for a decoration that existed but has new properties", ->
|
||||
initialCustomDecorations = 'test-gutter':
|
||||
initialCustomDecorations =
|
||||
'decoration-id-1':
|
||||
top: 0
|
||||
height: 10
|
||||
@@ -86,7 +83,7 @@ describe "CustomGutterComponent", ->
|
||||
|
||||
# Change the dimensions and item, remove the class.
|
||||
decorationItem2 = document.createElement('div')
|
||||
changedCustomDecorations = 'test-gutter':
|
||||
changedCustomDecorations =
|
||||
'decoration-id-1':
|
||||
top: 10
|
||||
height: 20
|
||||
@@ -103,7 +100,7 @@ describe "CustomGutterComponent", ->
|
||||
expect(decorationItem).toBe decorationItem2
|
||||
|
||||
# Remove the item, add a class.
|
||||
changedCustomDecorations = 'test-gutter':
|
||||
changedCustomDecorations =
|
||||
'decoration-id-1':
|
||||
top: 10
|
||||
height: 20
|
||||
@@ -118,7 +115,7 @@ describe "CustomGutterComponent", ->
|
||||
expect(changedDecorationNode.children.length).toBe 0
|
||||
|
||||
it "removes any decorations that existed previously but aren't in the latest update", ->
|
||||
customDecorations = 'test-gutter':
|
||||
customDecorations =
|
||||
'decoration-id-1':
|
||||
top: 0
|
||||
height: 10
|
||||
@@ -127,6 +124,6 @@ describe "CustomGutterComponent", ->
|
||||
decorationsWrapperNode = gutterComponent.getDomNode().children.item(0)
|
||||
expect(decorationsWrapperNode.children.length).toBe 1
|
||||
|
||||
emptyCustomDecorations = 'test-gutter': {}
|
||||
emptyCustomDecorations = {}
|
||||
gutterComponent.updateSync(buildTestState(emptyCustomDecorations))
|
||||
expect(decorationsWrapperNode.children.length).toBe 0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
DefaultDirectoryProvider = require "../src/default-directory-provider"
|
||||
path = require "path"
|
||||
fs = require 'fs-plus'
|
||||
temp = require "temp"
|
||||
|
||||
describe "DefaultDirectoryProvider", ->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "no events",
|
||||
"name": "package-with-empty-activation-commands",
|
||||
"version": "0.1.0",
|
||||
"activationCommands": {"atom-workspace": []}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "package-with-incompatible-native-module",
|
||||
"version": "1.0",
|
||||
"version": "1.0.0",
|
||||
"main": "./main.js"
|
||||
}
|
||||
|
||||
4
spec/fixtures/packages/package-with-invalid-url-package-json/package.json
vendored
Normal file
4
spec/fixtures/packages/package-with-invalid-url-package-json/package.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "package-with-invalid-url-package-json",
|
||||
"repository": "foo"
|
||||
}
|
||||
4
spec/fixtures/packages/package-with-short-url-package-json/package.json
vendored
Normal file
4
spec/fixtures/packages/package-with-short-url-package-json/package.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "package-with-short-url-package-json",
|
||||
"repository": "example/repo"
|
||||
}
|
||||
@@ -5,17 +5,20 @@ describe "GutterContainerComponent", ->
|
||||
[gutterContainerComponent] = []
|
||||
mockGutterContainer = {}
|
||||
|
||||
buildTestState = (sortedDescriptions) ->
|
||||
mockTestState =
|
||||
gutters:
|
||||
scrollHeight: 100
|
||||
scrollTop: 10
|
||||
backgroundColor: 'black'
|
||||
sortedDescriptions: sortedDescriptions
|
||||
customDecorations: {}
|
||||
lineNumberGutter:
|
||||
maxLineNumberDigits: 10
|
||||
lineNumbers: {}
|
||||
buildTestState = (gutters) ->
|
||||
styles =
|
||||
scrollHeight: 100
|
||||
scrollTop: 10
|
||||
backgroundColor: 'black'
|
||||
|
||||
mockTestState = {gutters: []}
|
||||
for gutter in gutters
|
||||
if gutter.name is 'line-number'
|
||||
content = {maxLineNumberDigits: 10, lineNumbers: {}}
|
||||
else
|
||||
content = {}
|
||||
mockTestState.gutters.push({gutter, styles, content, visible: gutter.visible})
|
||||
|
||||
mockTestState
|
||||
|
||||
beforeEach ->
|
||||
@@ -30,7 +33,7 @@ describe "GutterContainerComponent", ->
|
||||
describe "when updated with state that contains a new line-number gutter", ->
|
||||
it "adds a LineNumberGutterComponent to its children", ->
|
||||
lineNumberGutter = new Gutter(mockGutterContainer, {name: 'line-number'})
|
||||
testState = buildTestState([{gutter: lineNumberGutter, visible: true}])
|
||||
testState = buildTestState([lineNumberGutter])
|
||||
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 0
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
@@ -45,7 +48,7 @@ describe "GutterContainerComponent", ->
|
||||
describe "when updated with state that contains a new custom gutter", ->
|
||||
it "adds a CustomGutterComponent to its children", ->
|
||||
customGutter = new Gutter(mockGutterContainer, {name: 'custom'})
|
||||
testState = buildTestState([{gutter: customGutter, visible: true}])
|
||||
testState = buildTestState([customGutter])
|
||||
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 0
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
@@ -57,15 +60,16 @@ describe "GutterContainerComponent", ->
|
||||
|
||||
describe "when updated with state that contains a new gutter that is not visible", ->
|
||||
it "creates the gutter view but hides it, and unhides it when it is later updated to be visible", ->
|
||||
customGutter = new Gutter(mockGutterContainer, {name: 'custom'})
|
||||
testState = buildTestState([{gutter: customGutter, visible: false}])
|
||||
customGutter = new Gutter(mockGutterContainer, {name: 'custom', visible: false})
|
||||
testState = buildTestState([customGutter])
|
||||
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 1
|
||||
expectedCustomGutterNode = gutterContainerComponent.getDomNode().children.item(0)
|
||||
expect(expectedCustomGutterNode.style.display).toBe 'none'
|
||||
|
||||
testState = buildTestState([{gutter: customGutter, visible: true}])
|
||||
customGutter.show()
|
||||
testState = buildTestState([customGutter])
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 1
|
||||
expectedCustomGutterNode = gutterContainerComponent.getDomNode().children.item(0)
|
||||
@@ -74,20 +78,20 @@ describe "GutterContainerComponent", ->
|
||||
describe "when updated with a gutter that already exists", ->
|
||||
it "reuses the existing gutter view, instead of recreating it", ->
|
||||
customGutter = new Gutter(mockGutterContainer, {name: 'custom'})
|
||||
testState = buildTestState([{gutter: customGutter, visible: true}])
|
||||
testState = buildTestState([customGutter])
|
||||
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 1
|
||||
expectedCustomGutterNode = gutterContainerComponent.getDomNode().children.item(0)
|
||||
|
||||
testState = buildTestState([{gutter: customGutter, visible: true}])
|
||||
testState = buildTestState([customGutter])
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 1
|
||||
expect(gutterContainerComponent.getDomNode().children.item(0)).toBe expectedCustomGutterNode
|
||||
|
||||
it "removes a gutter from the DOM if it does not appear in the latest state update", ->
|
||||
lineNumberGutter = new Gutter(mockGutterContainer, {name: 'line-number'})
|
||||
testState = buildTestState([{gutter: lineNumberGutter, visible: true}])
|
||||
testState = buildTestState([lineNumberGutter])
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 1
|
||||
@@ -99,7 +103,7 @@ describe "GutterContainerComponent", ->
|
||||
it "positions (and repositions) the gutters to match the order they appear in each state update", ->
|
||||
lineNumberGutter = new Gutter(mockGutterContainer, {name: 'line-number'})
|
||||
customGutter1 = new Gutter(mockGutterContainer, {name: 'custom', priority: -100})
|
||||
testState = buildTestState([{gutter: customGutter1, visible: true}, {gutter: lineNumberGutter, visible: true}])
|
||||
testState = buildTestState([customGutter1, lineNumberGutter])
|
||||
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 2
|
||||
@@ -110,11 +114,7 @@ describe "GutterContainerComponent", ->
|
||||
|
||||
# Add a gutter.
|
||||
customGutter2 = new Gutter(mockGutterContainer, {name: 'custom2', priority: -10})
|
||||
testState = buildTestState([
|
||||
{gutter: customGutter1, visible: true},
|
||||
{gutter: customGutter2, visible: true},
|
||||
{gutter: lineNumberGutter, visible: true}
|
||||
])
|
||||
testState = buildTestState([customGutter1, customGutter2, lineNumberGutter])
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 3
|
||||
expectedCustomGutterNode1 = gutterContainerComponent.getDomNode().children.item(0)
|
||||
@@ -125,12 +125,9 @@ describe "GutterContainerComponent", ->
|
||||
expect(expectedLineNumbersNode).toBe atom.views.getView(lineNumberGutter)
|
||||
|
||||
# Hide one gutter, reposition one gutter, remove one gutter; and add a new gutter.
|
||||
customGutter2.hide()
|
||||
customGutter3 = new Gutter(mockGutterContainer, {name: 'custom3', priority: 100})
|
||||
testState = buildTestState([
|
||||
{gutter: customGutter2, visible: false},
|
||||
{gutter: customGutter1, visible: true},
|
||||
{gutter: customGutter3, visible: true}
|
||||
])
|
||||
testState = buildTestState([customGutter2, customGutter1, customGutter3])
|
||||
gutterContainerComponent.updateSync(testState)
|
||||
expect(gutterContainerComponent.getDomNode().children.length).toBe 3
|
||||
expectedCustomGutterNode2 = gutterContainerComponent.getDomNode().children.item(0)
|
||||
|
||||
@@ -15,15 +15,23 @@ ChromedriverPort = 9515
|
||||
ChromedriverURLBase = "/wd/hub"
|
||||
ChromedriverStatusURL = "http://localhost:#{ChromedriverPort}#{ChromedriverURLBase}/status"
|
||||
|
||||
pollChromeDriver = (done) ->
|
||||
chromeDriverUp = (done) ->
|
||||
checkStatus = ->
|
||||
http.get(ChromedriverStatusURL, (response) ->
|
||||
if response.statusCode is 200
|
||||
done()
|
||||
else
|
||||
pollChromeDriver(done)
|
||||
).on("error", -> pollChromeDriver(done))
|
||||
http
|
||||
.get ChromedriverStatusURL, (response) ->
|
||||
if response.statusCode is 200
|
||||
done()
|
||||
else
|
||||
chromeDriverUp(done)
|
||||
.on("error", -> chromeDriverUp(done))
|
||||
setTimeout(checkStatus, 100)
|
||||
|
||||
chromeDriverDown = (done) ->
|
||||
checkStatus = ->
|
||||
http
|
||||
.get ChromedriverStatusURL, (response) ->
|
||||
chromeDriverDown(done)
|
||||
.on("error", done)
|
||||
setTimeout(checkStatus, 100)
|
||||
|
||||
buildAtomClient = (args, env) ->
|
||||
@@ -137,9 +145,9 @@ module.exports = (args, env, fn) ->
|
||||
chromedriver.stderr.on "close", ->
|
||||
resolve(errorCode)
|
||||
|
||||
waitsFor("webdriver to start", pollChromeDriver, 15000)
|
||||
waitsFor("webdriver to start", chromeDriverUp, 15000)
|
||||
|
||||
waitsFor("webdriver to finish", (done) ->
|
||||
waitsFor("tests to run", (done) ->
|
||||
finish = once ->
|
||||
client
|
||||
.simulateQuit()
|
||||
@@ -162,3 +170,5 @@ module.exports = (args, env, fn) ->
|
||||
|
||||
fn(client.init()).then(finish)
|
||||
, 30000)
|
||||
|
||||
waitsFor("webdriver to stop", chromeDriverDown, 15000)
|
||||
|
||||
@@ -9,7 +9,7 @@ return if process.env.TRAVIS
|
||||
fs = require "fs"
|
||||
path = require "path"
|
||||
temp = require("temp").track()
|
||||
runAtom = require("./helpers/start-atom")
|
||||
runAtom = require "./helpers/start-atom"
|
||||
|
||||
describe "Starting Atom", ->
|
||||
[tempDirPath, otherTempDirPath, atomHome] = []
|
||||
@@ -41,6 +41,69 @@ describe "Starting Atom", ->
|
||||
.then ({value}) -> expect(value).toBe "Hello!"
|
||||
.dispatchCommand("editor:delete-line")
|
||||
|
||||
it "opens the file to the specified line number", ->
|
||||
filePath = path.join(fs.realpathSync(tempDirPath), "new-file")
|
||||
fs.writeFileSync filePath, """
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
"""
|
||||
|
||||
runAtom ["#{filePath}:3"], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForWindowCount(1, 1000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(1, 1000)
|
||||
.waitForExist("atom-text-editor", 5000)
|
||||
.then (exists) -> expect(exists).toBe true
|
||||
|
||||
.execute -> atom.workspace.getActiveTextEditor().getPath()
|
||||
.then ({value}) -> expect(value).toBe filePath
|
||||
|
||||
.execute -> atom.workspace.getActiveTextEditor().getCursorBufferPosition()
|
||||
.then ({value}) ->
|
||||
expect(value.row).toBe 2
|
||||
expect(value.column).toBe 0
|
||||
|
||||
it "opens the file to the specified line number and column number", ->
|
||||
filePath = path.join(fs.realpathSync(tempDirPath), "new-file")
|
||||
fs.writeFileSync filePath, """
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
"""
|
||||
|
||||
runAtom ["#{filePath}:2:2"], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForWindowCount(1, 1000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(1, 1000)
|
||||
.waitForExist("atom-text-editor", 5000)
|
||||
.then (exists) -> expect(exists).toBe true
|
||||
|
||||
.execute -> atom.workspace.getActiveTextEditor().getPath()
|
||||
.then ({value}) -> expect(value).toBe filePath
|
||||
|
||||
.execute -> atom.workspace.getActiveTextEditor().getCursorBufferPosition()
|
||||
.then ({value}) ->
|
||||
expect(value.row).toBe 1
|
||||
expect(value.column).toBe 1
|
||||
|
||||
it "removes all trailing whitespace and colons from the specified path", ->
|
||||
filePath = path.join(tempDirPath, "new-file")
|
||||
runAtom ["#{filePath}: "], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForWindowCount(1, 1000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(1, 1000)
|
||||
.waitForExist("atom-text-editor", 5000)
|
||||
.then (exists) -> expect(exists).toBe true
|
||||
|
||||
.execute -> atom.workspace.getActiveTextEditor().getPath()
|
||||
.then ({value}) -> expect(value).toBe filePath
|
||||
|
||||
describe "when there is already a window open", ->
|
||||
it "reuses that window when opening files, but not when opening directories", ->
|
||||
tempFilePath = path.join(temp.mkdirSync("a-third-dir"), "a-file")
|
||||
@@ -150,10 +213,12 @@ describe "Starting Atom", ->
|
||||
.waitForWindowCount(2, 10000)
|
||||
.then ({value: windowHandles}) ->
|
||||
@window(windowHandles[0])
|
||||
.waitForExist("atom-workspace")
|
||||
.treeViewRootDirectories()
|
||||
.then ({value: directories}) -> windowProjectPaths.push(directories)
|
||||
|
||||
.window(windowHandles[1])
|
||||
.waitForExist("atom-workspace")
|
||||
.treeViewRootDirectories()
|
||||
.then ({value: directories}) -> windowProjectPaths.push(directories)
|
||||
|
||||
|
||||
@@ -41,6 +41,15 @@ describe "PackageManager", ->
|
||||
expect(addErrorHandler.callCount).toBe 1
|
||||
expect(addErrorHandler.argsForCall[0][0].message).toContain("Failed to load the package-with-broken-package-json package")
|
||||
|
||||
it "normalizes short repository urls in package.json", ->
|
||||
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "https://github.com/example/repo.git"
|
||||
|
||||
{metadata} = atom.packages.loadPackage("package-with-invalid-url-package-json")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "foo"
|
||||
|
||||
it "returns null if the package is not found in any package directory", ->
|
||||
spyOn(console, 'warn')
|
||||
expect(atom.packages.loadPackage("this-package-cannot-be-found")).toBeNull()
|
||||
@@ -815,3 +824,37 @@ describe "PackageManager", ->
|
||||
expect(atom.config.get('core.themes')).not.toContain packageName
|
||||
expect(atom.config.get('core.themes')).not.toContain packageName
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
describe "deleting non-bundled autocomplete packages", ->
|
||||
[autocompleteCSSPath, autocompletePlusPath] = []
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
|
||||
beforeEach ->
|
||||
fixturePath = path.resolve(__dirname, './fixtures/packages')
|
||||
autocompleteCSSPath = path.join(fixturePath, 'autocomplete-css')
|
||||
autocompletePlusPath = path.join(fixturePath, 'autocomplete-plus')
|
||||
|
||||
try
|
||||
fs.mkdirSync(autocompleteCSSPath)
|
||||
fs.writeFileSync(path.join(autocompleteCSSPath, 'package.json'), '{}')
|
||||
fs.symlinkSync(path.join(fixturePath, 'package-with-main'), autocompletePlusPath, 'dir')
|
||||
|
||||
expect(fs.isDirectorySync(autocompleteCSSPath)).toBe true
|
||||
expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true
|
||||
|
||||
jasmine.unspy(atom.packages, 'uninstallAutocompletePlus')
|
||||
|
||||
afterEach ->
|
||||
try
|
||||
fs.unlink autocompletePlusPath, ->
|
||||
|
||||
it "removes the packages", ->
|
||||
atom.packages.loadPackages()
|
||||
|
||||
waitsFor ->
|
||||
not fs.isDirectorySync(autocompleteCSSPath)
|
||||
|
||||
runs ->
|
||||
expect(fs.isDirectorySync(autocompleteCSSPath)).toBe false
|
||||
expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true
|
||||
|
||||
@@ -132,3 +132,25 @@ describe "PaneContainerElement", ->
|
||||
# dynamically close pane, the pane's flexscale will recorver to origin value
|
||||
lowerPane.close()
|
||||
expectPaneScale [leftPane, 0.5], [rightPane, 1.5]
|
||||
|
||||
it "unsubscribes from mouse events when the pane is detached", ->
|
||||
container.getActivePane().splitRight()
|
||||
element = getResizeElement(0)
|
||||
spyOn(document, 'addEventListener').andCallThrough()
|
||||
spyOn(document, 'removeEventListener').andCallThrough()
|
||||
spyOn(element, 'resizeStopped').andCallThrough()
|
||||
|
||||
element.dispatchEvent(new MouseEvent('mousedown',
|
||||
view: window
|
||||
bubbles: true
|
||||
button: 0
|
||||
))
|
||||
|
||||
waitsFor ->
|
||||
document.addEventListener.callCount is 2
|
||||
|
||||
runs ->
|
||||
expect(element.resizeStopped.callCount).toBe 0
|
||||
container.destroy()
|
||||
expect(element.resizeStopped.callCount).toBe 1
|
||||
expect(document.removeEventListener.callCount).toBe 2
|
||||
|
||||
@@ -140,6 +140,8 @@ beforeEach ->
|
||||
spyOn(clipboard, 'writeText').andCallFake (text) -> clipboardContent = text
|
||||
spyOn(clipboard, 'readText').andCallFake -> clipboardContent
|
||||
|
||||
spyOn(atom.packages, 'uninstallAutocompletePlus')
|
||||
|
||||
addCustomMatchers(this)
|
||||
|
||||
afterEach ->
|
||||
@@ -311,7 +313,7 @@ window.waitsForPromise = (args...) ->
|
||||
else
|
||||
promise.then(moveOn)
|
||||
promise.catch.call promise, (error) ->
|
||||
jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected with #{jasmine.pp(error)}")
|
||||
jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected with: #{error?.message} #{jasmine.pp(error)}")
|
||||
moveOn()
|
||||
|
||||
window.resetTimeouts = ->
|
||||
|
||||
@@ -2266,13 +2266,13 @@ describe "TextEditorComponent", ->
|
||||
editor.setText("")
|
||||
componentNode.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode))
|
||||
|
||||
currentTime += 99
|
||||
currentTime += 100
|
||||
componentNode.dispatchEvent(buildTextInputEvent(data: 'y', target: inputNode))
|
||||
|
||||
currentTime += 99
|
||||
currentTime += 100
|
||||
componentNode.dispatchEvent(new CustomEvent('editor:duplicate-lines', bubbles: true, cancelable: true))
|
||||
|
||||
currentTime += 100
|
||||
currentTime += 101
|
||||
componentNode.dispatchEvent(new CustomEvent('editor:duplicate-lines', bubbles: true, cancelable: true))
|
||||
expect(editor.getText()).toBe "xy\nxy\nxy"
|
||||
|
||||
|
||||
@@ -65,6 +65,35 @@ describe "TextEditorElement", ->
|
||||
element.getModel().destroy()
|
||||
expect(component.mounted).toBe false
|
||||
|
||||
describe "when the editor is detached from the DOM and then reattached", ->
|
||||
it "does not render duplicate line numbers", ->
|
||||
editor = new TextEditor
|
||||
editor.setText('1\n2\n3')
|
||||
element = atom.views.getView(editor)
|
||||
|
||||
jasmine.attachToDOM(element)
|
||||
|
||||
initialCount = element.shadowRoot.querySelectorAll('.line-number').length
|
||||
|
||||
element.remove()
|
||||
jasmine.attachToDOM(element)
|
||||
expect(element.shadowRoot.querySelectorAll('.line-number').length).toBe initialCount
|
||||
|
||||
it "does not render duplicate decorations in custom gutters", ->
|
||||
editor = new TextEditor
|
||||
editor.setText('1\n2\n3')
|
||||
editor.addGutter({name: 'test-gutter'})
|
||||
marker = editor.markBufferRange([[0,0],[2,0]])
|
||||
editor.decorateMarker(marker, {type: 'gutter', gutterName: 'test-gutter'})
|
||||
element = atom.views.getView(editor)
|
||||
|
||||
jasmine.attachToDOM(element)
|
||||
initialDecorationCount = element.shadowRoot.querySelectorAll('.decoration').length
|
||||
|
||||
element.remove()
|
||||
jasmine.attachToDOM(element)
|
||||
expect(element.shadowRoot.querySelectorAll('.decoration').length).toBe initialDecorationCount
|
||||
|
||||
describe "focus and blur handling", ->
|
||||
describe "when the editor.useShadowDOM config option is true", ->
|
||||
it "proxies focus/blur events to/from the hidden input inside the shadow root", ->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,23 +36,21 @@ describe "TextEditor", ->
|
||||
|
||||
it "preserves the invisibles setting", ->
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
previousInvisibles = editor.displayBuffer.invisibles
|
||||
previousInvisibles = editor.tokenizedLineForScreenRow(0).invisibles
|
||||
|
||||
editor2 = editor.testSerialization()
|
||||
|
||||
expect(editor2.displayBuffer.invisibles).toEqual previousInvisibles
|
||||
expect(editor2.displayBuffer.tokenizedBuffer.invisibles).toEqual previousInvisibles
|
||||
expect(previousInvisibles).toBeDefined()
|
||||
expect(editor2.displayBuffer.tokenizedLineForScreenRow(0).invisibles).toEqual previousInvisibles
|
||||
|
||||
it "updates invisibles if the settings have changed between serialization and deserialization", ->
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
previousInvisibles = editor.displayBuffer.invisibles
|
||||
|
||||
state = editor.serialize()
|
||||
atom.config.set('editor.invisibles', eol: '?')
|
||||
editor2 = TextEditor.deserialize(state)
|
||||
|
||||
expect(editor2.displayBuffer.invisibles.eol).toBe '?'
|
||||
expect(editor2.displayBuffer.tokenizedBuffer.invisibles.eol).toBe '?'
|
||||
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
|
||||
|
||||
describe "when the editor is constructed with an initialLine option", ->
|
||||
it "positions the cursor on the specified line", ->
|
||||
@@ -2343,6 +2341,62 @@ describe "TextEditor", ->
|
||||
editor.backspace()
|
||||
expect(editor.lineTextForBufferRow(0)).toBe 'var = () {'
|
||||
|
||||
describe ".deleteToPreviousWordBoundary()", ->
|
||||
describe "when no text is selected", ->
|
||||
it "deletes to the previous word boundary", ->
|
||||
editor.setCursorBufferPosition([0, 16])
|
||||
editor.addCursorAtBufferPosition([1, 21])
|
||||
[cursor1, cursor2] = editor.getCursors()
|
||||
|
||||
editor.deleteToPreviousWordBoundary()
|
||||
expect(buffer.lineForRow(0)).toBe 'var quicksort =function () {'
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = (items) {'
|
||||
expect(cursor1.getBufferPosition()).toEqual [0, 15]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1, 13]
|
||||
|
||||
editor.deleteToPreviousWordBoundary()
|
||||
expect(buffer.lineForRow(0)).toBe 'var quicksort function () {'
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort =(items) {'
|
||||
expect(cursor1.getBufferPosition()).toEqual [0, 14]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1, 12]
|
||||
|
||||
describe "when text is selected", ->
|
||||
it "deletes only selected text", ->
|
||||
editor.setSelectedBufferRange([[1, 24], [1, 27]])
|
||||
editor.deleteToPreviousWordBoundary()
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it) {'
|
||||
|
||||
describe ".deleteToNextWordBoundary()", ->
|
||||
describe "when no text is selected", ->
|
||||
it "deletes to the next word boundary", ->
|
||||
editor.setCursorBufferPosition([0, 15])
|
||||
editor.addCursorAtBufferPosition([1, 24])
|
||||
[cursor1, cursor2] = editor.getCursors()
|
||||
|
||||
editor.deleteToNextWordBoundary()
|
||||
expect(buffer.lineForRow(0)).toBe 'var quicksort =function () {'
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it) {'
|
||||
expect(cursor1.getBufferPosition()).toEqual [0, 15]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1, 24]
|
||||
|
||||
editor.deleteToNextWordBoundary()
|
||||
expect(buffer.lineForRow(0)).toBe 'var quicksort = () {'
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it {'
|
||||
expect(cursor1.getBufferPosition()).toEqual [0, 15]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1, 24]
|
||||
|
||||
editor.deleteToNextWordBoundary()
|
||||
expect(buffer.lineForRow(0)).toBe 'var quicksort =() {'
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it{'
|
||||
expect(cursor1.getBufferPosition()).toEqual [0, 15]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1, 24]
|
||||
|
||||
describe "when text is selected", ->
|
||||
it "deletes only selected text", ->
|
||||
editor.setSelectedBufferRange([[1, 24], [1, 27]])
|
||||
editor.deleteToNextWordBoundary()
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it) {'
|
||||
|
||||
describe ".deleteToBeginningOfWord()", ->
|
||||
describe "when no text is selected", ->
|
||||
it "deletes all text between the cursor and the beginning of the word", ->
|
||||
|
||||
@@ -611,7 +611,8 @@ describe "TokenizedBuffer", ->
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
tokenizedBuffer.setInvisibles(space: 'S', tab: 'T')
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "SST Sa line with tabsTand T spacesSTS"
|
||||
@@ -623,7 +624,7 @@ describe "TokenizedBuffer", ->
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
tokenizedBuffer.setInvisibles(cr: 'R', eol: 'N')
|
||||
atom.config.set("editor.invisibles", cr: 'R', eol: 'N')
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R', 'N']
|
||||
@@ -634,7 +635,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(left.endOfLineInvisibles).toBe null
|
||||
expect(right.endOfLineInvisibles).toEqual ['R', 'N']
|
||||
|
||||
tokenizedBuffer.setInvisibles(cr: 'R', eol: false)
|
||||
atom.config.set("editor.invisibles", cr: 'R', eol: false)
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R']
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual []
|
||||
|
||||
@@ -688,7 +689,8 @@ describe "TokenizedBuffer", ->
|
||||
it "sets leading and trailing whitespace correctly on a line with invisible characters that is copied", ->
|
||||
buffer.setText(" \t a line with tabs\tand \tspaces \t ")
|
||||
|
||||
tokenizedBuffer.setInvisibles(space: 'S', tab: 'T')
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
line = tokenizedBuffer.tokenizedLineForRow(0).copy()
|
||||
@@ -696,7 +698,8 @@ describe "TokenizedBuffer", ->
|
||||
expect(line.tokens[line.tokens.length - 1].firstTrailingWhitespaceIndex).toBe 0
|
||||
|
||||
it "sets the ::firstNonWhitespaceIndex and ::firstTrailingWhitespaceIndex correctly when tokens are split for soft-wrapping", ->
|
||||
tokenizedBuffer.setInvisibles(space: 'S')
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
atom.config.set("editor.invisibles", space: 'S')
|
||||
buffer.setText(" token ")
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
token = tokenizedBuffer.tokenizedLines[0].tokens[0]
|
||||
|
||||
44
spec/workspace-element-spec.coffee
Normal file
44
spec/workspace-element-spec.coffee
Normal file
@@ -0,0 +1,44 @@
|
||||
ipc = require 'ipc'
|
||||
path = require 'path'
|
||||
temp = require('temp').track()
|
||||
|
||||
describe "WorkspaceElement", ->
|
||||
workspaceElement = null
|
||||
|
||||
beforeEach ->
|
||||
workspaceElement = atom.views.getView(atom.workspace)
|
||||
|
||||
describe "the 'window:run-package-specs' command", ->
|
||||
it "runs the package specs for the active item's project path, or the first project path", ->
|
||||
spyOn(ipc, 'send')
|
||||
|
||||
# No project paths. Don't try to run specs.
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).not.toHaveBeenCalledWith("run-package-specs")
|
||||
|
||||
projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
|
||||
atom.project.setPaths(projectPaths)
|
||||
|
||||
# No active item. Use first project directory.
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipc.send.reset()
|
||||
|
||||
# Active item doesn't implement ::getPath(). Use first project directory.
|
||||
item = document.createElement("div")
|
||||
atom.workspace.getActivePane().activateItem(item)
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipc.send.reset()
|
||||
|
||||
# Active item has no path. Use first project directory.
|
||||
item.getPath = -> null
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipc.send.reset()
|
||||
|
||||
# Active item has path. Use project path for item path.
|
||||
item.getPath = -> path.join(projectPaths[1], "a-file.txt")
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec"))
|
||||
ipc.send.reset()
|
||||
@@ -426,6 +426,35 @@ describe "Workspace", ->
|
||||
workspace.decreaseFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe 1
|
||||
|
||||
describe "::resetFontSize()", ->
|
||||
it "resets the font size to the window's starting font size", ->
|
||||
originalFontSize = atom.config.get('editor.fontSize')
|
||||
|
||||
workspace.increaseFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe originalFontSize + 1
|
||||
workspace.resetFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe originalFontSize
|
||||
workspace.decreaseFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe originalFontSize - 1
|
||||
workspace.resetFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe originalFontSize
|
||||
|
||||
it "does nothing if the font size has not been changed", ->
|
||||
originalFontSize = atom.config.get('editor.fontSize')
|
||||
|
||||
workspace.resetFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe originalFontSize
|
||||
|
||||
it "resets the font size when the editor's font size changes", ->
|
||||
originalFontSize = atom.config.get('editor.fontSize')
|
||||
|
||||
atom.config.set('editor.fontSize', originalFontSize + 1)
|
||||
workspace.resetFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe originalFontSize
|
||||
atom.config.set('editor.fontSize', originalFontSize - 1)
|
||||
workspace.resetFontSize()
|
||||
expect(atom.config.get('editor.fontSize')).toBe originalFontSize
|
||||
|
||||
describe "::openLicense()", ->
|
||||
it "opens the license as plain-text in a buffer", ->
|
||||
waitsForPromise -> workspace.openLicense()
|
||||
|
||||
@@ -242,10 +242,15 @@ describe "WorkspaceView", ->
|
||||
|
||||
describe "the scrollbar visibility class", ->
|
||||
it "has a class based on the style of the scrollbar", ->
|
||||
style = 'legacy'
|
||||
scrollbarStyle = require 'scrollbar-style'
|
||||
scrollbarStyle.emitValue 'legacy'
|
||||
spyOn(scrollbarStyle, 'getPreferredScrollbarStyle').andCallFake -> style
|
||||
|
||||
atom.workspaceView.element.observeScrollbarStyle()
|
||||
expect(atom.workspaceView).toHaveClass 'scrollbars-visible-always'
|
||||
scrollbarStyle.emitValue 'overlay'
|
||||
|
||||
style = 'overlay'
|
||||
atom.workspaceView.element.observeScrollbarStyle()
|
||||
expect(atom.workspaceView).toHaveClass 'scrollbars-visible-when-scrolling'
|
||||
|
||||
describe "editor font styling", ->
|
||||
|
||||
@@ -151,7 +151,7 @@ class Atom extends Model
|
||||
# Public: A {TooltipManager} instance
|
||||
tooltips: null
|
||||
|
||||
# Experimental: A {NotificationManager} instance
|
||||
# Public: A {NotificationManager} instance
|
||||
notifications: null
|
||||
|
||||
# Public: A {Project} instance
|
||||
@@ -223,6 +223,8 @@ class Atom extends Model
|
||||
@disposables?.dispose()
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@displayWindow() unless @inSpecMode()
|
||||
|
||||
@setBodyPlatformClass()
|
||||
|
||||
@loadTime = null
|
||||
@@ -483,22 +485,27 @@ class Atom extends Model
|
||||
# Extended: Set the full screen state of the current window.
|
||||
setFullScreen: (fullScreen=false) ->
|
||||
ipc.send('call-window-method', 'setFullScreen', fullScreen)
|
||||
if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen")
|
||||
if fullScreen
|
||||
document.body.classList.add("fullscreen")
|
||||
else
|
||||
document.body.classList.remove("fullscreen")
|
||||
|
||||
# Extended: Toggle the full screen state of the current window.
|
||||
toggleFullScreen: ->
|
||||
@setFullScreen(not @isFullScreen())
|
||||
|
||||
# Schedule the window to be shown and focused on the next tick.
|
||||
# Restore the window to its previous dimensions and show it.
|
||||
#
|
||||
# This is done in a next tick to prevent a white flicker from occurring
|
||||
# if called synchronously.
|
||||
displayWindow: ({maximize}={}) ->
|
||||
# Also restores the full screen and maximized state on the next tick to
|
||||
# prevent resize glitches.
|
||||
displayWindow: ->
|
||||
dimensions = @restoreWindowDimensions()
|
||||
@show()
|
||||
|
||||
setImmediate =>
|
||||
@show()
|
||||
@focus()
|
||||
@setFullScreen(true) if @workspace.fullScreen
|
||||
@maximize() if maximize
|
||||
@setFullScreen(true) if @workspace?.fullScreen
|
||||
@maximize() if dimensions?.maximized and process.platform isnt 'darwin'
|
||||
|
||||
# Get the dimensions of this window.
|
||||
#
|
||||
@@ -572,6 +579,13 @@ class Atom extends Model
|
||||
dimensions = @getWindowDimensions()
|
||||
@state.windowDimensions = dimensions if @isValidDimensions(dimensions)
|
||||
|
||||
storeWindowBackground: ->
|
||||
return if @inSpecMode()
|
||||
|
||||
workspaceElement = @views.getView(@workspace)
|
||||
backgroundColor = window.getComputedStyle(workspaceElement)['background-color']
|
||||
window.localStorage.setItem('atom:window-background-color', backgroundColor)
|
||||
|
||||
# Call this method when establishing a real application window.
|
||||
startEditorWindow: ->
|
||||
{safeMode} = @getLoadSettings()
|
||||
@@ -582,7 +596,6 @@ class Atom extends Model
|
||||
CommandInstaller.installApmCommand false, (error) ->
|
||||
console.warn error.message if error?
|
||||
|
||||
dimensions = @restoreWindowDimensions()
|
||||
@loadConfig()
|
||||
@keymaps.loadBundledKeymaps()
|
||||
@themes.loadBaseStylesheets()
|
||||
@@ -602,12 +615,10 @@ class Atom extends Model
|
||||
|
||||
@openInitialEmptyEditorIfNecessary()
|
||||
|
||||
maximize = dimensions?.maximized and process.platform isnt 'darwin'
|
||||
@displayWindow({maximize})
|
||||
|
||||
unloadEditorWindow: ->
|
||||
return if not @project
|
||||
|
||||
@storeWindowBackground()
|
||||
@state.grammars = @grammars.serialize()
|
||||
@state.project = @project.serialize()
|
||||
@state.workspace = @workspace.serialize()
|
||||
@@ -747,7 +758,7 @@ class Atom extends Model
|
||||
# Only reload stylesheets from non-theme packages
|
||||
for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
|
||||
pack.reloadStylesheets?()
|
||||
null
|
||||
return
|
||||
|
||||
# Notify the browser project of the window's current project path
|
||||
watchProjectPath: ->
|
||||
|
||||
@@ -32,11 +32,6 @@ defaultOptions =
|
||||
# Target a version of the regenerator runtime that
|
||||
# supports yield so the transpiled code is cleaner/smaller.
|
||||
'asyncToGenerator'
|
||||
|
||||
# Because Atom is currently packaged with a fork of React v0.11,
|
||||
# it makes sense to use the reactCompat transform so the React
|
||||
# JSX transformer produces pre-v0.12 code.
|
||||
'reactCompat'
|
||||
]
|
||||
|
||||
# Includes support for es7 features listed at:
|
||||
|
||||
@@ -9,11 +9,10 @@ _ = require 'underscore-plus'
|
||||
# and maintain the state of all menu items.
|
||||
module.exports =
|
||||
class ApplicationMenu
|
||||
constructor: (@version) ->
|
||||
constructor: (@version, @autoUpdateManager) ->
|
||||
@windowTemplates = new WeakMap()
|
||||
@setActiveTemplate(@getDefaultTemplate())
|
||||
global.atomApplication.autoUpdateManager.on 'state-changed', (state) =>
|
||||
@showUpdateMenuItem(state)
|
||||
@autoUpdateManager.on 'state-changed', (state) => @showUpdateMenuItem(state)
|
||||
|
||||
# Public: Updates the entire menu with the given keybindings.
|
||||
#
|
||||
@@ -33,7 +32,7 @@ class ApplicationMenu
|
||||
@menu = Menu.buildFromTemplate(_.deepClone(template))
|
||||
Menu.setApplicationMenu(@menu)
|
||||
|
||||
@showUpdateMenuItem(global.atomApplication.autoUpdateManager.getState())
|
||||
@showUpdateMenuItem(@autoUpdateManager.getState())
|
||||
|
||||
# Register a BrowserWindow with this application menu.
|
||||
addWindow: (window) ->
|
||||
@@ -162,8 +161,8 @@ class ApplicationMenu
|
||||
firstKeystroke = keystrokesByCommand[command]?[0]
|
||||
return null unless firstKeystroke
|
||||
|
||||
modifiers = firstKeystroke.split('-')
|
||||
key = modifiers.pop()
|
||||
modifiers = firstKeystroke.split(/-(?=.)/)
|
||||
key = modifiers.pop().toUpperCase().replace('+', 'Plus')
|
||||
|
||||
modifiers = modifiers.map (modifier) ->
|
||||
modifier.replace(/shift/ig, "Shift")
|
||||
@@ -171,5 +170,5 @@ class ApplicationMenu
|
||||
.replace(/ctrl/ig, "Ctrl")
|
||||
.replace(/alt/ig, "Alt")
|
||||
|
||||
keys = modifiers.concat([key.toUpperCase()])
|
||||
keys = modifiers.concat([key])
|
||||
keys.join("+")
|
||||
|
||||
@@ -56,6 +56,7 @@ class AtomApplication
|
||||
atomProtocolHandler: null
|
||||
resourcePath: null
|
||||
version: null
|
||||
quitting: false
|
||||
|
||||
exit: (status) -> app.exit(status)
|
||||
|
||||
@@ -71,8 +72,8 @@ class AtomApplication
|
||||
@pathsToOpen ?= []
|
||||
@windows = []
|
||||
|
||||
@autoUpdateManager = new AutoUpdateManager(@version)
|
||||
@applicationMenu = new ApplicationMenu(@version)
|
||||
@autoUpdateManager = new AutoUpdateManager(@version, options.test)
|
||||
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
|
||||
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
|
||||
|
||||
@listenForArgumentsFromNewProcess()
|
||||
@@ -85,20 +86,26 @@ class AtomApplication
|
||||
else
|
||||
@loadState() or @openPath(options)
|
||||
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, apiPreviewMode, newWindow, specDirectory, logFile}) ->
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, apiPreviewMode, newWindow, specDirectory, logFile, profileStartup}) ->
|
||||
if test
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile, apiPreviewMode})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode})
|
||||
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup})
|
||||
else if urlsToOpen.length > 0
|
||||
@openUrl({urlToOpen, devMode, safeMode, apiPreviewMode}) for urlToOpen in urlsToOpen
|
||||
else
|
||||
@openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode}) # Always open a editor window if this is the first instance of Atom.
|
||||
# Always open a editor window if this is the first instance of Atom.
|
||||
@openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup})
|
||||
|
||||
# Public: Removes the {AtomWindow} from the global window list.
|
||||
removeWindow: (window) ->
|
||||
@windows.splice @windows.indexOf(window), 1
|
||||
@applicationMenu?.enableWindowSpecificItems(false) if @windows.length is 0
|
||||
if @windows.length is 0
|
||||
@applicationMenu?.enableWindowSpecificItems(false)
|
||||
if process.platform in ['win32', 'linux']
|
||||
app.quit()
|
||||
return
|
||||
@saveState() unless window.isSpec or @quitting
|
||||
|
||||
# Public: Adds the {AtomWindow} to the global window list.
|
||||
addWindow: (window) ->
|
||||
@@ -109,10 +116,14 @@ class AtomApplication
|
||||
|
||||
unless window.isSpec
|
||||
focusHandler = => @lastFocusedWindow = window
|
||||
blurHandler = => @saveState()
|
||||
window.browserWindow.on 'focus', focusHandler
|
||||
window.browserWindow.on 'blur', blurHandler
|
||||
window.browserWindow.once 'closed', =>
|
||||
@lastFocusedWindow = null if window is @lastFocusedWindow
|
||||
window.browserWindow.removeListener 'focus', focusHandler
|
||||
window.browserWindow.removeListener 'blur', blurHandler
|
||||
window.browserWindow.webContents.once 'did-finish-load', => @saveState()
|
||||
|
||||
# Creates server to listen for additional atom application launches.
|
||||
#
|
||||
@@ -175,7 +186,7 @@ class AtomApplication
|
||||
@on 'application:report-issue', -> require('shell').openExternal('https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues')
|
||||
@on 'application:search-issues', -> require('shell').openExternal('https://github.com/issues?q=+is%3Aissue+user%3Aatom')
|
||||
|
||||
@on 'application:install-update', -> @autoUpdateManager.install()
|
||||
@on 'application:install-update', => @autoUpdateManager.install()
|
||||
@on 'application:check-for-update', => @autoUpdateManager.check()
|
||||
|
||||
if process.platform is 'darwin'
|
||||
@@ -198,17 +209,15 @@ class AtomApplication
|
||||
@openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
|
||||
@openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md'))
|
||||
|
||||
app.on 'window-all-closed', ->
|
||||
app.quit() if process.platform in ['win32', 'linux']
|
||||
|
||||
app.on 'before-quit', =>
|
||||
@saveState()
|
||||
@quitting = true
|
||||
|
||||
app.on 'will-quit', =>
|
||||
@killAllProcesses()
|
||||
@deleteSocketFile()
|
||||
|
||||
app.on 'will-exit', =>
|
||||
@saveState() unless @windows.every (window) -> window.isSpec
|
||||
@killAllProcesses()
|
||||
@deleteSocketFile()
|
||||
|
||||
@@ -343,9 +352,10 @@ class AtomApplication
|
||||
# :devMode - Boolean to control the opened window's dev mode.
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :apiPreviewMode - Boolean to control the opened window's 1.0 API preview mode.
|
||||
# :profileStartup - Boolean to control creating a profile of the startup time.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window})
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup, window}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, profileStartup, window})
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
@@ -358,7 +368,7 @@ class AtomApplication
|
||||
# :apiPreviewMode - Boolean to control the opened window's 1.0 API preview mode.
|
||||
# :windowDimensions - Object with height and width keys.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, windowDimensions, window}={}) ->
|
||||
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, windowDimensions, profileStartup, window}={}) ->
|
||||
pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen)
|
||||
|
||||
@@ -388,7 +398,7 @@ class AtomApplication
|
||||
|
||||
bootstrapScript ?= require.resolve('../window-bootstrap')
|
||||
resourcePath ?= @resourcePath
|
||||
openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, apiPreviewMode, windowDimensions})
|
||||
openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, apiPreviewMode, windowDimensions, profileStartup})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
||||
@@ -420,14 +430,9 @@ class AtomApplication
|
||||
saveState: ->
|
||||
states = []
|
||||
for window in @windows
|
||||
if loadSettings = window.getLoadSettings()
|
||||
unless loadSettings.isSpec
|
||||
states.push(_.pick(loadSettings,
|
||||
'initialPaths'
|
||||
'devMode'
|
||||
'safeMode'
|
||||
'apiPreviewMode'
|
||||
))
|
||||
unless window.isSpec
|
||||
if loadSettings = window.getLoadSettings()
|
||||
states.push(initialPaths: loadSettings.initialPaths)
|
||||
@storageFolder.store('application.json', states)
|
||||
|
||||
loadState: ->
|
||||
@@ -436,9 +441,9 @@ class AtomApplication
|
||||
@openWithOptions({
|
||||
pathsToOpen: state.initialPaths
|
||||
urlsToOpen: []
|
||||
devMode: state.devMode
|
||||
safeMode: state.safeMode
|
||||
apiPreviewMode: state.apiPreviewMode
|
||||
devMode: @devMode
|
||||
safeMode: @safeMode
|
||||
apiPreviewMode: @apiPreviewMode
|
||||
})
|
||||
true
|
||||
else
|
||||
@@ -484,7 +489,7 @@ class AtomApplication
|
||||
# :specPath - The directory to load specs from.
|
||||
# :safeMode - A Boolean that, if true, won't run specs from ~/.atom/packages
|
||||
# and ~/.atom/dev/packages, defaults to false.
|
||||
runSpecs: ({exitWhenDone, resourcePath, specDirectory, logFile, safeMode}) ->
|
||||
runSpecs: ({exitWhenDone, resourcePath, specDirectory, logFile, safeMode, apiPreviewMode}) ->
|
||||
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
|
||||
resourcePath = @resourcePath
|
||||
|
||||
@@ -496,7 +501,8 @@ class AtomApplication
|
||||
isSpec = true
|
||||
devMode = true
|
||||
safeMode ?= false
|
||||
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile, safeMode})
|
||||
apiPreviewMode ?= false
|
||||
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile, safeMode, apiPreviewMode})
|
||||
|
||||
runBenchmarks: ({exitWhenDone, specDirectory}={}) ->
|
||||
try
|
||||
@@ -514,13 +520,15 @@ class AtomApplication
|
||||
return {pathToOpen} unless pathToOpen
|
||||
return {pathToOpen} if fs.existsSync(pathToOpen)
|
||||
|
||||
pathToOpen = pathToOpen.replace(/[:\s]+$/, '')
|
||||
|
||||
[fileToOpen, initialLine, initialColumn] = path.basename(pathToOpen).split(':')
|
||||
return {pathToOpen} unless initialLine
|
||||
return {pathToOpen} unless parseInt(initialLine) > 0
|
||||
return {pathToOpen} unless parseInt(initialLine) >= 0
|
||||
|
||||
# Convert line numbers to a base of 0
|
||||
initialLine -= 1 if initialLine
|
||||
initialColumn -= 1 if initialColumn
|
||||
initialLine = Math.max(0, initialLine - 1) if initialLine
|
||||
initialColumn = Math.max(0, initialColumn - 1) if initialColumn
|
||||
pathToOpen = path.join(path.dirname(pathToOpen), fileToOpen)
|
||||
{pathToOpen, initialLine, initialColumn}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports =
|
||||
class AutoUpdateManager
|
||||
_.extend @prototype, EventEmitter.prototype
|
||||
|
||||
constructor: (@version) ->
|
||||
constructor: (@version, @testMode) ->
|
||||
@state = IdleState
|
||||
if process.platform is 'win32'
|
||||
# Squirrel for Windows can't handle query params
|
||||
@@ -53,7 +53,7 @@ class AutoUpdateManager
|
||||
@emitUpdateAvailableEvent(@getWindows()...)
|
||||
|
||||
# Only released versions should check for updates.
|
||||
@check(hidePopups: true) unless /\w{7}/.test(@version)
|
||||
@scheduleUpdateCheck() unless /\w{7}/.test(@version)
|
||||
|
||||
switch process.platform
|
||||
when 'win32'
|
||||
@@ -75,15 +75,21 @@ class AutoUpdateManager
|
||||
getState: ->
|
||||
@state
|
||||
|
||||
scheduleUpdateCheck: ->
|
||||
checkForUpdates = => @check(hidePopups: true)
|
||||
fourHours = 1000 * 60 * 60 * 4
|
||||
setInterval(checkForUpdates, fourHours)
|
||||
checkForUpdates()
|
||||
|
||||
check: ({hidePopups}={}) ->
|
||||
unless hidePopups
|
||||
autoUpdater.once 'update-not-available', @onUpdateNotAvailable
|
||||
autoUpdater.once 'error', @onUpdateError
|
||||
|
||||
autoUpdater.checkForUpdates()
|
||||
autoUpdater.checkForUpdates() unless @testMode
|
||||
|
||||
install: ->
|
||||
autoUpdater.quitAndInstall()
|
||||
autoUpdater.quitAndInstall() unless @testMode
|
||||
|
||||
onUpdateNotAvailable: =>
|
||||
autoUpdater.removeListener 'error', @onUpdateError
|
||||
|
||||
@@ -110,6 +110,7 @@ parseCommandLine = ->
|
||||
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.')
|
||||
options.alias('l', 'log-file').string('l').describe('l', 'Log all output to file.')
|
||||
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
|
||||
options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.')
|
||||
options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.')
|
||||
options.alias('s', 'spec-directory').string('s').describe('s', 'Set the directory from which to run package specs (default: Atom\'s spec directory).')
|
||||
options.boolean('safe').describe('safe', 'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.')
|
||||
@@ -132,13 +133,13 @@ parseCommandLine = ->
|
||||
safeMode = args['safe']
|
||||
apiPreviewMode = args['one']
|
||||
pathsToOpen = args._
|
||||
pathsToOpen = [executedFrom] if executedFrom and pathsToOpen.length is 0
|
||||
test = args['test']
|
||||
specDirectory = args['spec-directory']
|
||||
newWindow = args['new-window']
|
||||
pidToKillWhenClosed = args['pid'] if args['wait']
|
||||
logFile = args['log-file']
|
||||
socketPath = args['socket-path']
|
||||
profileStartup = args['profile-startup']
|
||||
|
||||
if args['resource-path']
|
||||
devMode = true
|
||||
@@ -165,6 +166,6 @@ parseCommandLine = ->
|
||||
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed,
|
||||
devMode, apiPreviewMode, safeMode, newWindow, specDirectory, logFile,
|
||||
socketPath}
|
||||
socketPath, profileStartup}
|
||||
|
||||
start()
|
||||
|
||||
@@ -48,6 +48,7 @@ class BufferedProcess
|
||||
constructor: ({command, args, options, stdout, stderr, exit}={}) ->
|
||||
@emitter = new Emitter
|
||||
options ?= {}
|
||||
@command = command
|
||||
# Related to joyent/node#2318
|
||||
if process.platform is 'win32'
|
||||
# Quote all arguments and escapes inner quotes
|
||||
@@ -69,50 +70,12 @@ class BufferedProcess
|
||||
cmdArgs = ['/s', '/c', "\"#{cmdArgs.join(' ')}\""]
|
||||
cmdOptions = _.clone(options)
|
||||
cmdOptions.windowsVerbatimArguments = true
|
||||
@process = ChildProcess.spawn(@getCmdPath(), cmdArgs, cmdOptions)
|
||||
@spawn(@getCmdPath(), cmdArgs, cmdOptions)
|
||||
else
|
||||
@process = ChildProcess.spawn(command, args, options)
|
||||
@spawn(command, args, options)
|
||||
|
||||
@killed = false
|
||||
|
||||
stdoutClosed = true
|
||||
stderrClosed = true
|
||||
processExited = true
|
||||
exitCode = 0
|
||||
triggerExitCallback = ->
|
||||
return if @killed
|
||||
if stdoutClosed and stderrClosed and processExited
|
||||
exit?(exitCode)
|
||||
|
||||
if stdout
|
||||
stdoutClosed = false
|
||||
@bufferStream @process.stdout, stdout, ->
|
||||
stdoutClosed = true
|
||||
triggerExitCallback()
|
||||
|
||||
if stderr
|
||||
stderrClosed = false
|
||||
@bufferStream @process.stderr, stderr, ->
|
||||
stderrClosed = true
|
||||
triggerExitCallback()
|
||||
|
||||
if exit
|
||||
processExited = false
|
||||
@process.on 'exit', (code) ->
|
||||
exitCode = code
|
||||
processExited = true
|
||||
triggerExitCallback()
|
||||
|
||||
@process.on 'error', (error) =>
|
||||
handled = false
|
||||
handle = -> handled = true
|
||||
|
||||
@emitter.emit 'will-throw-error', {error, handle}
|
||||
|
||||
if error.code is 'ENOENT' and error.syscall.indexOf('spawn') is 0
|
||||
error = new Error("Failed to spawn command `#{command}`. Make sure `#{command}` is installed and on your PATH", error.path)
|
||||
error.name = 'BufferedProcessError'
|
||||
|
||||
throw error unless handled
|
||||
@handleEvents(stdout, stderr, exit)
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
@@ -164,6 +127,8 @@ class BufferedProcess
|
||||
# This is required since killing the cmd.exe does not terminate child
|
||||
# processes.
|
||||
killOnWindows: ->
|
||||
return unless @process?
|
||||
|
||||
parentPid = @process.pid
|
||||
cmd = 'wmic'
|
||||
args = [
|
||||
@@ -174,7 +139,12 @@ class BufferedProcess
|
||||
'processid'
|
||||
]
|
||||
|
||||
wmicProcess = ChildProcess.spawn(cmd, args)
|
||||
try
|
||||
wmicProcess = ChildProcess.spawn(cmd, args)
|
||||
catch spawnError
|
||||
@killProcess()
|
||||
return
|
||||
|
||||
wmicProcess.on 'error', -> # ignore errors
|
||||
output = ''
|
||||
wmicProcess.stdout.on 'data', (data) -> output += data
|
||||
@@ -220,3 +190,55 @@ class BufferedProcess
|
||||
@killProcess()
|
||||
|
||||
undefined
|
||||
|
||||
spawn: (command, args, options) ->
|
||||
try
|
||||
@process = ChildProcess.spawn(command, args, options)
|
||||
catch spawnError
|
||||
process.nextTick => @handleError(spawnError)
|
||||
|
||||
handleEvents: (stdout, stderr, exit) ->
|
||||
return unless @process?
|
||||
|
||||
stdoutClosed = true
|
||||
stderrClosed = true
|
||||
processExited = true
|
||||
exitCode = 0
|
||||
triggerExitCallback = ->
|
||||
return if @killed
|
||||
if stdoutClosed and stderrClosed and processExited
|
||||
exit?(exitCode)
|
||||
|
||||
if stdout
|
||||
stdoutClosed = false
|
||||
@bufferStream @process.stdout, stdout, ->
|
||||
stdoutClosed = true
|
||||
triggerExitCallback()
|
||||
|
||||
if stderr
|
||||
stderrClosed = false
|
||||
@bufferStream @process.stderr, stderr, ->
|
||||
stderrClosed = true
|
||||
triggerExitCallback()
|
||||
|
||||
if exit
|
||||
processExited = false
|
||||
@process.on 'exit', (code) ->
|
||||
exitCode = code
|
||||
processExited = true
|
||||
triggerExitCallback()
|
||||
|
||||
@process.on 'error', (error) => @handleError(error)
|
||||
return
|
||||
|
||||
handleError: (error) ->
|
||||
handled = false
|
||||
handle = -> handled = true
|
||||
|
||||
@emitter.emit 'will-throw-error', {error, handle}
|
||||
|
||||
if error.code is 'ENOENT' and error.syscall.indexOf('spawn') is 0
|
||||
error = new Error("Failed to spawn command `#{@command}`. Make sure `#{@command}` is installed and on your PATH", error.path)
|
||||
error.name = 'BufferedProcessError'
|
||||
|
||||
throw error unless handled
|
||||
|
||||
@@ -98,9 +98,6 @@ module.exports =
|
||||
type: ['string', 'null']
|
||||
|
||||
# These can be used as globals or scoped, thus defaults.
|
||||
completions:
|
||||
type: ['array', 'object']
|
||||
default: []
|
||||
fontFamily:
|
||||
type: 'string'
|
||||
default: ''
|
||||
@@ -108,6 +105,7 @@ module.exports =
|
||||
type: 'integer'
|
||||
default: 16
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
lineHeight:
|
||||
type: ['string', 'number']
|
||||
default: 1.3
|
||||
|
||||
@@ -105,9 +105,9 @@ class ContextMenuManager
|
||||
# Detect deprecated file path as first argument
|
||||
if itemsBySelector? and typeof itemsBySelector isnt 'object'
|
||||
Grim.deprecate """
|
||||
ContextMenuManager::add has changed to take a single object as its
|
||||
`ContextMenuManager::add` has changed to take a single object as its
|
||||
argument. Please see
|
||||
https://atom.io/docs/api/latest/ContextMenuManager for more info.
|
||||
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format for more info.
|
||||
"""
|
||||
itemsBySelector = arguments[1]
|
||||
devMode = arguments[2]?.devMode
|
||||
@@ -116,9 +116,9 @@ class ContextMenuManager
|
||||
for key, value of itemsBySelector
|
||||
unless _.isArray(value)
|
||||
Grim.deprecate """
|
||||
ContextMenuManager::add has changed to take a single object as its
|
||||
`ContextMenuManager::add` has changed to take a single object as its
|
||||
argument. Please see
|
||||
https://atom.io/docs/api/latest/ContextMenuManager for more info.
|
||||
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format for more info.
|
||||
"""
|
||||
itemsBySelector = @convertLegacyItemsBySelector(itemsBySelector, devMode)
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ class CustomGutterComponent
|
||||
|
||||
@domNode = atom.views.getView(@gutter)
|
||||
@decorationsNode = @domNode.firstChild
|
||||
# Clear the contents in case the domNode is being reused.
|
||||
@decorationsNode.innerHTML = ''
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
@@ -27,13 +29,14 @@ class CustomGutterComponent
|
||||
@domNode.style.removeProperty('display')
|
||||
@visible = true
|
||||
|
||||
# `state` is a subset of the TextEditorPresenter state that is specific
|
||||
# to this line number gutter.
|
||||
updateSync: (state) ->
|
||||
@oldDimensionsAndBackgroundState ?= {}
|
||||
newDimensionsAndBackgroundState = state.gutters
|
||||
setDimensionsAndBackground(@oldDimensionsAndBackgroundState, newDimensionsAndBackgroundState, @decorationsNode)
|
||||
setDimensionsAndBackground(@oldDimensionsAndBackgroundState, state.styles, @decorationsNode)
|
||||
|
||||
@oldDecorationPositionState ?= {}
|
||||
decorationState = state.gutters.customDecorations[@gutter.name]
|
||||
decorationState = state.content
|
||||
|
||||
updatedDecorationIds = new Set
|
||||
for decorationId, decorationInfo of decorationState
|
||||
|
||||
@@ -201,6 +201,6 @@ if Grim.includeDeprecatedAPIs
|
||||
Grim.deprecate 'Use Decoration::getProperties instead'
|
||||
@getProperties()
|
||||
|
||||
Decoration::update = -> (newProperties) ->
|
||||
Decoration::update = (newProperties) ->
|
||||
Grim.deprecate 'Use Decoration::setProperties instead'
|
||||
@setProperties(newProperties)
|
||||
|
||||
@@ -24,13 +24,13 @@ class DisplayBuffer extends Model
|
||||
horizontalScrollMargin: 6
|
||||
scopedCharacterWidthsChangeCount: 0
|
||||
|
||||
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, @invisibles}={}) ->
|
||||
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles}={}) ->
|
||||
super
|
||||
|
||||
@emitter = new Emitter
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, @invisibles})
|
||||
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, ignoreInvisibles})
|
||||
@buffer = @tokenizedBuffer.buffer
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@@ -39,9 +39,9 @@ class DisplayBuffer extends Model
|
||||
@decorationsByMarkerId = {}
|
||||
@disposables.add @tokenizedBuffer.observeGrammar @subscribeToScopedConfigSettings
|
||||
@disposables.add @tokenizedBuffer.onDidChange @handleTokenizedBufferChange
|
||||
@disposables.add @buffer.onDidUpdateMarkers @handleBufferMarkersUpdated
|
||||
@disposables.add @buffer.onDidCreateMarker @handleBufferMarkerCreated
|
||||
@updateAllScreenLines()
|
||||
@foldMarkerAttributes = Object.freeze({class: 'fold', displayBufferId: @id})
|
||||
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
|
||||
|
||||
subscribeToScopedConfigSettings: =>
|
||||
@@ -86,14 +86,13 @@ class DisplayBuffer extends Model
|
||||
scrollTop: @scrollTop
|
||||
scrollLeft: @scrollLeft
|
||||
tokenizedBuffer: @tokenizedBuffer.serialize()
|
||||
invisibles: _.clone(@invisibles)
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer)
|
||||
params
|
||||
|
||||
copy: ->
|
||||
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @invisibles})
|
||||
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()})
|
||||
newDisplayBuffer.setScrollTop(@getScrollTop())
|
||||
newDisplayBuffer.setScrollLeft(@getScrollLeft())
|
||||
|
||||
@@ -153,12 +152,12 @@ class DisplayBuffer extends Model
|
||||
@emitter.on 'did-update-markers', callback
|
||||
|
||||
emitDidChange: (eventProperties, refreshMarkers=true) ->
|
||||
if refreshMarkers
|
||||
@pauseMarkerChangeEvents()
|
||||
@refreshMarkerScreenPositions()
|
||||
@emit 'changed', eventProperties if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change', eventProperties
|
||||
@resumeMarkerChangeEvents()
|
||||
if refreshMarkers
|
||||
@refreshMarkerScreenPositions()
|
||||
@emit 'markers-updated' if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-update-markers'
|
||||
|
||||
updateWrappedScreenLines: ->
|
||||
start = 0
|
||||
@@ -428,8 +427,8 @@ class DisplayBuffer extends Model
|
||||
setTabLength: (tabLength) ->
|
||||
@tokenizedBuffer.setTabLength(tabLength)
|
||||
|
||||
setInvisibles: (@invisibles) ->
|
||||
@tokenizedBuffer.setInvisibles(@invisibles)
|
||||
setIgnoreInvisibles: (ignoreInvisibles) ->
|
||||
@tokenizedBuffer.setIgnoreInvisibles(ignoreInvisibles)
|
||||
|
||||
setSoftWrapped: (softWrapped) ->
|
||||
if softWrapped isnt @softWrapped
|
||||
@@ -886,7 +885,7 @@ class DisplayBuffer extends Model
|
||||
getDecorations: (propertyFilter) ->
|
||||
allDecorations = []
|
||||
for markerId, decorations of @decorationsByMarkerId
|
||||
allDecorations = allDecorations.concat(decorations) if decorations?
|
||||
allDecorations.push(decorations...) if decorations?
|
||||
if propertyFilter?
|
||||
allDecorations = allDecorations.filter (decoration) ->
|
||||
for key, value of propertyFilter
|
||||
@@ -1075,17 +1074,11 @@ class DisplayBuffer extends Model
|
||||
findFoldMarkers: (attributes) ->
|
||||
@buffer.findMarkers(@getFoldMarkerAttributes(attributes))
|
||||
|
||||
getFoldMarkerAttributes: (attributes={}) ->
|
||||
_.extend(attributes, class: 'fold', displayBufferId: @id)
|
||||
|
||||
pauseMarkerChangeEvents: ->
|
||||
marker.pauseChangeEvents() for marker in @getMarkers()
|
||||
return
|
||||
|
||||
resumeMarkerChangeEvents: ->
|
||||
marker.resumeChangeEvents() for marker in @getMarkers()
|
||||
@emit 'markers-updated' if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-update-markers'
|
||||
getFoldMarkerAttributes: (attributes) ->
|
||||
if attributes
|
||||
_.extend(attributes, @foldMarkerAttributes)
|
||||
else
|
||||
@foldMarkerAttributes
|
||||
|
||||
refreshMarkerScreenPositions: ->
|
||||
for marker in @getMarkers()
|
||||
@@ -1109,7 +1102,7 @@ class DisplayBuffer extends Model
|
||||
|
||||
handleTokenizedBufferChange: (tokenizedBufferChange) =>
|
||||
{start, end, delta, bufferChange} = tokenizedBufferChange
|
||||
@updateScreenLines(start, end + 1, delta, delayChangeEvent: bufferChange?)
|
||||
@updateScreenLines(start, end + 1, delta, refreshMarkers: false)
|
||||
@setScrollTop(Math.min(@getScrollTop(), @getMaxScrollTop())) if delta < 0
|
||||
|
||||
updateScreenLines: (startBufferRow, endBufferRow, bufferDelta=0, options={}) ->
|
||||
@@ -1120,7 +1113,7 @@ class DisplayBuffer extends Model
|
||||
{screenLines, regions} = @buildScreenLines(startBufferRow, endBufferRow + bufferDelta)
|
||||
screenDelta = screenLines.length - (endScreenRow - startScreenRow)
|
||||
|
||||
@screenLines[startScreenRow...endScreenRow] = screenLines
|
||||
_.spliceWithArray(@screenLines, startScreenRow, endScreenRow - startScreenRow, screenLines, 10000)
|
||||
@rowMap.spliceRegions(startBufferRow, endBufferRow - startBufferRow, regions)
|
||||
@findMaxLineLength(startScreenRow, endScreenRow, screenLines, screenDelta)
|
||||
|
||||
@@ -1132,11 +1125,7 @@ class DisplayBuffer extends Model
|
||||
screenDelta: screenDelta
|
||||
bufferDelta: bufferDelta
|
||||
|
||||
if options.delayChangeEvent
|
||||
@pauseMarkerChangeEvents()
|
||||
@pendingChangeEvent = changeEvent
|
||||
else
|
||||
@emitDidChange(changeEvent, options.refreshMarkers)
|
||||
@emitDidChange(changeEvent, options.refreshMarkers)
|
||||
|
||||
buildScreenLines: (startBufferRow, endBufferRow) ->
|
||||
screenLines = []
|
||||
@@ -1216,11 +1205,6 @@ class DisplayBuffer extends Model
|
||||
@scrollWidth += 1 unless @isSoftWrapped()
|
||||
@setScrollLeft(Math.min(@getScrollLeft(), @getMaxScrollLeft()))
|
||||
|
||||
handleBufferMarkersUpdated: =>
|
||||
if event = @pendingChangeEvent
|
||||
@pendingChangeEvent = null
|
||||
@emitDidChange(event, false)
|
||||
|
||||
handleBufferMarkerCreated: (textBufferMarker) =>
|
||||
@createFoldForMarker(textBufferMarker) if textBufferMarker.matchesParams(@getFoldMarkerAttributes())
|
||||
if marker = @getMarker(textBufferMarker.id)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
_ = require 'underscore-plus'
|
||||
CustomGutterComponent = require './custom-gutter-component'
|
||||
LineNumberGutterComponent = require './line-number-gutter-component'
|
||||
|
||||
@@ -26,11 +27,11 @@ class GutterContainerComponent
|
||||
updateSync: (state) ->
|
||||
# The GutterContainerComponent expects the gutters to be sorted in the order
|
||||
# they should appear.
|
||||
newState = state.gutters.sortedDescriptions
|
||||
newState = state.gutters
|
||||
|
||||
newGutterComponents = []
|
||||
newGutterComponentsByGutterName = {}
|
||||
for {gutter, visible} in newState
|
||||
for {gutter, visible, styles, content} in newState
|
||||
gutterComponent = @gutterComponentsByGutterName[gutter.name]
|
||||
if not gutterComponent
|
||||
if gutter.name is 'line-number'
|
||||
@@ -38,8 +39,20 @@ class GutterContainerComponent
|
||||
@lineNumberGutterComponent = gutterComponent
|
||||
else
|
||||
gutterComponent = new CustomGutterComponent({gutter})
|
||||
|
||||
if visible then gutterComponent.showNode() else gutterComponent.hideNode()
|
||||
gutterComponent.updateSync(state)
|
||||
# Pass the gutter only the state that it needs.
|
||||
if gutter.name is 'line-number'
|
||||
# For ease of use in the line number gutter component, set the shared
|
||||
# 'styles' as a field under the 'content'.
|
||||
gutterSubstate = _.clone(content)
|
||||
gutterSubstate.styles = styles
|
||||
else
|
||||
# Custom gutter 'content' is keyed on gutter name, so we cannot set
|
||||
# 'styles' as a subfield directly under it.
|
||||
gutterSubstate = {content, styles}
|
||||
gutterComponent.updateSync(gutterSubstate)
|
||||
|
||||
newGutterComponents.push({
|
||||
name: gutter.name,
|
||||
component: gutterComponent,
|
||||
|
||||
@@ -13,6 +13,7 @@ class LineNumberGutterComponent
|
||||
|
||||
@domNode = atom.views.getView(@gutter)
|
||||
@lineNumbersNode = @domNode.firstChild
|
||||
@lineNumbersNode.innerHTML = ''
|
||||
|
||||
@domNode.addEventListener 'click', @onClick
|
||||
@domNode.addEventListener 'mousedown', @onMouseDown
|
||||
@@ -30,19 +31,25 @@ class LineNumberGutterComponent
|
||||
@domNode.style.removeProperty('display')
|
||||
@visible = true
|
||||
|
||||
# `state` is a subset of the TextEditorPresenter state that is specific
|
||||
# to this line number gutter.
|
||||
updateSync: (state) ->
|
||||
@newState = state.gutters.lineNumberGutter
|
||||
@oldState ?= {lineNumbers: {}}
|
||||
@newState = state
|
||||
@oldState ?=
|
||||
lineNumbers: {}
|
||||
styles: {}
|
||||
|
||||
@appendDummyLineNumber() unless @dummyLineNumberNode?
|
||||
|
||||
newDimensionsAndBackgroundState = state.gutters
|
||||
setDimensionsAndBackground(@oldState, newDimensionsAndBackgroundState, @lineNumbersNode)
|
||||
setDimensionsAndBackground(@oldState.styles, @newState.styles, @lineNumbersNode)
|
||||
|
||||
if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits
|
||||
@updateDummyLineNumber()
|
||||
node.remove() for id, node of @lineNumberNodesById
|
||||
@oldState = {maxLineNumberDigits: @newState.maxLineNumberDigits, lineNumbers: {}}
|
||||
@oldState =
|
||||
maxLineNumberDigits: @newState.maxLineNumberDigits
|
||||
lineNumbers: {}
|
||||
styles: {}
|
||||
@lineNumberNodesById = {}
|
||||
|
||||
@updateLineNumbers()
|
||||
|
||||
@@ -48,7 +48,6 @@ class Marker
|
||||
oldTailBufferPosition: null
|
||||
oldTailScreenPosition: null
|
||||
wasValid: true
|
||||
deferredChangeEvents: null
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
@@ -332,11 +331,11 @@ class Marker
|
||||
newTailScreenPosition = @getTailScreenPosition()
|
||||
isValid = @isValid()
|
||||
|
||||
return if _.isEqual(isValid, @wasValid) and
|
||||
_.isEqual(newHeadBufferPosition, @oldHeadBufferPosition) and
|
||||
_.isEqual(newHeadScreenPosition, @oldHeadScreenPosition) and
|
||||
_.isEqual(newTailBufferPosition, @oldTailBufferPosition) and
|
||||
_.isEqual(newTailScreenPosition, @oldTailScreenPosition)
|
||||
return if isValid is @wasValid and
|
||||
newHeadBufferPosition.isEqual(@oldHeadBufferPosition) and
|
||||
newHeadScreenPosition.isEqual(@oldHeadScreenPosition) and
|
||||
newTailBufferPosition.isEqual(@oldTailBufferPosition) and
|
||||
newTailScreenPosition.isEqual(@oldTailScreenPosition)
|
||||
|
||||
changeEvent = {
|
||||
@oldHeadScreenPosition, newHeadScreenPosition,
|
||||
@@ -347,11 +346,8 @@ class Marker
|
||||
isValid
|
||||
}
|
||||
|
||||
if @deferredChangeEvents?
|
||||
@deferredChangeEvents.push(changeEvent)
|
||||
else
|
||||
@emit 'changed', changeEvent if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change', changeEvent
|
||||
@emit 'changed', changeEvent if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change', changeEvent
|
||||
|
||||
@oldHeadBufferPosition = newHeadBufferPosition
|
||||
@oldHeadScreenPosition = newHeadScreenPosition
|
||||
@@ -359,18 +355,6 @@ class Marker
|
||||
@oldTailScreenPosition = newTailScreenPosition
|
||||
@wasValid = isValid
|
||||
|
||||
pauseChangeEvents: ->
|
||||
@deferredChangeEvents = []
|
||||
|
||||
resumeChangeEvents: ->
|
||||
if deferredChangeEvents = @deferredChangeEvents
|
||||
@deferredChangeEvents = null
|
||||
|
||||
for event in deferredChangeEvents
|
||||
@emit 'changed', event if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change', event
|
||||
return
|
||||
|
||||
getPixelRange: ->
|
||||
@displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{Emitter, Disposable} = require 'event-kit'
|
||||
Notification = require '../src/notification'
|
||||
|
||||
# Experimental: Allows messaging the user. This will likely change, dont use
|
||||
# quite yet!
|
||||
# Public: A notification manager used to create {Notification}s to be shown
|
||||
# to the user.
|
||||
module.exports =
|
||||
class NotificationManager
|
||||
constructor: ->
|
||||
@@ -13,6 +13,12 @@ class NotificationManager
|
||||
Section: Events
|
||||
###
|
||||
|
||||
# Public: Invoke the given callback after a notification has been added.
|
||||
#
|
||||
# * `callback` {Function} to be called after the notification is added.
|
||||
# * `notification` The {Notification} that was added.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidAddNotification: (callback) ->
|
||||
@emitter.on 'did-add-notification', callback
|
||||
|
||||
@@ -20,18 +26,43 @@ class NotificationManager
|
||||
Section: Adding Notifications
|
||||
###
|
||||
|
||||
# Public: Add a success notification.
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` An options {Object} with optional keys such as:
|
||||
# * `detail` A {String} with additional details about the notification
|
||||
addSuccess: (message, options) ->
|
||||
@addNotification(new Notification('success', message, options))
|
||||
|
||||
# Public: Add an informational notification.
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` An options {Object} with optional keys such as:
|
||||
# * `detail` A {String} with additional details about the notification
|
||||
addInfo: (message, options) ->
|
||||
@addNotification(new Notification('info', message, options))
|
||||
|
||||
# Public: Add a warning notification.
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` An options {Object} with optional keys such as:
|
||||
# * `detail` A {String} with additional details about the notification
|
||||
addWarning: (message, options) ->
|
||||
@addNotification(new Notification('warning', message, options))
|
||||
|
||||
# Public: Add an error notification.
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` An options {Object} with optional keys such as:
|
||||
# * `detail` A {String} with additional details about the notification
|
||||
addError: (message, options) ->
|
||||
@addNotification(new Notification('error', message, options))
|
||||
|
||||
# Public: Add a fatal error notification.
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` An options {Object} with optional keys such as:
|
||||
# * `detail` A {String} with additional details about the notification
|
||||
addFatalError: (message, options) ->
|
||||
@addNotification(new Notification('fatal', message, options))
|
||||
|
||||
@@ -47,7 +78,10 @@ class NotificationManager
|
||||
Section: Getting Notifications
|
||||
###
|
||||
|
||||
getNotifications: -> @notifications
|
||||
# Public: Get all the notifications.
|
||||
#
|
||||
# Returns an {Array} of {Notifications}s.
|
||||
getNotifications: -> @notifications.slice()
|
||||
|
||||
###
|
||||
Section: Managing Notifications
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{Emitter} = require 'event-kit'
|
||||
|
||||
# Experimental: This will likely change, do not use.
|
||||
# Public: A notification to the user containing a message and type.
|
||||
module.exports =
|
||||
class Notification
|
||||
constructor: (@type, @message, @options={}) ->
|
||||
@@ -18,8 +18,10 @@ class Notification
|
||||
|
||||
getOptions: -> @options
|
||||
|
||||
# Public: Retrieves the {String} type.
|
||||
getType: -> @type
|
||||
|
||||
# Public: Retrieves the {String} message.
|
||||
getMessage: -> @message
|
||||
|
||||
getTimestamp: -> @timestamp
|
||||
|
||||
@@ -303,6 +303,9 @@ class PackageManager
|
||||
# of the first package isn't skewed by being the first to require atom
|
||||
require '../exports/atom'
|
||||
|
||||
# TODO: remove after a few atom versions.
|
||||
@uninstallAutocompletePlus()
|
||||
|
||||
packagePaths = @getAvailablePackagePaths()
|
||||
packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath))
|
||||
packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath)
|
||||
@@ -409,6 +412,39 @@ class PackageManager
|
||||
message = "Failed to load the #{path.basename(packagePath)} package"
|
||||
atom.notifications.addError(message, {stack, detail, dismissable: true})
|
||||
|
||||
# TODO: remove these autocomplete-plus specific helpers after a few versions.
|
||||
uninstallAutocompletePlus: ->
|
||||
packageDir = null
|
||||
devDir = path.join("dev", "packages")
|
||||
for packageDirPath in @packageDirPaths
|
||||
if not packageDirPath.endsWith(devDir)
|
||||
packageDir = packageDirPath
|
||||
break
|
||||
|
||||
if packageDir?
|
||||
dirsToRemove = [
|
||||
path.join(packageDir, 'autocomplete-plus')
|
||||
path.join(packageDir, 'autocomplete-atom-api')
|
||||
path.join(packageDir, 'autocomplete-css')
|
||||
path.join(packageDir, 'autocomplete-html')
|
||||
path.join(packageDir, 'autocomplete-snippets')
|
||||
]
|
||||
for dirToRemove in dirsToRemove
|
||||
@uninstallDirectory(dirToRemove)
|
||||
return
|
||||
|
||||
uninstallDirectory: (directory) ->
|
||||
symlinkPromise = new Promise (resolve) ->
|
||||
fs.isSymbolicLink directory, (isSymLink) -> resolve(isSymLink)
|
||||
|
||||
dirPromise = new Promise (resolve) ->
|
||||
fs.isDirectory directory, (isDir) -> resolve(isDir)
|
||||
|
||||
Promise.all([symlinkPromise, dirPromise]).then (values) ->
|
||||
[isSymLink, isDir] = values
|
||||
if not isSymLink and isDir
|
||||
fs.remove directory, ->
|
||||
|
||||
if Grim.includeDeprecatedAPIs
|
||||
EmitterMixin = require('emissary').Emitter
|
||||
EmitterMixin.includeInto(PackageManager)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
path = require 'path'
|
||||
normalizePackageData = null
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
async = require 'async'
|
||||
@@ -24,6 +25,13 @@ class Package
|
||||
@resourcePathWithTrailingSlash ?= "#{atom.packages.resourcePath}#{path.sep}"
|
||||
packagePath?.startsWith(@resourcePathWithTrailingSlash)
|
||||
|
||||
@normalizeMetadata: (metadata) ->
|
||||
unless metadata?._id
|
||||
normalizePackageData ?= require 'normalize-package-data'
|
||||
normalizePackageData(metadata)
|
||||
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
|
||||
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')
|
||||
|
||||
@loadMetadata: (packagePath, ignoreErrors=false) ->
|
||||
packageName = path.basename(packagePath)
|
||||
if @isBundledPackagePath(packagePath)
|
||||
@@ -32,8 +40,10 @@ class Package
|
||||
if metadataPath = CSON.resolve(path.join(packagePath, 'package'))
|
||||
try
|
||||
metadata = CSON.readFileSync(metadataPath)
|
||||
@normalizeMetadata(metadata)
|
||||
catch error
|
||||
throw error unless ignoreErrors
|
||||
|
||||
metadata ?= {}
|
||||
metadata.name = packageName
|
||||
|
||||
@@ -161,9 +171,9 @@ class Package
|
||||
if @mainModule.config? and typeof @mainModule.config is 'object'
|
||||
atom.config.setSchema @name, {type: 'object', properties: @mainModule.config}
|
||||
else if includeDeprecatedAPIs and @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object'
|
||||
deprecate """Use a config schema instead. See the configuration section
|
||||
deprecate("""Use a config schema instead. See the configuration section
|
||||
of https://atom.io/docs/latest/hacking-atom-package-word-count and
|
||||
https://atom.io/docs/api/latest/Config for more details"""
|
||||
https://atom.io/docs/api/latest/Config for more details""", {packageName: @name})
|
||||
atom.config.setDefaults(@name, @mainModule.configDefaults)
|
||||
@mainModule.activateConfig?()
|
||||
@configActivated = true
|
||||
@@ -191,7 +201,20 @@ class Package
|
||||
|
||||
for [menuPath, map] in @menus when map['context-menu']?
|
||||
try
|
||||
@activationDisposables.add(atom.contextMenu.add(map['context-menu']))
|
||||
itemsBySelector = map['context-menu']
|
||||
|
||||
if includeDeprecatedAPIs
|
||||
# Detect deprecated format for items object
|
||||
for key, value of itemsBySelector
|
||||
unless _.isArray(value)
|
||||
deprecate("""
|
||||
The context menu CSON format has changed. Please see
|
||||
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format
|
||||
for more info.
|
||||
""", {packageName: @name})
|
||||
itemsBySelector = atom.contextMenu.convertLegacyItemsBySelector(itemsBySelector)
|
||||
|
||||
@activationDisposables.add(atom.contextMenu.add(itemsBySelector))
|
||||
catch error
|
||||
if error.code is 'EBADSELECTOR'
|
||||
error.message += " in #{menuPath}"
|
||||
@@ -465,7 +488,7 @@ class Package
|
||||
@activationCommands[selector].push(commands...)
|
||||
|
||||
if includeDeprecatedAPIs and @metadata.activationEvents?
|
||||
deprecate """
|
||||
deprecate("""
|
||||
Use `activationCommands` instead of `activationEvents` in your package.json
|
||||
Commands should be grouped by selector as follows:
|
||||
```json
|
||||
@@ -474,7 +497,7 @@ class Package
|
||||
"atom-text-editor": ["foo:quux"]
|
||||
}
|
||||
```
|
||||
"""
|
||||
""", {packageName: @name})
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
for eventName in @metadata.activationEvents
|
||||
@activationCommands['atom-workspace'] ?= []
|
||||
|
||||
@@ -12,6 +12,9 @@ class PaneResizeHandleElement extends HTMLElement
|
||||
@isHorizontal = @parentElement.classList.contains("horizontal")
|
||||
@classList.add if @isHorizontal then 'horizontal' else 'vertical'
|
||||
|
||||
detachedCallback: ->
|
||||
@resizeStopped()
|
||||
|
||||
resizeToFitContent: ->
|
||||
# clear flex-grow css style of both pane
|
||||
@previousSibling.model.setFlexScale(1)
|
||||
@@ -43,6 +46,7 @@ class PaneResizeHandleElement extends HTMLElement
|
||||
|
||||
resizePane: ({clientX, clientY, which}) ->
|
||||
return @resizeStopped() unless which is 1
|
||||
return @resizeStopped() unless @previousSibling? and @nextSibling?
|
||||
|
||||
if @isHorizontal
|
||||
totalWidth = @previousSibling.clientWidth + @nextSibling.clientWidth
|
||||
|
||||
@@ -404,6 +404,20 @@ class Selection extends Model
|
||||
@selectLeft() if @isEmpty() and not @editor.isFoldedAtScreenRow(@cursor.getScreenRow())
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Removes the selection or, if nothing is selected, then all
|
||||
# characters from the start of the selection back to the previous word
|
||||
# boundary.
|
||||
deleteToPreviousWordBoundary: ->
|
||||
@selectToPreviousWordBoundary() if @isEmpty()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Removes the selection or, if nothing is selected, then all
|
||||
# characters from the start of the selection up to the next word
|
||||
# boundary.
|
||||
deleteToNextWordBoundary: ->
|
||||
@selectToNextWordBoundary() if @isEmpty()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Removes from the start of the selection to the beginning of the
|
||||
# current word if the selection is empty otherwise it deletes the selection.
|
||||
deleteToBeginningOfWord: ->
|
||||
|
||||
@@ -70,7 +70,7 @@ class TextEditorComponent
|
||||
@scrollViewNode.classList.add('scroll-view')
|
||||
@domNode.appendChild(@scrollViewNode)
|
||||
|
||||
@mountGutterContainerComponent() if @presenter.getState().gutters.sortedDescriptions.length
|
||||
@mountGutterContainerComponent() if @presenter.getState().gutters.length
|
||||
|
||||
@hiddenInputComponent = new InputComponent
|
||||
@scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
|
||||
@@ -137,7 +137,7 @@ class TextEditorComponent
|
||||
else
|
||||
@domNode.style.height = ''
|
||||
|
||||
if @newState.gutters.sortedDescriptions.length
|
||||
if @newState.gutters.length
|
||||
@mountGutterContainerComponent() unless @gutterContainerComponent?
|
||||
@gutterContainerComponent.updateSync(@newState)
|
||||
else
|
||||
@@ -349,15 +349,15 @@ class TextEditorComponent
|
||||
|
||||
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
|
||||
# Scrolling horizontally
|
||||
previousScrollLeft = @editor.getScrollLeft()
|
||||
previousScrollLeft = @presenter.getScrollLeft()
|
||||
@presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity))
|
||||
event.preventDefault() unless previousScrollLeft is @editor.getScrollLeft()
|
||||
event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft()
|
||||
else
|
||||
# Scrolling vertically
|
||||
@presenter.setMouseWheelScreenRow(@screenRowForNode(event.target))
|
||||
previousScrollTop = @presenter.scrollTop
|
||||
previousScrollTop = @presenter.getScrollTop()
|
||||
@presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity))
|
||||
event.preventDefault() unless previousScrollTop is @editor.getScrollTop()
|
||||
event.preventDefault() unless previousScrollTop is @presenter.getScrollTop()
|
||||
|
||||
onScrollViewScroll: =>
|
||||
if @mounted
|
||||
@@ -619,6 +619,7 @@ class TextEditorComponent
|
||||
if clientWidth > 0
|
||||
@presenter.setContentFrameWidth(clientWidth)
|
||||
|
||||
@presenter.setGutterWidth(@gutterContainerComponent?.getDomNode().offsetWidth ? 0)
|
||||
@presenter.setBoundingClientRect(@hostElement.getBoundingClientRect())
|
||||
|
||||
measureWindowSize: ->
|
||||
|
||||
@@ -281,6 +281,8 @@ atom.commands.add 'atom-text-editor', stopEventPropagationAndGroupUndo(
|
||||
'core:cut': -> @cutSelectedText()
|
||||
'core:copy': -> @copySelectedText()
|
||||
'core:paste': -> @pasteText()
|
||||
'editor:delete-to-previous-word-boundary': -> @deleteToPreviousWordBoundary()
|
||||
'editor:delete-to-next-word-boundary': -> @deleteToNextWordBoundary()
|
||||
'editor:delete-to-beginning-of-word': -> @deleteToBeginningOfWord()
|
||||
'editor:delete-to-beginning-of-line': -> @deleteToBeginningOfLine()
|
||||
'editor:delete-to-end-of-line': -> @deleteToEndOfLine()
|
||||
|
||||
@@ -13,12 +13,13 @@ class TextEditorPresenter
|
||||
overlayDimensions: {}
|
||||
|
||||
constructor: (params) ->
|
||||
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight} = params
|
||||
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
|
||||
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
|
||||
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
|
||||
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
|
||||
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
|
||||
@gutterWidth ?= 0
|
||||
|
||||
@disposables = new CompositeDisposable
|
||||
@emitter = new Emitter
|
||||
@@ -55,18 +56,6 @@ class TextEditorPresenter
|
||||
isBatching: ->
|
||||
@updating is false
|
||||
|
||||
# Private: Executes `fn` if `isBatching()` is false, otherwise sets `@[flagName]` to `true` for later processing. In either cases, it calls `emitDidUpdateState`.
|
||||
# * `flagName` {String} name of a property of this presenter
|
||||
# * `fn` {Function} to call when not batching.
|
||||
batch: (flagName, fn) ->
|
||||
if @isBatching()
|
||||
@[flagName] = true
|
||||
else
|
||||
fn.apply(this)
|
||||
@[flagName] = false
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
# Public: Gets this presenter's state, updating it just in time before returning from this function.
|
||||
# Returns a state {Object}, useful for rendering to screen.
|
||||
getState: ->
|
||||
@@ -94,40 +83,68 @@ class TextEditorPresenter
|
||||
@updateCustomGutterDecorationState() if @shouldUpdateCustomGutterDecorationState
|
||||
@updating = false
|
||||
|
||||
@resetTrackedUpdates()
|
||||
|
||||
@state
|
||||
|
||||
resetTrackedUpdates: ->
|
||||
@shouldUpdateFocusedState = false
|
||||
@shouldUpdateHeightState = false
|
||||
@shouldUpdateVerticalScrollState = false
|
||||
@shouldUpdateHorizontalScrollState = false
|
||||
@shouldUpdateScrollbarsState = false
|
||||
@shouldUpdateHiddenInputState = false
|
||||
@shouldUpdateContentState = false
|
||||
@shouldUpdateDecorations = false
|
||||
@shouldUpdateLinesState = false
|
||||
@shouldUpdateCursorsState = false
|
||||
@shouldUpdateOverlaysState = false
|
||||
@shouldUpdateLineNumberGutterState = false
|
||||
@shouldUpdateLineNumbersState = false
|
||||
@shouldUpdateGutterOrderState = false
|
||||
@shouldUpdateCustomGutterDecorationState = false
|
||||
|
||||
observeModel: ->
|
||||
@disposables.add @model.onDidChange =>
|
||||
@updateContentDimensions()
|
||||
@updateEndRow()
|
||||
@updateHeightState()
|
||||
@updateVerticalScrollState()
|
||||
@updateHorizontalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateLineNumberGutterState()
|
||||
@updateLineNumbersState()
|
||||
@updateGutterOrderState()
|
||||
@updateCustomGutterDecorationState()
|
||||
@shouldUpdateHeightState = true
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
@disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this))
|
||||
@disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this))
|
||||
@disposables.add @model.onDidChangePlaceholderText =>
|
||||
@shouldUpdateContentState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
@disposables.add @model.onDidChangeMini =>
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@updateScrollbarDimensions()
|
||||
@updateScrollbarsState()
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateLineNumberGutterState()
|
||||
@updateLineNumbersState()
|
||||
@updateCommonGutterState()
|
||||
@updateGutterOrderState()
|
||||
@updateCustomGutterDecorationState()
|
||||
|
||||
@emitDidUpdateState()
|
||||
@disposables.add @model.onDidChangeLineNumberGutterVisible =>
|
||||
@updateLineNumberGutterState()
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@updateCommonGutterState()
|
||||
@updateGutterOrderState()
|
||||
|
||||
@emitDidUpdateState()
|
||||
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
|
||||
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
|
||||
@disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this))
|
||||
@@ -153,24 +170,32 @@ class TextEditorPresenter
|
||||
|
||||
@configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) =>
|
||||
@showIndentGuide = newValue
|
||||
@updateContentState()
|
||||
@shouldUpdateContentState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
@configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) =>
|
||||
@scrollPastEnd = newValue
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@updateScrollHeight()
|
||||
@updateVerticalScrollState()
|
||||
@updateScrollbarsState()
|
||||
|
||||
@emitDidUpdateState()
|
||||
@configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) =>
|
||||
@showLineNumbers = newValue
|
||||
@updateLineNumberGutterState()
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@updateCommonGutterState()
|
||||
@updateGutterOrderState()
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
didChangeGrammar: ->
|
||||
@observeConfig()
|
||||
@updateContentState()
|
||||
@updateLineNumberGutterState()
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@updateCommonGutterState()
|
||||
@updateGutterOrderState()
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
buildState: ->
|
||||
@state =
|
||||
@@ -183,11 +208,13 @@ class TextEditorPresenter
|
||||
lines: {}
|
||||
highlights: {}
|
||||
overlays: {}
|
||||
gutters:
|
||||
sortedDescriptions: []
|
||||
customDecorations: {}
|
||||
lineNumberGutter:
|
||||
lineNumbers: {}
|
||||
gutters: []
|
||||
# Shared state that is copied into ``@state.gutters`.
|
||||
@sharedGutterStyles = {}
|
||||
@customGutterDecorations = {}
|
||||
@lineNumberGutter =
|
||||
lineNumbers: {}
|
||||
|
||||
@updateState()
|
||||
|
||||
updateState: ->
|
||||
@@ -213,32 +240,34 @@ class TextEditorPresenter
|
||||
@updateGutterOrderState()
|
||||
@updateCustomGutterDecorationState()
|
||||
|
||||
updateFocusedState: -> @batch "shouldUpdateFocusedState", ->
|
||||
@resetTrackedUpdates()
|
||||
|
||||
updateFocusedState: ->
|
||||
@state.focused = @focused
|
||||
|
||||
updateHeightState: -> @batch "shouldUpdateHeightState", ->
|
||||
updateHeightState: ->
|
||||
if @autoHeight
|
||||
@state.height = @contentHeight
|
||||
else
|
||||
@state.height = null
|
||||
|
||||
updateVerticalScrollState: -> @batch "shouldUpdateVerticalScrollState", ->
|
||||
updateVerticalScrollState: ->
|
||||
@state.content.scrollHeight = @scrollHeight
|
||||
@state.gutters.scrollHeight = @scrollHeight
|
||||
@sharedGutterStyles.scrollHeight = @scrollHeight
|
||||
@state.verticalScrollbar.scrollHeight = @scrollHeight
|
||||
|
||||
@state.content.scrollTop = @scrollTop
|
||||
@state.gutters.scrollTop = @scrollTop
|
||||
@sharedGutterStyles.scrollTop = @scrollTop
|
||||
@state.verticalScrollbar.scrollTop = @scrollTop
|
||||
|
||||
updateHorizontalScrollState: -> @batch "shouldUpdateHorizontalScrollState", ->
|
||||
updateHorizontalScrollState: ->
|
||||
@state.content.scrollWidth = @scrollWidth
|
||||
@state.horizontalScrollbar.scrollWidth = @scrollWidth
|
||||
|
||||
@state.content.scrollLeft = @scrollLeft
|
||||
@state.horizontalScrollbar.scrollLeft = @scrollLeft
|
||||
|
||||
updateScrollbarsState: -> @batch "shouldUpdateScrollbarsState", ->
|
||||
updateScrollbarsState: ->
|
||||
@state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0
|
||||
@state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight
|
||||
@state.horizontalScrollbar.right = @verticalScrollbarWidth
|
||||
@@ -247,7 +276,7 @@ class TextEditorPresenter
|
||||
@state.verticalScrollbar.width = @measuredVerticalScrollbarWidth
|
||||
@state.verticalScrollbar.bottom = @horizontalScrollbarHeight
|
||||
|
||||
updateHiddenInputState: -> @batch "shouldUpdateHiddenInputState", ->
|
||||
updateHiddenInputState: ->
|
||||
return unless lastCursor = @model.getLastCursor()
|
||||
|
||||
{top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange())
|
||||
@@ -264,14 +293,14 @@ class TextEditorPresenter
|
||||
@state.hiddenInput.height = height
|
||||
@state.hiddenInput.width = Math.max(width, 2)
|
||||
|
||||
updateContentState: -> @batch "shouldUpdateContentState", ->
|
||||
updateContentState: ->
|
||||
@state.content.scrollWidth = @scrollWidth
|
||||
@state.content.scrollLeft = @scrollLeft
|
||||
@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
|
||||
|
||||
updateLinesState: -> @batch "shouldUpdateLinesState", ->
|
||||
updateLinesState: ->
|
||||
return unless @startRow? and @endRow? and @lineHeight?
|
||||
|
||||
visibleLineIds = {}
|
||||
@@ -316,7 +345,7 @@ class TextEditorPresenter
|
||||
top: row * @lineHeight
|
||||
decorationClasses: @lineDecorationClassesForRow(row)
|
||||
|
||||
updateCursorsState: -> @batch "shouldUpdateCursorsState", ->
|
||||
updateCursorsState: ->
|
||||
@state.content.cursors = {}
|
||||
@updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation
|
||||
return
|
||||
@@ -334,7 +363,7 @@ class TextEditorPresenter
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
updateOverlaysState: -> @batch "shouldUpdateOverlaysState", ->
|
||||
updateOverlaysState: ->
|
||||
return unless @hasOverlayPositionRequirements()
|
||||
|
||||
visibleDecorationIds = {}
|
||||
@@ -351,10 +380,9 @@ class TextEditorPresenter
|
||||
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
|
||||
|
||||
{scrollTop, scrollLeft} = @state.content
|
||||
gutterWidth = @boundingClientRect.width - @contentFrameWidth
|
||||
|
||||
top = pixelPosition.top + @lineHeight - scrollTop
|
||||
left = pixelPosition.left + gutterWidth - scrollLeft
|
||||
left = pixelPosition.left + @gutterWidth - scrollLeft
|
||||
|
||||
if overlayDimensions = @overlayDimensions[decoration.id]
|
||||
{itemWidth, itemHeight, contentMargin} = overlayDimensions
|
||||
@@ -383,11 +411,11 @@ class TextEditorPresenter
|
||||
|
||||
return
|
||||
|
||||
updateLineNumberGutterState: -> @batch "shouldUpdateLineNumberGutterState", ->
|
||||
@state.gutters.lineNumberGutter.maxLineNumberDigits = @model.getLineCount().toString().length
|
||||
updateLineNumberGutterState: ->
|
||||
@lineNumberGutter.maxLineNumberDigits = @model.getLineCount().toString().length
|
||||
|
||||
updateCommonGutterState: ->
|
||||
@state.gutters.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)"
|
||||
@sharedGutterStyles.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)"
|
||||
@gutterBackgroundColor
|
||||
else
|
||||
@backgroundColor
|
||||
@@ -395,30 +423,45 @@ class TextEditorPresenter
|
||||
didAddGutter: (gutter) ->
|
||||
gutterDisposables = new CompositeDisposable
|
||||
gutterDisposables.add gutter.onDidChangeVisible =>
|
||||
@updateGutterOrderState()
|
||||
@updateCustomGutterDecorationState()
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
gutterDisposables.add gutter.onDidDestroy =>
|
||||
@disposables.remove(gutterDisposables)
|
||||
gutterDisposables.dispose()
|
||||
@updateGutterOrderState()
|
||||
@shouldUpdateGutterOrderState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
# It is not necessary to @updateCustomGutterDecorationState here.
|
||||
# The destroyed gutter will be removed from the list of gutters in @state,
|
||||
# and thus will be removed from the DOM.
|
||||
@disposables.add(gutterDisposables)
|
||||
@updateGutterOrderState()
|
||||
@updateCustomGutterDecorationState()
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
updateGutterOrderState: ->
|
||||
@batch "shouldUpdateGutterOrderState", ->
|
||||
@state.gutters.sortedDescriptions = []
|
||||
if @model.isMini()
|
||||
return
|
||||
for gutter in @model.getGutters()
|
||||
isVisible = @gutterIsVisible(gutter)
|
||||
@state.gutters.sortedDescriptions.push({gutter, visible: isVisible})
|
||||
@state.gutters = []
|
||||
if @model.isMini()
|
||||
return
|
||||
for gutter in @model.getGutters()
|
||||
isVisible = @gutterIsVisible(gutter)
|
||||
if gutter.name is 'line-number'
|
||||
content = @lineNumberGutter
|
||||
else
|
||||
@customGutterDecorations[gutter.name] ?= {}
|
||||
content = @customGutterDecorations[gutter.name]
|
||||
@state.gutters.push({
|
||||
gutter,
|
||||
visible: isVisible,
|
||||
styles: @sharedGutterStyles,
|
||||
content,
|
||||
})
|
||||
|
||||
# Updates the decoration state for the gutter with the given gutterName.
|
||||
# @state.gutters.customDecorations is an {Object}, with the form:
|
||||
# @customGutterDecorations is an {Object}, with the form:
|
||||
# * gutterName : {
|
||||
# decoration.id : {
|
||||
# top: # of pixels from top
|
||||
@@ -428,25 +471,44 @@ class TextEditorPresenter
|
||||
# }
|
||||
# }
|
||||
updateCustomGutterDecorationState: ->
|
||||
@batch 'shouldUpdateCustomGutterDecorationState', =>
|
||||
return unless @startRow? and @endRow? and @lineHeight?
|
||||
return unless @startRow? and @endRow? and @lineHeight?
|
||||
|
||||
@state.gutters.customDecorations = {}
|
||||
return if @model.isMini()
|
||||
if @model.isMini()
|
||||
# Mini editors have no gutter decorations.
|
||||
# We clear instead of reassigning to preserve the reference.
|
||||
@clearAllCustomGutterDecorations()
|
||||
|
||||
for gutter in @model.getGutters()
|
||||
gutterName = gutter.name
|
||||
@state.gutters.customDecorations[gutterName] = {}
|
||||
return if not @gutterIsVisible(gutter)
|
||||
for gutter in @model.getGutters()
|
||||
gutterName = gutter.name
|
||||
gutterDecorations = @customGutterDecorations[gutterName]
|
||||
if gutterDecorations
|
||||
# Clear the gutter decorations; they are rebuilt.
|
||||
# We clear instead of reassigning to preserve the reference.
|
||||
@clearDecorationsForCustomGutterName(gutterName)
|
||||
else
|
||||
@customGutterDecorations[gutterName] = {}
|
||||
return if not @gutterIsVisible(gutter)
|
||||
|
||||
relevantDecorations = @customGutterDecorationsInRange(gutterName, @startRow, @endRow - 1)
|
||||
relevantDecorations.forEach (decoration) =>
|
||||
decorationRange = decoration.getMarker().getScreenRange()
|
||||
@state.gutters.customDecorations[gutterName][decoration.id] =
|
||||
top: @lineHeight * decorationRange.start.row
|
||||
height: @lineHeight * decorationRange.getRowCount()
|
||||
item: decoration.getProperties().item
|
||||
class: decoration.getProperties().class
|
||||
relevantDecorations = @customGutterDecorationsInRange(gutterName, @startRow, @endRow - 1)
|
||||
relevantDecorations.forEach (decoration) =>
|
||||
decorationRange = decoration.getMarker().getScreenRange()
|
||||
@customGutterDecorations[gutterName][decoration.id] =
|
||||
top: @lineHeight * decorationRange.start.row
|
||||
height: @lineHeight * decorationRange.getRowCount()
|
||||
item: decoration.getProperties().item
|
||||
class: decoration.getProperties().class
|
||||
|
||||
clearAllCustomGutterDecorations: ->
|
||||
allGutterNames = Object.keys(@customGutterDecorations)
|
||||
for gutterName in allGutterNames
|
||||
@clearDecorationsForCustomGutterName(gutterName)
|
||||
|
||||
clearDecorationsForCustomGutterName: (gutterName) ->
|
||||
gutterDecorations = @customGutterDecorations[gutterName]
|
||||
if gutterDecorations
|
||||
allDecorationIds = Object.keys(gutterDecorations)
|
||||
for decorationId in allDecorationIds
|
||||
delete gutterDecorations[decorationId]
|
||||
|
||||
gutterIsVisible: (gutterModel) ->
|
||||
isVisible = gutterModel.isVisible()
|
||||
@@ -454,7 +516,7 @@ class TextEditorPresenter
|
||||
isVisible = isVisible and @showLineNumbers
|
||||
isVisible
|
||||
|
||||
updateLineNumbersState: -> @batch "shouldUpdateLineNumbersState", ->
|
||||
updateLineNumbersState: ->
|
||||
return unless @startRow? and @endRow? and @lineHeight?
|
||||
|
||||
visibleLineNumberIds = {}
|
||||
@@ -484,7 +546,7 @@ class TextEditorPresenter
|
||||
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
|
||||
foldable = @model.isFoldableAtScreenRow(screenRow)
|
||||
|
||||
@state.gutters.lineNumberGutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
|
||||
@lineNumberGutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
|
||||
visibleLineNumberIds[id] = true
|
||||
|
||||
if @mouseWheelScreenRow?
|
||||
@@ -494,8 +556,8 @@ class TextEditorPresenter
|
||||
id += '-' + wrapCount if wrapCount > 0
|
||||
visibleLineNumberIds[id] = true
|
||||
|
||||
for id of @state.gutters.lineNumberGutter.lineNumbers
|
||||
delete @state.gutters.lineNumberGutter.lineNumbers[id] unless visibleLineNumberIds[id]
|
||||
for id of @lineNumberGutter.lineNumbers
|
||||
delete @lineNumberGutter.lineNumbers[id] unless visibleLineNumberIds[id]
|
||||
|
||||
return
|
||||
|
||||
@@ -541,7 +603,8 @@ class TextEditorPresenter
|
||||
|
||||
if @baseCharacterWidth?
|
||||
oldContentWidth = @contentWidth
|
||||
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
|
||||
clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped()
|
||||
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left
|
||||
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
|
||||
|
||||
if @contentHeight isnt oldContentHeight
|
||||
@@ -670,8 +733,10 @@ class TextEditorPresenter
|
||||
@startBlinkingCursors()
|
||||
else
|
||||
@stopBlinkingCursors(false)
|
||||
@updateFocusedState()
|
||||
@updateHiddenInputState()
|
||||
@shouldUpdateFocusedState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setScrollTop: (scrollTop) ->
|
||||
scrollTop = @constrainScrollTop(scrollTop)
|
||||
@@ -682,14 +747,19 @@ class TextEditorPresenter
|
||||
@updateStartRow()
|
||||
@updateEndRow()
|
||||
@didStartScrolling()
|
||||
@updateVerticalScrollState()
|
||||
@updateHiddenInputState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateCursorsState()
|
||||
@updateLineNumbersState()
|
||||
@updateCustomGutterDecorationState()
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
getScrollTop: ->
|
||||
@scrollTop
|
||||
|
||||
didStartScrolling: ->
|
||||
if @stoppedScrollingTimeoutId?
|
||||
@@ -703,11 +773,11 @@ class TextEditorPresenter
|
||||
@state.content.scrollingVertically = false
|
||||
if @mouseWheelScreenRow?
|
||||
@mouseWheelScreenRow = null
|
||||
@updateLinesState()
|
||||
@updateLineNumbersState()
|
||||
@updateCustomGutterDecorationState()
|
||||
else
|
||||
@emitDidUpdateState()
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setScrollLeft: (scrollLeft) ->
|
||||
scrollLeft = @constrainScrollLeft(scrollLeft)
|
||||
@@ -715,10 +785,15 @@ class TextEditorPresenter
|
||||
oldScrollLeft = @scrollLeft
|
||||
@scrollLeft = scrollLeft
|
||||
@model.setScrollLeft(scrollLeft)
|
||||
@updateHorizontalScrollState()
|
||||
@updateHiddenInputState()
|
||||
@updateCursorsState() unless oldScrollLeft?
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateCursorsState = true unless oldScrollLeft?
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
getScrollLeft: ->
|
||||
@scrollLeft
|
||||
|
||||
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
|
||||
unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight
|
||||
@@ -726,10 +801,12 @@ class TextEditorPresenter
|
||||
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
|
||||
@model.setHorizontalScrollbarHeight(horizontalScrollbarHeight)
|
||||
@updateScrollbarDimensions()
|
||||
@updateScrollbarsState()
|
||||
@updateVerticalScrollState()
|
||||
@updateHorizontalScrollState()
|
||||
@updateCursorsState() unless oldHorizontalScrollbarHeight?
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateCursorsState = true unless oldHorizontalScrollbarHeight?
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setVerticalScrollbarWidth: (verticalScrollbarWidth) ->
|
||||
unless @measuredVerticalScrollbarWidth is verticalScrollbarWidth
|
||||
@@ -737,28 +814,34 @@ class TextEditorPresenter
|
||||
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
|
||||
@model.setVerticalScrollbarWidth(verticalScrollbarWidth)
|
||||
@updateScrollbarDimensions()
|
||||
@updateScrollbarsState()
|
||||
@updateVerticalScrollState()
|
||||
@updateHorizontalScrollState()
|
||||
@updateCursorsState() unless oldVerticalScrollbarWidth?
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateCursorsState = true unless oldVerticalScrollbarWidth?
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setAutoHeight: (autoHeight) ->
|
||||
unless @autoHeight is autoHeight
|
||||
@autoHeight = autoHeight
|
||||
@updateHeightState()
|
||||
@shouldUpdateHeightState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setExplicitHeight: (explicitHeight) ->
|
||||
unless @explicitHeight is explicitHeight
|
||||
@explicitHeight = explicitHeight
|
||||
@model.setHeight(explicitHeight)
|
||||
@updateHeight()
|
||||
@updateVerticalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateCursorsState()
|
||||
@updateLineNumbersState()
|
||||
@updateCustomGutterDecorationState()
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
updateHeight: ->
|
||||
height = @explicitHeight ? @contentHeight
|
||||
@@ -776,18 +859,22 @@ class TextEditorPresenter
|
||||
@model.setWidth(contentFrameWidth)
|
||||
@updateScrollbarDimensions()
|
||||
@updateClientWidth()
|
||||
@updateVerticalScrollState()
|
||||
@updateHorizontalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateCursorsState() unless oldContentFrameWidth?
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateCursorsState = true unless oldContentFrameWidth?
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBoundingClientRect: (boundingClientRect) ->
|
||||
unless @clientRectsEqual(@boundingClientRect, boundingClientRect)
|
||||
@boundingClientRect = boundingClientRect
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
clientRectsEqual: (clientRectA, clientRectB) ->
|
||||
clientRectA? and clientRectB? and
|
||||
@@ -800,22 +887,33 @@ class TextEditorPresenter
|
||||
if @windowWidth isnt width or @windowHeight isnt height
|
||||
@windowWidth = width
|
||||
@windowHeight = height
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBackgroundColor: (backgroundColor) ->
|
||||
unless @backgroundColor is backgroundColor
|
||||
@backgroundColor = backgroundColor
|
||||
@updateContentState()
|
||||
@updateLineNumberGutterState()
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@updateCommonGutterState()
|
||||
@updateGutterOrderState()
|
||||
@shouldUpdateGutterOrderState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setGutterBackgroundColor: (gutterBackgroundColor) ->
|
||||
unless @gutterBackgroundColor is gutterBackgroundColor
|
||||
@gutterBackgroundColor = gutterBackgroundColor
|
||||
@updateLineNumberGutterState()
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@updateCommonGutterState()
|
||||
@updateGutterOrderState()
|
||||
@shouldUpdateGutterOrderState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setGutterWidth: (gutterWidth) ->
|
||||
if @gutterWidth isnt gutterWidth
|
||||
@gutterWidth = gutterWidth
|
||||
@updateOverlaysState()
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
unless @lineHeight is lineHeight
|
||||
@@ -826,17 +924,19 @@ class TextEditorPresenter
|
||||
@updateHeight()
|
||||
@updateStartRow()
|
||||
@updateEndRow()
|
||||
@updateHeightState()
|
||||
@updateHorizontalScrollState()
|
||||
@updateVerticalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateHiddenInputState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateCursorsState()
|
||||
@updateLineNumbersState()
|
||||
@updateCustomGutterDecorationState()
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateHeightState = true
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setMouseWheelScreenRow: (mouseWheelScreenRow) ->
|
||||
unless @mouseWheelScreenRow is mouseWheelScreenRow
|
||||
@@ -876,15 +976,17 @@ class TextEditorPresenter
|
||||
characterWidthsChanged: ->
|
||||
@updateContentDimensions()
|
||||
|
||||
@updateHorizontalScrollState()
|
||||
@updateVerticalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateHiddenInputState()
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateCursorsState()
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
clearScopedCharacterWidths: ->
|
||||
@characterWidthsByScope = {}
|
||||
@@ -972,11 +1074,11 @@ class TextEditorPresenter
|
||||
intersectsVisibleRowRange = true
|
||||
|
||||
if intersectsVisibleRowRange
|
||||
@updateLinesState() if decoration.isType('line')
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@updateLineNumbersState()
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@updateCustomGutterDecorationState()
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
|
||||
if decoration.isType('highlight')
|
||||
return if change.textChanged
|
||||
@@ -984,7 +1086,9 @@ class TextEditorPresenter
|
||||
@updateHighlightState(decoration)
|
||||
|
||||
if decoration.isType('overlay')
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
decorationPropertiesDidChange: (decoration, event) ->
|
||||
{oldProperties} = event
|
||||
@@ -995,29 +1099,33 @@ class TextEditorPresenter
|
||||
decoration.getMarker().getScreenRange())
|
||||
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||
if decoration.isType('line') or Decoration.isType(oldProperties, 'line')
|
||||
@updateLinesState()
|
||||
@shouldUpdateLinesState = true
|
||||
if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number')
|
||||
@updateLineNumbersState()
|
||||
@shouldUpdateLineNumbersState = true
|
||||
if (decoration.isType('gutter') and not decoration.isType('line-number')) or
|
||||
(Decoration.isType(oldProperties, 'gutter') and not Decoration.isType(oldProperties, 'line-number'))
|
||||
@updateCustomGutterDecorationState()
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
else if decoration.isType('overlay')
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateOverlaysState = true
|
||||
else if decoration.isType('highlight')
|
||||
@updateHighlightState(decoration, event)
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||
@updateLinesState() if decoration.isType('line')
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@updateLineNumbersState()
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@updateCustomGutterDecorationState(decoration.getProperties().gutterName)
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
if decoration.isType('highlight')
|
||||
@updateHighlightState(decoration)
|
||||
if decoration.isType('overlay')
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
highlightDidFlash: (decoration) ->
|
||||
flash = decoration.consumeNextFlash()
|
||||
@@ -1032,17 +1140,19 @@ class TextEditorPresenter
|
||||
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||
@updateLinesState() if decoration.isType('line')
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@updateLineNumbersState()
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@updateCustomGutterDecorationState()
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
else if decoration.isType('highlight')
|
||||
@updateHighlightState(decoration)
|
||||
else if decoration.isType('overlay')
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
updateDecorations: -> @batch "shouldUpdateDecorations", ->
|
||||
@emitDidUpdateState()
|
||||
|
||||
updateDecorations: ->
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow = {}
|
||||
@@ -1197,14 +1307,18 @@ class TextEditorPresenter
|
||||
overlayState.itemWidth = itemWidth
|
||||
overlayState.itemHeight = itemHeight
|
||||
overlayState.contentMargin = contentMargin
|
||||
@updateOverlaysState()
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
observeCursor: (cursor) ->
|
||||
didChangePositionDisposable = cursor.onDidChangePosition =>
|
||||
@updateHiddenInputState() if cursor.isLastCursor()
|
||||
@shouldUpdateHiddenInputState = true if cursor.isLastCursor()
|
||||
@pauseCursorBlinking()
|
||||
@updateCursorState(cursor)
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
didChangeVisibilityDisposable = cursor.onDidChangeVisibility =>
|
||||
@updateCursorState(cursor)
|
||||
|
||||
@@ -1212,19 +1326,23 @@ class TextEditorPresenter
|
||||
@disposables.remove(didChangePositionDisposable)
|
||||
@disposables.remove(didChangeVisibilityDisposable)
|
||||
@disposables.remove(didDestroyDisposable)
|
||||
@updateHiddenInputState()
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@updateCursorState(cursor, true)
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add(didChangePositionDisposable)
|
||||
@disposables.add(didChangeVisibilityDisposable)
|
||||
@disposables.add(didDestroyDisposable)
|
||||
|
||||
didAddCursor: (cursor) ->
|
||||
@observeCursor(cursor)
|
||||
@updateHiddenInputState()
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@pauseCursorBlinking()
|
||||
@updateCursorState(cursor)
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
startBlinkingCursors: ->
|
||||
unless @toggleCursorBlinkHandle
|
||||
@state.content.cursorsVisible = true
|
||||
|
||||
@@ -75,7 +75,7 @@ class TextEditor extends Model
|
||||
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
|
||||
toProperty: 'languageMode'
|
||||
|
||||
constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible}) ->
|
||||
constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible}={}) ->
|
||||
super
|
||||
|
||||
@emitter = new Emitter
|
||||
@@ -84,12 +84,10 @@ class TextEditor extends Model
|
||||
@selections = []
|
||||
|
||||
buffer ?= new TextBuffer
|
||||
@displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped})
|
||||
@displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini})
|
||||
@buffer = @displayBuffer.buffer
|
||||
@softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true
|
||||
|
||||
@updateInvisibles()
|
||||
|
||||
for marker in @findMarkers(@getSelectionMarkerAttributes())
|
||||
marker.setProperties(preserveFolds: true)
|
||||
@addSelection(marker)
|
||||
@@ -170,21 +168,9 @@ class TextEditor extends Model
|
||||
@subscribe @displayBuffer.onDidAddDecoration (decoration) => @emit 'decoration-added', decoration
|
||||
@subscribe @displayBuffer.onDidRemoveDecoration (decoration) => @emit 'decoration-removed', decoration
|
||||
|
||||
@subscribeToScopedConfigSettings()
|
||||
|
||||
subscribeToScopedConfigSettings: ->
|
||||
@scopedConfigSubscriptions?.dispose()
|
||||
@scopedConfigSubscriptions = subscriptions = new CompositeDisposable
|
||||
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
|
||||
subscriptions.add atom.config.onDidChange 'editor.showInvisibles', scope: scopeDescriptor, => @updateInvisibles()
|
||||
subscriptions.add atom.config.onDidChange 'editor.invisibles', scope: scopeDescriptor, => @updateInvisibles()
|
||||
|
||||
destroyed: ->
|
||||
@unsubscribe() if includeDeprecatedAPIs
|
||||
@disposables.dispose()
|
||||
@scopedConfigSubscriptions.dispose()
|
||||
selection.destroy() for selection in @getSelections()
|
||||
@buffer.release()
|
||||
@displayBuffer.destroy()
|
||||
@@ -488,7 +474,7 @@ class TextEditor extends Model
|
||||
setMini: (mini) ->
|
||||
if mini isnt @mini
|
||||
@mini = mini
|
||||
@updateInvisibles()
|
||||
@displayBuffer.setIgnoreInvisibles(@mini)
|
||||
@emitter.emit 'did-change-mini', @mini
|
||||
@mini
|
||||
|
||||
@@ -1057,6 +1043,16 @@ class TextEditor extends Model
|
||||
deleteToBeginningOfWord: ->
|
||||
@mutateSelectedText (selection) -> selection.deleteToBeginningOfWord()
|
||||
|
||||
# Extended: Similar to {::deleteToBeginningOfWord}, but deletes only back to the
|
||||
# previous word boundary.
|
||||
deleteToPreviousWordBoundary: ->
|
||||
@mutateSelectedText (selection) -> selection.deleteToPreviousWordBoundary()
|
||||
|
||||
# Extended: Similar to {::deleteToEndOfWord}, but deletes only up to the
|
||||
# next word boundary.
|
||||
deleteToNextWordBoundary: ->
|
||||
@mutateSelectedText (selection) -> selection.deleteToNextWordBoundary()
|
||||
|
||||
# Extended: For each selection, if the selection is empty, delete all characters
|
||||
# of the containing line that precede the cursor. Otherwise delete the
|
||||
# selected text.
|
||||
@@ -2769,15 +2765,6 @@ class TextEditor extends Model
|
||||
shouldAutoIndentOnPaste: ->
|
||||
atom.config.get("editor.autoIndentOnPaste", scope: @getRootScopeDescriptor())
|
||||
|
||||
shouldShowInvisibles: ->
|
||||
not @mini and atom.config.get('editor.showInvisibles', scope: @getRootScopeDescriptor())
|
||||
|
||||
updateInvisibles: ->
|
||||
if @shouldShowInvisibles()
|
||||
@displayBuffer.setInvisibles(atom.config.get('editor.invisibles', scope: @getRootScopeDescriptor()))
|
||||
else
|
||||
@displayBuffer.setInvisibles(null)
|
||||
|
||||
###
|
||||
Section: Event Handlers
|
||||
###
|
||||
@@ -2786,8 +2773,6 @@ class TextEditor extends Model
|
||||
@softTabs = @usesSoftTabs() ? @softTabs
|
||||
|
||||
handleGrammarChange: ->
|
||||
@updateInvisibles()
|
||||
@subscribeToScopedConfigSettings()
|
||||
@unfoldAll()
|
||||
@emit 'grammar-changed' if includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change-grammar', @getGrammar()
|
||||
|
||||
@@ -20,8 +20,9 @@ class TokenizedBuffer extends Model
|
||||
chunkSize: 50
|
||||
invalidRows: null
|
||||
visible: false
|
||||
configSettings: null
|
||||
|
||||
constructor: ({@buffer, @tabLength, @invisibles}) ->
|
||||
constructor: ({@buffer, @tabLength, @ignoreInvisibles}) ->
|
||||
@emitter = new Emitter
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@@ -39,7 +40,7 @@ class TokenizedBuffer extends Model
|
||||
serializeParams: ->
|
||||
bufferPath: @buffer.getPath()
|
||||
tabLength: @tabLength
|
||||
invisibles: _.clone(@invisibles)
|
||||
ignoreInvisibles: @ignoreInvisibles
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.buffer = atom.project.bufferForPathSync(params.bufferPath)
|
||||
@@ -76,13 +77,25 @@ class TokenizedBuffer extends Model
|
||||
@grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines()
|
||||
@disposables.add(@grammarUpdateDisposable)
|
||||
|
||||
@configSettings = tabLength: atom.config.get('editor.tabLength', scope: @rootScopeDescriptor)
|
||||
scopeOptions = {scope: @rootScopeDescriptor}
|
||||
@configSettings =
|
||||
tabLength: atom.config.get('editor.tabLength', scopeOptions)
|
||||
invisibles: atom.config.get('editor.invisibles', scopeOptions)
|
||||
showInvisibles: atom.config.get('editor.showInvisibles', scopeOptions)
|
||||
|
||||
@grammarTabLengthSubscription?.dispose()
|
||||
@grammarTabLengthSubscription = atom.config.onDidChange 'editor.tabLength', scope: @rootScopeDescriptor, ({newValue}) =>
|
||||
if @configSubscriptions?
|
||||
@configSubscriptions.dispose()
|
||||
@disposables.remove(@configSubscriptions)
|
||||
@configSubscriptions = new CompositeDisposable
|
||||
@configSubscriptions.add atom.config.onDidChange 'editor.tabLength', scopeOptions, ({newValue}) =>
|
||||
@configSettings.tabLength = newValue
|
||||
@retokenizeLines()
|
||||
@disposables.add(@grammarTabLengthSubscription)
|
||||
['invisibles', 'showInvisibles'].forEach (key) =>
|
||||
@configSubscriptions.add atom.config.onDidChange "editor.#{key}", scopeOptions, ({newValue}) =>
|
||||
oldInvisibles = @getInvisiblesToShow()
|
||||
@configSettings[key] = newValue
|
||||
@retokenizeLines() unless _.isEqual(@getInvisiblesToShow(), oldInvisibles)
|
||||
@disposables.add(@configSubscriptions)
|
||||
|
||||
@retokenizeLines()
|
||||
|
||||
@@ -123,10 +136,11 @@ class TokenizedBuffer extends Model
|
||||
@tabLength = tabLength
|
||||
@retokenizeLines()
|
||||
|
||||
setInvisibles: (invisibles) ->
|
||||
unless _.isEqual(invisibles, @invisibles)
|
||||
@invisibles = invisibles
|
||||
@retokenizeLines()
|
||||
setIgnoreInvisibles: (ignoreInvisibles) ->
|
||||
if ignoreInvisibles isnt @ignoreInvisibles
|
||||
@ignoreInvisibles = ignoreInvisibles
|
||||
if @configSettings.showInvisibles and @configSettings.invisibles?
|
||||
@retokenizeLines()
|
||||
|
||||
tokenizeInBackground: ->
|
||||
return if not @visible or @pendingChunk or not @isAlive()
|
||||
@@ -302,7 +316,7 @@ class TokenizedBuffer extends Model
|
||||
tabLength = @getTabLength()
|
||||
indentLevel = @indentLevelForRow(row)
|
||||
lineEnding = @buffer.lineEndingForRow(row)
|
||||
new TokenizedLine({tokens, tabLength, indentLevel, @invisibles, lineEnding})
|
||||
new TokenizedLine({tokens, tabLength, indentLevel, invisibles: @getInvisiblesToShow(), lineEnding})
|
||||
|
||||
buildTokenizedLineForRow: (row, ruleStack) ->
|
||||
@buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack)
|
||||
@@ -312,7 +326,13 @@ class TokenizedBuffer extends Model
|
||||
tabLength = @getTabLength()
|
||||
indentLevel = @indentLevelForRow(row)
|
||||
{tokens, ruleStack} = @grammar.tokenizeLine(line, ruleStack, row is 0)
|
||||
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding, indentLevel, @invisibles})
|
||||
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding, indentLevel, invisibles: @getInvisiblesToShow()})
|
||||
|
||||
getInvisiblesToShow: ->
|
||||
if @configSettings.showInvisibles and not @ignoreInvisibles
|
||||
@configSettings.invisibles
|
||||
else
|
||||
null
|
||||
|
||||
tokenizedLineForRow: (bufferRow) ->
|
||||
@tokenizedLines[bufferRow]
|
||||
|
||||
@@ -87,8 +87,9 @@ class TooltipManager
|
||||
|
||||
new Disposable ->
|
||||
tooltip = $target.data('bs.tooltip')
|
||||
tooltip.leave(currentTarget: target)
|
||||
tooltip.hide()
|
||||
if tooltip?
|
||||
tooltip.leave(currentTarget: target)
|
||||
tooltip.hide()
|
||||
$target.tooltip('destroy')
|
||||
|
||||
humanizeKeystrokes = (keystroke) ->
|
||||
|
||||
@@ -113,7 +113,10 @@ class WorkspaceElement extends HTMLElement
|
||||
focusPaneViewOnRight: -> @paneContainer.focusPaneViewOnRight()
|
||||
|
||||
runPackageSpecs: ->
|
||||
[projectPath] = atom.project.getPaths()
|
||||
if activePath = atom.workspace.getActivePaneItem()?.getPath?()
|
||||
[projectPath] = atom.project.relativizePath(activePath)
|
||||
else
|
||||
[projectPath] = atom.project.getPaths()
|
||||
ipc.send('run-package-specs', path.join(projectPath, 'spec')) if projectPath
|
||||
|
||||
atom.commands.add 'atom-workspace',
|
||||
|
||||
@@ -75,6 +75,8 @@ class Workspace extends Model
|
||||
atom.views.addViewProvider Panel, (model) ->
|
||||
new PanelElement().initialize(model)
|
||||
|
||||
@subscribeToFontSize()
|
||||
|
||||
# Called by the Serializable mixin during deserialization
|
||||
deserializeParams: (params) ->
|
||||
for packageName in params.packagesWithActiveGrammars ? []
|
||||
@@ -619,9 +621,14 @@ class Workspace extends Model
|
||||
fontSize = atom.config.get("editor.fontSize")
|
||||
atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1
|
||||
|
||||
# Restore to a default editor font size.
|
||||
# Restore to the window's original editor font size.
|
||||
resetFontSize: ->
|
||||
atom.config.unset("editor.fontSize")
|
||||
if @originalFontSize
|
||||
atom.config.set("editor.fontSize", @originalFontSize)
|
||||
|
||||
subscribeToFontSize: ->
|
||||
atom.config.onDidChange 'editor.fontSize', ({oldValue}) =>
|
||||
@originalFontSize ?= oldValue
|
||||
|
||||
# Removes the item's uri from the list of potential items to reopen.
|
||||
itemOpened: (item) ->
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="background: #fff">
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src *; script-src 'self'; style-src 'self' 'unsafe-inline';">
|
||||
|
||||
<script src="index.js"></script>
|
||||
|
||||
189
static/index.js
189
static/index.js
@@ -1,6 +1,11 @@
|
||||
(function() {
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var loadSettings = null;
|
||||
var loadSettingsError = null;
|
||||
|
||||
window.onload = function() {
|
||||
try {
|
||||
var startTime = Date.now();
|
||||
@@ -12,66 +17,81 @@ window.onload = function() {
|
||||
// Ensure ATOM_HOME is always set before anything else is required
|
||||
setupAtomHome();
|
||||
|
||||
var cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache');
|
||||
// Use separate compile cache when sudo'ing as root to avoid permission issues
|
||||
if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) {
|
||||
cacheDir = path.join(cacheDir, 'root');
|
||||
}
|
||||
|
||||
var rawLoadSettings = decodeURIComponent(location.hash.substr(1));
|
||||
var loadSettings;
|
||||
try {
|
||||
loadSettings = JSON.parse(rawLoadSettings);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse load settings: " + rawLoadSettings);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Normalize to make sure drive letter case is consistent on Windows
|
||||
process.resourcesPath = path.normalize(process.resourcesPath);
|
||||
|
||||
if (loadSettingsError) {
|
||||
throw loadSettingsError;
|
||||
}
|
||||
|
||||
var devMode = loadSettings.devMode || !loadSettings.resourcePath.startsWith(process.resourcesPath + path.sep);
|
||||
|
||||
setupCoffeeCache(cacheDir);
|
||||
|
||||
ModuleCache = require('../src/module-cache');
|
||||
ModuleCache.register(loadSettings);
|
||||
ModuleCache.add(loadSettings.resourcePath);
|
||||
|
||||
require('grim').includeDeprecatedAPIs = !loadSettings.apiPreviewMode;
|
||||
|
||||
// Start the crash reporter before anything else.
|
||||
require('crash-reporter').start({
|
||||
productName: 'Atom',
|
||||
companyName: 'GitHub',
|
||||
// By explicitly passing the app version here, we could save the call
|
||||
// of "require('remote').require('app').getVersion()".
|
||||
extra: {_version: loadSettings.appVersion}
|
||||
});
|
||||
|
||||
setupVmCompatibility();
|
||||
setupCsonCache(cacheDir);
|
||||
setupSourceMapCache(cacheDir);
|
||||
setupBabel(cacheDir);
|
||||
setupTypeScript(cacheDir);
|
||||
|
||||
require(loadSettings.bootstrapScript);
|
||||
require('ipc').sendChannel('window-command', 'window:loaded');
|
||||
|
||||
if (global.atom) {
|
||||
global.atom.loadTime = Date.now() - startTime;
|
||||
console.log('Window load time: ' + global.atom.getWindowLoadTime() + 'ms');
|
||||
if (loadSettings.profileStartup) {
|
||||
profileStartup(loadSettings, Date.now() - startTime);
|
||||
} else {
|
||||
setupWindow(loadSettings);
|
||||
setLoadTime(Date.now() - startTime);
|
||||
}
|
||||
} catch (error) {
|
||||
var currentWindow = require('remote').getCurrentWindow();
|
||||
currentWindow.setSize(800, 600);
|
||||
currentWindow.center();
|
||||
currentWindow.show();
|
||||
currentWindow.openDevTools();
|
||||
console.error(error.stack || error);
|
||||
handleSetupError(error);
|
||||
}
|
||||
}
|
||||
|
||||
var getCacheDirectory = function() {
|
||||
var cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache');
|
||||
// Use separate compile cache when sudo'ing as root to avoid permission issues
|
||||
if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) {
|
||||
cacheDir = path.join(cacheDir, 'root');
|
||||
}
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
var setLoadTime = function(loadTime) {
|
||||
if (global.atom) {
|
||||
global.atom.loadTime = loadTime;
|
||||
console.log('Window load time: ' + global.atom.getWindowLoadTime() + 'ms');
|
||||
}
|
||||
}
|
||||
|
||||
var handleSetupError = function(error) {
|
||||
var currentWindow = require('remote').getCurrentWindow();
|
||||
currentWindow.setSize(800, 600);
|
||||
currentWindow.center();
|
||||
currentWindow.show();
|
||||
currentWindow.openDevTools();
|
||||
console.error(error.stack || error);
|
||||
}
|
||||
|
||||
var setupWindow = function(loadSettings) {
|
||||
var cacheDir = getCacheDirectory();
|
||||
|
||||
setupCoffeeCache(cacheDir);
|
||||
|
||||
ModuleCache = require('../src/module-cache');
|
||||
ModuleCache.register(loadSettings);
|
||||
ModuleCache.add(loadSettings.resourcePath);
|
||||
|
||||
require('grim').includeDeprecatedAPIs = !loadSettings.apiPreviewMode;
|
||||
|
||||
// Start the crash reporter before anything else.
|
||||
require('crash-reporter').start({
|
||||
productName: 'Atom',
|
||||
companyName: 'GitHub',
|
||||
// By explicitly passing the app version here, we could save the call
|
||||
// of "require('remote').require('app').getVersion()".
|
||||
extra: {_version: loadSettings.appVersion}
|
||||
});
|
||||
|
||||
setupVmCompatibility();
|
||||
setupCsonCache(cacheDir);
|
||||
setupSourceMapCache(cacheDir);
|
||||
setupBabel(cacheDir);
|
||||
setupTypeScript(cacheDir);
|
||||
|
||||
require(loadSettings.bootstrapScript);
|
||||
require('ipc').sendChannel('window-command', 'window:loaded');
|
||||
}
|
||||
|
||||
var setupCoffeeCache = function(cacheDir) {
|
||||
var CoffeeCache = require('coffee-cash');
|
||||
CoffeeCache.setCacheDirectory(path.join(cacheDir, 'coffee'));
|
||||
@@ -118,6 +138,73 @@ var setupSourceMapCache = function(cacheDir) {
|
||||
|
||||
var setupVmCompatibility = function() {
|
||||
var vm = require('vm');
|
||||
if (!vm.Script.createContext)
|
||||
if (!vm.Script.createContext) {
|
||||
vm.Script.createContext = vm.createContext;
|
||||
}
|
||||
}
|
||||
|
||||
var profileStartup = function(loadSettings, initialTime) {
|
||||
var profile = function() {
|
||||
console.profile('startup');
|
||||
try {
|
||||
var startTime = Date.now()
|
||||
setupWindow(loadSettings);
|
||||
setLoadTime(Date.now() - startTime + initialTime);
|
||||
} catch (error) {
|
||||
handleSetupError(error);
|
||||
} finally {
|
||||
console.profileEnd('startup');
|
||||
console.log("Switch to the Profiles tab to view the created startup profile")
|
||||
}
|
||||
};
|
||||
|
||||
var currentWindow = require('remote').getCurrentWindow();
|
||||
if (currentWindow.devToolsWebContents) {
|
||||
profile();
|
||||
} else {
|
||||
currentWindow.openDevTools();
|
||||
currentWindow.once('devtools-opened', function() {
|
||||
setTimeout(profile, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var parseLoadSettings = function() {
|
||||
var rawLoadSettings = decodeURIComponent(location.hash.substr(1));
|
||||
try {
|
||||
loadSettings = JSON.parse(rawLoadSettings);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse load settings: " + rawLoadSettings);
|
||||
loadSettingsError = error;
|
||||
}
|
||||
}
|
||||
|
||||
var setupWindowBackground = function() {
|
||||
if (loadSettings && loadSettings.isSpec) {
|
||||
return;
|
||||
}
|
||||
|
||||
var backgroundColor = window.localStorage.getItem('atom:window-background-color');
|
||||
if (!backgroundColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var backgroundStylesheet = document.createElement('style');
|
||||
backgroundStylesheet.type = 'text/css';
|
||||
backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + '; }';
|
||||
document.head.appendChild(backgroundStylesheet);
|
||||
|
||||
// Remove once the page loads
|
||||
window.addEventListener("load", function loadWindow() {
|
||||
window.removeEventListener("load", loadWindow, false);
|
||||
setTimeout(function() {
|
||||
backgroundStylesheet.remove();
|
||||
backgroundStylesheet = null;
|
||||
}, 1000);
|
||||
}, false);
|
||||
}
|
||||
|
||||
parseLoadSettings();
|
||||
setupWindowBackground();
|
||||
|
||||
})();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
atom-text-editor {
|
||||
display: block;
|
||||
font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier;
|
||||
font-family: Inconsolata, Monaco, Consolas, 'DejaVu Sans Mono', monospace;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user