Comparar commits

..

3 Commits

Autor SHA1 Mensagem Data
Ben Ogle ec5cd6a68e Add method organization docs 2014-08-19 19:09:56 -06:00
Ben Ogle 0fc94faf29 Changes based on feedback 2014-08-01 11:03:13 -07:00
Ben Ogle de2d9739c8 Initial API guidelines doc 2014-07-31 12:33:59 -07:00
134 arquivos alterados com 11200 adições e 10331 exclusões
-1
Ver Arquivo
@@ -1 +0,0 @@
v0.10.21
-11
Ver Arquivo
@@ -25,17 +25,6 @@ You can also download a `.zip` file from the [releases page](https://github.com/
The Windows version does not currently automatically update so you will need to
manually upgrade to future releases by re-downloading the `.zip` file.
### Debian Linux (Ubuntu)
Currently only a 64-bit version is available.
1. Download `atom-amd64.deb` from the [Atom releases page](https://github.com/atom/atom/releases/latest).
2. Run `sudo dpkg --install atom-amd64.deb` on the downloaded package.
3. Launch Atom using the installed `atom` command.
The Linux version does not currently automatically update so you will need to
repeat these steps to upgrade to future releases.
## Building
* [Linux](docs/build-instructions/linux.md)
+1 -1
Ver Arquivo
@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "0.96.0"
"atom-package-manager": "0.87.0"
}
}
+1 -1
Ver Arquivo
@@ -50,7 +50,7 @@ if [ $OS == 'Mac' ]; then
# If ATOM_PATH isn't a executable file, use spotlight to search for Atom
if [ ! -x "$ATOM_PATH/$ATOM_APP_NAME" ]; then
ATOM_PATH=$(mdfind "kMDItemCFBundleIdentifier == 'com.github.atom'" | grep -v ShipIt | head -1 | xargs dirname)
ATOM_PATH=$(mdfind "kMDItemCFBundleIdentifier == 'com.github.atom'" | head -1 | xargs dirname)
fi
# Exit if Atom can't be found
+3 -2
Ver Arquivo
@@ -1,6 +1,6 @@
require '../src/window'
Atom = require '../src/atom'
window.atom = Atom.loadOrCreate('spec')
atom = new Atom()
atom.show() unless atom.getLoadSettings().exitWhenDone
window.atom = atom
@@ -9,4 +9,5 @@ window.atom = atom
atom.openDevTools()
document.title = "Benchmark Suite"
runSpecSuite('../benchmark/benchmark-suite', atom.getLoadSettings().logFile)
benchmarkSuite = require.resolve('./benchmark-suite')
runSpecSuite(benchmarkSuite, true)
+2 -4
Ver Arquivo
@@ -1,9 +1,7 @@
require '../spec/spec-helper'
path = require 'path'
{$, Point} = require 'atom'
_ = require 'underscore-plus'
fs = require 'fs-plus'
{$, _, Point, fs} = require 'atom'
Project = require '../src/project'
TokenizedBuffer = require '../src/tokenized-buffer'
@@ -103,7 +101,7 @@ $.fn.resultOfTrigger = (type) ->
event.result
$.fn.enableKeymap = ->
@on 'keydown', (e) -> window.keymap.handleKeyEvent(e)
@on 'keydown', (e) => window.keymap.handleKeyEvent(e)
$.fn.attachToDom = ->
$('#jasmine-content').append(this)
+9 -9
Ver Arquivo
@@ -53,7 +53,7 @@ describe "editorView.", ->
describe "at-end.", ->
beforeEach ->
editorView.moveToBottom()
editorView.moveCursorToBottom()
benchmark "insert-delete", ->
editorView.insertText('"')
@@ -62,8 +62,8 @@ describe "editorView.", ->
describe "empty-vs-set-innerHTML.", ->
[firstRow, lastRow] = []
beforeEach ->
firstRow = editorView.getModel().getFirstVisibleScreenRow()
lastRow = editorView.getModel().getLastVisibleScreenRow()
firstRow = editorView.getFirstVisibleScreenRow()
lastRow = editorView.getLastVisibleScreenRow()
benchmark "build-gutter-html.", 1000, ->
editorView.gutter.renderLineNumbers(null, firstRow, lastRow)
@@ -97,13 +97,13 @@ describe "editorView.", ->
describe "multiple-lines.", ->
[firstRow, lastRow] = []
beforeEach ->
firstRow = editorView.getModel().getFirstVisibleScreenRow()
lastRow = editorView.getModel().getLastVisibleScreenRow()
firstRow = editorView.getFirstVisibleScreenRow()
lastRow = editorView.getLastVisibleScreenRow()
benchmark "cache-entire-visible-area", 100, ->
for i in [firstRow..lastRow]
line = editorView.lineElementForScreenRow(i)[0]
editorView.positionLeftForLineAndColumn(line, i, Math.max(0, editorView.getModel().lineTextForBufferRow(i).length))
editorView.positionLeftForLineAndColumn(line, i, Math.max(0, editorView.lineLengthForBufferRow(i)))
describe "text-rendering.", ->
beforeEach ->
@@ -178,7 +178,7 @@ describe "editorView.", ->
atom.workspaceView.openSync('huge.js')
benchmark "moving-to-eof.", 1, ->
editorView.moveToBottom()
editorView.moveCursorToBottom()
describe "on-first-line.", ->
benchmark "inserting-newline", 5, ->
@@ -195,11 +195,11 @@ describe "editorView.", ->
endPosition = null
beforeEach ->
editorView.moveToBottom()
editorView.moveCursorToBottom()
endPosition = editorView.getCursorScreenPosition()
benchmark "move-to-beginning-of-word", ->
editorView.moveToBeginningOfWord()
editorView.moveCursorToBeginningOfWord()
editorView.setCursorScreenPosition(endPosition)
benchmark "insert", ->
-56
Ver Arquivo
@@ -1,56 +0,0 @@
#!/usr/bin/env coffee
{spawn, exec} = require 'child_process'
fs = require 'fs'
os = require 'os'
path = require 'path'
_ = require 'underscore-plus'
temp = require 'temp'
directoryToOpen = temp.mkdirSync('browser-process-startup-')
socketPath = path.join(os.tmpdir(), 'atom.sock')
numberOfRuns = 10
deleteSocketFile = ->
try
fs.unlinkSync(socketPath) if fs.existsSync(socketPath)
catch error
console.error(error)
launchAtom = (callback) ->
deleteSocketFile()
cmd = 'atom'
args = ['--safe', '--new-window', '--foreground', directoryToOpen]
atomProcess = spawn(cmd, args)
output = ''
startupTimes = []
dataListener = (data) ->
output += data
if match = /App load time: (\d+)/.exec(output)
startupTime = parseInt(match[1])
atomProcess.stderr.removeListener 'data', dataListener
atomProcess.kill()
exec 'pkill -9 Atom', (error) ->
console.error(error) if error?
callback(startupTime)
atomProcess.stderr.on 'data', dataListener
startupTimes = []
collector = (startupTime) ->
startupTimes.push(startupTime)
if startupTimes.length < numberOfRuns
launchAtom(collector)
else
maxTime = _.max(startupTimes)
minTime = _.min(startupTimes)
totalTime = startupTimes.reduce (previousValue=0, currentValue) -> previousValue + currentValue
console.log "Startup Runs: #{startupTimes.length}"
console.log "First run time: #{startupTimes[0]}ms"
console.log "Max time: #{maxTime}ms"
console.log "Min time: #{minTime}ms"
console.log "Average time: #{Math.round(totalTime/startupTimes.length)}ms"
launchAtom(collector)
+9 -11
Ver Arquivo
@@ -60,7 +60,7 @@ module.exports = (grunt) ->
contentsDir = shellAppDir
appDir = path.join(shellAppDir, 'resources', 'app')
installDir ?= process.env.INSTALL_PREFIX ? '/usr/local'
killCommand ='pkill -9 atom'
killCommand ='pkill -9 Atom'
coffeeConfig =
glob_to_multiple:
@@ -130,7 +130,7 @@ module.exports = (grunt) ->
atom: {appDir, appName, symbolsDir, buildDir, contentsDir, installDir, shellAppDir}
docsOutputDir: 'docs/output'
docsOutputDir: 'docs/output/api'
coffee: coffeeConfig
@@ -144,7 +144,12 @@ module.exports = (grunt) ->
coffeelint:
options:
configFile: 'coffeelint.json'
no_empty_param_list:
level: 'error'
max_line_length:
level: 'ignore'
indentation:
level: 'ignore'
src: [
'dot-atom/**/*.coffee'
'exports/**/*.coffee'
@@ -225,16 +230,9 @@ module.exports = (grunt) ->
grunt.registerTask('compile', ['coffee', 'prebuild-less', 'cson', 'peg'])
grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint'])
grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
grunt.registerTask('ci', ['output-disk-space', 'download-atom-shell', 'build', 'dump-symbols', 'set-version', 'check-licenses', 'lint', 'test', 'codesign', 'publish-build'])
grunt.registerTask('docs', ['markdown:guides', 'build-docs'])
ciTasks = ['output-disk-space', 'download-atom-shell', 'build']
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
ciTasks.push('set-version', 'check-licenses', 'lint')
ciTasks.push('mkdeb') if process.platform is 'linux'
ciTasks.push('test') if process.platform isnt 'linux'
ciTasks.push('codesign', 'publish-build')
grunt.registerTask('ci', ciTasks)
defaultTasks = ['download-atom-shell', 'build', 'set-version']
defaultTasks.push 'install' unless process.platform is 'linux'
grunt.registerTask('default', defaultTasks)
+3 -4
Ver Arquivo
@@ -7,14 +7,13 @@
},
"dependencies": {
"async": "~0.2.9",
"donna": "1.0.1",
"tello": "1.0.3",
"biscotto": ">=2.1.1 <3.0",
"formidable": "~1.0.14",
"fs-plus": "2.x",
"github-releases": "~0.2.0",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.9",
"grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git#cfb99aa99811d52687969532bd5a98011ed95bfe",
"grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git",
"grunt-contrib-coffee": "~0.9.0",
"grunt-contrib-csslint": "~0.1.2",
"grunt-contrib-less": "~0.8.0",
@@ -27,7 +26,7 @@
"harmony-collections": "~0.3.8",
"json-front-matter": "~0.1.3",
"legal-eagle": "~0.4.0",
"minidump": "~0.8",
"minidump": "~0.7",
"normalize-package-data": "0.2.12",
"npm": "~1.4.5",
"rcedit": "~0.1.2",
+3
Ver Arquivo
@@ -148,6 +148,9 @@ module.exports = (grunt) ->
grunt.file.copy(sourcePath, path.resolve(appDir, '..', subDirectory, filename))
if process.platform is 'win32'
cp path.join('resources', 'win', 'msvcp100.dll'), path.join(shellAppDir, 'msvcp100.dll')
cp path.join('resources', 'win', 'msvcr100.dll'), path.join(shellAppDir, 'msvcr100.dll')
# Set up chocolatey ignore and gui files
fs.writeFileSync path.join(appDir, 'apm', 'node_modules', 'atom-package-manager', 'bin', 'node.exe.ignore'), ''
fs.writeFileSync path.join(appDir, 'node_modules', 'symbols-view', 'vendor', 'ctags-win32.exe.ignore'), ''
+151 -29
Ver Arquivo
@@ -1,40 +1,162 @@
path = require 'path'
async = require 'async'
fs = require 'fs-plus'
_ = require 'underscore-plus'
donna = require 'donna'
tello = require 'tello'
request = require 'request'
module.exports = (grunt) ->
getClassesToInclude = ->
modulesPath = path.resolve(__dirname, '..', '..', 'node_modules')
classes = {}
fs.traverseTreeSync modulesPath, (modulePath) ->
return false if modulePath.match(/node_modules/g).length > 1 # dont need the dependencies of the dependencies
return true unless path.basename(modulePath) is 'package.json'
return true unless fs.isFileSync(modulePath)
{rm} = require('./task-helpers')(grunt)
apiPath = path.join(path.dirname(modulePath), 'api.json')
if fs.isFileSync(apiPath)
_.extend(classes, grunt.file.readJSON(apiPath).classes)
true
classes
sortClasses = (classes) ->
sortedClasses = {}
for className in Object.keys(classes).sort()
sortedClasses[className] = classes[className]
sortedClasses
cmd = path.join('node_modules', '.bin', 'coffee')
commonArgs = [path.join('build', 'node_modules', '.bin', 'biscotto'), '--']
opts =
stdio: 'inherit'
grunt.registerTask 'build-docs', 'Builds the API docs in src', ->
done = @async()
docsOutputDir = grunt.config.get('docsOutputDir')
metadata = donna.generateMetadata(['.'])
api = tello.digest(metadata)
_.extend(api.classes, getClassesToInclude())
api.classes = sortClasses(api.classes)
downloadIncludes (error, includePaths) ->
if error?
done(error)
else
rm(docsOutputDir)
args = [
commonArgs...
'--title', 'Atom API Documentation'
'-o', docsOutputDir
'-r', 'docs/README.md'
'--stability', '1'
'src/'
includePaths...
]
grunt.util.spawn({cmd, args, opts}, done)
apiJson = JSON.stringify(api, null, 2)
apiJsonPath = path.join(docsOutputDir, 'api.json')
grunt.file.write(apiJsonPath, apiJson)
grunt.registerTask 'lint-docs', 'Generate stats about the doc coverage', ->
done = @async()
downloadIncludes (error, includePaths) ->
if error?
done(error)
else
args = [
commonArgs...
'--noOutput'
'src/'
includePaths...
]
grunt.util.spawn({cmd, args, opts}, done)
grunt.registerTask 'missing-docs', 'Generate stats about the doc coverage', ->
done = @async()
downloadIncludes (error, includePaths) ->
if error?
done(error)
else
args = [
commonArgs...
'--noOutput'
'--missing'
'src/'
includePaths...
]
grunt.util.spawn({cmd, args, opts}, done)
grunt.registerTask 'copy-docs', 'Copies over latest API docs to atom-docs', ->
done = @async()
fetchTag = (args..., callback) ->
cmd = 'git'
args = ['describe', '--abbrev=0', '--tags']
grunt.util.spawn {cmd, args}, (error, result) ->
if error?
callback(error)
else
callback(null, String(result).trim())
copyDocs = (tag, callback) ->
cmd = 'cp'
args = ['-r', 'docs/output/', "../atom.io/public/docs/api/#{tag}/"]
fs.exists "../atom.io/public/docs/api/", (exists) ->
if exists
grunt.util.spawn {cmd, args}, (error, result) ->
if error?
callback(error)
else
callback(null, tag)
else
grunt.log.error "../atom.io/public/docs/api/ doesn't exist"
return false
grunt.util.async.waterfall [fetchTag, copyDocs], done
grunt.registerTask 'deploy-docs', 'Publishes latest API docs to atom-docs.githubapp.com', ->
done = @async()
docsRepoArgs = ['--work-tree=../atom-docs/', '--git-dir=../atom-docs/.git/']
fetchTag = (args..., callback) ->
cmd = 'git'
args = ['describe', '--abbrev=0', '--tags']
grunt.util.spawn {cmd, args}, (error, result) ->
if error?
callback(error)
else
callback(null, String(result).trim().split('.')[0..1].join('.'))
stageDocs = (tag, callback) ->
cmd = 'git'
args = [docsRepoArgs..., 'add', "public/#{tag}"]
grunt.util.spawn({cmd, args, opts}, callback)
fetchSha = (args..., callback) ->
cmd = 'git'
args = ['rev-parse', 'HEAD']
grunt.util.spawn {cmd, args}, (error, result) ->
if error?
callback(error)
else
callback(null, String(result).trim())
commitChanges = (sha, callback) ->
cmd = 'git'
args = [docsRepoArgs..., 'commit', "-m Update API docs to #{sha}"]
grunt.util.spawn({cmd, args, opts}, callback)
pushOrigin = (args..., callback) ->
cmd = 'git'
args = [docsRepoArgs..., 'push', 'origin', 'master']
grunt.util.spawn({cmd, args, opts}, callback)
pushHeroku = (args..., callback) ->
cmd = 'git'
args = [docsRepoArgs..., 'push', 'heroku', 'master']
grunt.util.spawn({cmd, args, opts}, callback)
grunt.util.async.waterfall [fetchTag, stageDocs, fetchSha, commitChanges, pushOrigin, pushHeroku], done
downloadFileFromRepo = ({repo, file}, callback) ->
uri = "https://raw.github.com/atom/#{repo}/master/#{file}"
request uri, (error, response, contents) ->
return callback(error) if error?
downloadPath = path.join('docs', 'includes', repo, file)
fs.writeFile downloadPath, contents, (error) ->
callback(error, downloadPath)
downloadIncludes = (callback) ->
includes = [
{repo: 'atom-keymap', file: 'src/keymap-manager.coffee'}
{repo: 'atom-keymap', file: 'src/key-binding.coffee'}
{repo: 'first-mate', file: 'src/grammar.coffee'}
{repo: 'first-mate', file: 'src/grammar-registry.coffee'}
{repo: 'node-pathwatcher', file: 'src/directory.coffee'}
{repo: 'node-pathwatcher', file: 'src/file.coffee'}
{repo: 'space-pen', file: 'src/space-pen.coffee'}
{repo: 'text-buffer', file: 'src/marker.coffee'}
{repo: 'text-buffer', file: 'src/point.coffee'}
{repo: 'text-buffer', file: 'src/range.coffee'}
{repo: 'text-buffer', file: 'src/text-buffer.coffee'}
{repo: 'theorist', file: 'src/model.coffee'}
]
async.map(includes, downloadFileFromRepo, callback)
+9 -21
Ver Arquivo
@@ -13,16 +13,8 @@ module.exports = (grunt) ->
grunt.file.write(outputPath, filled)
outputPath
getInstalledSize = (buildDir, callback) ->
cmd = 'du'
args = ['-sk', path.join(buildDir, 'Atom')]
spawn {cmd, args}, (error, {stdout}) ->
installedSize = stdout.split(/\s+/)?[0] or '200000' # default to 200MB
callback(null, installedSize)
grunt.registerTask 'mkdeb', 'Create debian package', ->
done = @async()
buildDir = grunt.config.get('atom.buildDir')
if process.arch is 'ia32'
arch = 'i386'
@@ -36,17 +28,13 @@ module.exports = (grunt) ->
maintainer = 'GitHub <atom@github.com>'
installDir = '/usr'
iconName = 'atom'
getInstalledSize buildDir, (error, installedSize) ->
data = {name, version, description, section, arch, maintainer, installDir, iconName, installedSize}
controlFilePath = fillTemplate(path.join('resources', 'linux', 'debian', 'control'), data)
desktopFilePath = fillTemplate(path.join('resources', 'linux', 'Atom.desktop'), data)
icon = path.join('resources', 'atom.png')
data = {name, version, description, section, arch, maintainer, installDir, iconName}
cmd = path.join('script', 'mkdeb')
args = [version, arch, controlFilePath, desktopFilePath, icon, buildDir]
spawn {cmd, args}, (error) ->
if error?
done(error)
else
grunt.log.ok "Created #{buildDir}/atom-#{version}-#{arch}.deb"
done()
controlFilePath = fillTemplate(path.join('resources', 'linux', 'debian', 'control'), data)
desktopFilePath = fillTemplate(path.join('resources', 'linux', 'Atom.desktop'), data)
icon = path.join('resources', 'atom.png')
buildDir = grunt.config.get('atom.buildDir')
cmd = path.join('script', 'mkdeb')
args = [version, arch, controlFilePath, desktopFilePath, icon, buildDir]
spawn({cmd, args}, done)
+13 -33
Ver Arquivo
@@ -17,7 +17,6 @@ defaultHeaders =
module.exports = (gruntObject) ->
grunt = gruntObject
{cp} = require('./task-helpers')(grunt)
grunt.registerTask 'publish-build', 'Publish the built app', ->
return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
@@ -25,10 +24,8 @@ module.exports = (gruntObject) ->
tasks.unshift('build-docs', 'prepare-docs') if process.platform is 'darwin'
grunt.task.run(tasks)
grunt.registerTask 'prepare-docs', 'Move api.json to atom-api.json', ->
docsOutputDir = grunt.config.get('docsOutputDir')
buildDir = grunt.config.get('atom.buildDir')
cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json')
grunt.registerTask 'prepare-docs', 'Move the build docs to the build dir', ->
fs.copySync(grunt.config.get('docsOutputDir'), path.join(grunt.config.get('atom.buildDir'), 'atom-docs'))
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
done = @async()
@@ -45,33 +42,16 @@ module.exports = (gruntObject) ->
uploadAssets(release, buildDir, assets, done)
getAssets = ->
switch process.platform
when 'darwin'
[
{assetName: 'atom-mac.zip', sourcePath: 'Atom.app'}
{assetName: 'atom-mac-symbols.zip', sourcePath: 'Atom.breakpad.syms'}
{assetName: 'atom-api.json', sourcePath: 'atom-api.json'}
]
when 'win32'
[
{assetName: 'atom-windows.zip', sourcePath: 'Atom'}
]
when 'linux'
buildDir = grunt.config.get('atom.buildDir')
if process.arch is 'ia32'
arch = 'i386'
else
arch = 'amd64'
{version} = grunt.file.readJSON('package.json')
sourcePath = "#{buildDir}/atom-#{version}-#{arch}.deb"
assetName = "atom-#{arch}.deb"
{cp} = require('./task-helpers')(grunt)
cp sourcePath, path.join(buildDir, assetName)
[
{assetName, sourcePath}
]
if process.platform is 'darwin'
[
{assetName: 'atom-mac.zip', sourcePath: 'Atom.app'}
{assetName: 'atom-mac-symbols.zip', sourcePath: 'Atom.breakpad.syms'}
{assetName: 'atom-docs.zip', sourcePath: 'atom-docs'}
]
else
[
{assetName: 'atom-windows.zip', sourcePath: 'Atom'}
]
logError = (message, error, details) ->
grunt.log.error(message)
@@ -90,7 +70,7 @@ zipAssets = (buildDir, assets, callback) ->
callback(error)
tasks = []
for {assetName, sourcePath} in assets when path.extname(assetName) is '.zip'
for {assetName, sourcePath} in assets
fs.removeSync(path.join(buildDir, assetName))
tasks.push(zip.bind(this, buildDir, sourcePath, assetName))
async.parallel(tasks, callback)
+2 -2
Ver Arquivo
@@ -45,8 +45,8 @@ module.exports = (grunt) ->
strings =
CompanyName: 'GitHub, Inc.'
FileDescription: 'Atom'
LegalCopyright: 'Copyright (C) 2014 GitHub, Inc. All rights reserved'
FileDescription: 'The hackable editor'
LegalCopyright: 'Copyright (C) 2013 GitHub, Inc. All rights reserved'
ProductName: 'Atom'
ProductVersion: version
+16 -19
Ver Arquivo
@@ -2,6 +2,7 @@ fs = require 'fs'
path = require 'path'
_ = require 'underscore-plus'
async = require 'async'
module.exports = (grunt) ->
@@ -9,27 +10,18 @@ module.exports = (grunt) ->
packageSpecQueue = null
getAppPath = ->
contentsDir = grunt.config.get('atom.contentsDir')
switch process.platform
when 'darwin'
path.join(contentsDir, 'MacOS', 'Atom')
when 'linux'
path.join(contentsDir, 'atom')
when 'win32'
path.join(contentsDir, 'atom.exe')
runPackageSpecs = (callback) ->
failedPackages = []
rootDir = grunt.config.get('atom.shellAppDir')
contentsDir = grunt.config.get('atom.contentsDir')
resourcePath = process.cwd()
appPath = getAppPath()
# Ensure application is executable on Linux
fs.chmodSync(appPath, '755') if process.platform is 'linux'
if process.platform is 'darwin'
appPath = path.join(contentsDir, 'MacOS', 'Atom')
else if process.platform is 'win32'
appPath = path.join(contentsDir, 'atom.exe')
packageSpecQueue = async.queue (packagePath, callback) ->
if process.platform in ['darwin', 'linux']
if process.platform is 'darwin'
options =
cmd: appPath
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"]
@@ -65,11 +57,15 @@ module.exports = (grunt) ->
packageSpecQueue.drain = -> callback(null, failedPackages)
runCoreSpecs = (callback) ->
appPath = getAppPath()
contentsDir = grunt.config.get('atom.contentsDir')
if process.platform is 'darwin'
appPath = path.join(contentsDir, 'MacOS', 'Atom')
else if process.platform is 'win32'
appPath = path.join(contentsDir, 'atom.exe')
resourcePath = process.cwd()
coreSpecsPath = path.resolve('spec')
if process.platform in ['darwin', 'linux']
if process.platform is 'darwin'
options =
cmd: appPath
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
@@ -94,7 +90,7 @@ module.exports = (grunt) ->
# TODO: This should really be parallel on both platforms, however our
# fixtures step on each others toes currently.
if process.platform in ['darwin', 'linux']
if process.platform is 'darwin'
method = async.parallel
else if process.platform is 'win32'
method = async.series
@@ -109,6 +105,7 @@ module.exports = (grunt) ->
grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0
if process.platform is 'win32' and process.env.JANKY_SHA1
done()
# Package specs are still flaky on Windows CI
done(!coreSpecFailed)
else
done(!coreSpecFailed and failedPackages.length == 0)
+1 -2
Ver Arquivo
@@ -53,9 +53,8 @@ module.exports = (grunt) ->
proc = childProcess.spawn(options.cmd, options.args, options.opts)
proc.stdout.on 'data', (data) -> stdout.push(data.toString())
proc.stderr.on 'data', (data) -> stderr.push(data.toString())
proc.on 'error', (processError) -> error ?= processError
proc.on 'close', (exitCode, signal) ->
error ?= new Error(signal) if exitCode != 0
error = new Error(signal) if exitCode != 0
results = {stderr: stderr.join(''), stdout: stdout.join(''), code: exitCode}
grunt.log.error results.stderr if exitCode != 0
callback(error, results, exitCode)
-14
Ver Arquivo
@@ -1,14 +0,0 @@
{
"max_line_length": {
"level": "ignore"
},
"no_empty_param_list": {
"level": "error"
},
"arrow_spacing": {
"level": "error"
},
"no_interpolation_in_single_quotes": {
"level": "error"
}
}
+1 -1
Ver Arquivo
@@ -56,7 +56,7 @@ character of the current line:
class EditorView
listenForEvents: ->
@command 'editor:move-to-first-character-of-line', =>
@editor.moveToFirstCharacterOfLine()
@editor.moveCursorToFirstCharacterOfLine()
```
The `::command` method is basically an enhanced version of jQuery's `::on`
+112
Ver Arquivo
@@ -0,0 +1,112 @@
# API Guidelines
__Note__: These are not all in practice yet. We are still sorting this out, and plan to move the entire API this way.
## General Guidelines
We should strive to have only one way to do something, and make it clear what that way is.
__Bad__
* Several ways to get the `Editor` model from the view.
* `EditorView.editor`
* `EditorView.getModel()`
* `EditorView.getEditor()`
* Delegated methods on the views when there is a model counterpart
* `Editor.toggleSoftTabs()`
* `EditorView.toggleSoftTabs()`
__Good__
* One way to get the `Editor` model from the view
* `EditorView.getModel()`
* Use the method on the model
* `Editor.toggleSoftTabs()`
* One clear way to subscribe to events
* `subscription = @subscribe thing, 'event', -> ...`
* One clear way to unsubscribe to events
* `subscription.off()` only; not `thing.off 'event', -> ...`
## Essential vs Extended
There are two groups of classes / methods. We break them up to facilitate a gentle introduction into the API and help authors build a knowledge foundation more quickly.
### Essential
These are classes, methods, and concepts nearly every package author will need to know about. Need to create commands? Subscribe to atom events? Get a reference to all the editors? Highlight a line in the editor? Patterns and methods for these things will be explained in the essential API.
We want to keep the essential API minimal and focused.
### Extended
The extended API contains The Power. Need to move one cursor independent of the others? Want to do some processing on the markers? You can do it with the extended API.
## View / Model
Operations on Views should be limited to DOM manipulation only. A package author should only need access to, say, the `EditorView` when it needs to directly modify the `EditorView`'s DOM.
## Properties
No public properties. Use methods instead.
## Methods
### Naming
We strive to fit the [Objective C][naming] naming conventions for the sake of readability.
* Be descriptive, always write out the whole word. `selection` not `sel`, `cursor` not `cur`.
* Describe the arguments names in the method name eg. `decorationsForMarker(marker)`, `objectAtIndex(index)`
* Use `get` prefix only when there are no arguments `getCursors()`, `getLastCursor()`, `cursorForMarker(marker)`
* Prefix bool methods with `is` eg. `isDefault()`
Array accessor methods would be written as follows
```coffee
getObjects()
addObject(object)
removeObject(object)
removeObjectAtIndex(index)
objectAtIndex(index)
objectsForThing(thing)
objectForThing(thing)
```
## Events
There will be no `off()` method on objects. `on()` will return a subscription object which contains the `off()` method.
* Events should be emitted with one event object as an argument, rather than a bunch of arguments.
* If an event is cancelable, it will provide a `cancel` function in the event object.
### Naming
Past tense. ???
## Documentation
Comment doc strings on methods, events, etc
* how to doc sections?
* events?
* props?
* callback args?
* option hashes?
### Method organization
* All methods in classes should be grouped into sections by usage pattern.
* Methods within a section should be ordered with the most commonly used methods at the top. `Essential` methods always go above `Extended` methods.
* Sections should be ordered in the class with the most commonly used sections at the top.
A section:
```coffee
###
Section: Reading Text
###
# Essential: Returns a {String} representing the entire contents of the editor.
getText: -> @buffer.getText()
```
[naming]:https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingMethods.html
+20 -62
Ver Arquivo
@@ -6,26 +6,23 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++ toolchain
* [Git](http://git-scm.com/)
* [Node.js](http://nodejs.org/download/) v0.10.x
* [npm](http://www.npmjs.org/) v1.4.x (bundled with Node.js)
* git
* [node.js](http://nodejs.org/download/) v0.10.x
* [npm](http://www.npmjs.org/) v1.4.x (bundled with node.js)
* `npm -v` to check the version.
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2.
* You might need to run this command as `sudo`, depending on how you have set up [npm](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
* development headers for [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring)
* libgnome-keyring-dev
### Ubuntu / Debian
* `sudo apt-get install build-essential git libgnome-keyring-dev`
* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
* Instructions for [node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
### Fedora
* `sudo yum --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel`
* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora).
* Instructions for [node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora).
### Arch
* `sudo pacman -S base-devel git nodejs libgnome-keyring`
* `export PYTHON=/usr/bin/python2` before building Atom.
@@ -33,56 +30,26 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
If you have problems with permissions don't forget to prefix with `sudo`
1. Clone the Atom repository:
```sh
git clone https://github.com/atom/atom
cd atom
```
2. Checkout the latest Atom release:
```sh
git fetch
git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
```
3. Build Atom:
```sh
script/build
```
This will create the atom application at `$TMPDIR/atom-build/Atom`.
4. Install the `atom` and `apm` commands to `/usr/local/bin` by executing:
```sh
sudo script/grunt install
```
5. *Optionally*, you may generate a `.deb` package at `$TMPDIR/atom-build`:
```sh
script/grunt mkdeb
```
Use the newly installed Atom by fully quitting Atom and then reopening.
## Advanced Options
### Custom install directory
Create the atom application at `$TMPDIR/atom-build/Atom`:
```sh
sudo script/grunt install --install-dir /install/atom/here
script/build
```
### Custom build directory
Install the `atom` and `apm` commands to `/usr/local/bin`:
```sh
script/build --build-dir /build/atom/here
sudo script/grunt install
```
Generate a `.deb` package at `$TMPDIR/atom-build`: (*optional*)
```sh
script/grunt mkdeb
```
Use the newly installed atom by restarting any running atom instances.
## Troubleshooting
### Exception: "TypeError: Unable to watch path"
@@ -103,7 +70,7 @@ this is the reason for this error you can issue
and restart Atom. If Atom now works fine, you can make this setting permanent:
```sh
echo 32768 | sudo tee -a /proc/sys/fs/inotify/max_user_watches
echo 32768 > /proc/sys/fs/inotify/max_user_watches
```
See also https://github.com/atom/atom/issues/2082.
@@ -111,19 +78,10 @@ See also https://github.com/atom/atom/issues/2082.
### /usr/bin/env: node: No such file or directory
If you get this notice when attempting to `script/build`, you either do not
have Node.js installed, or node isn't identified as Node.js on your machine.
have nodejs installed, or node isn't identified as nodejs on your machine.
If it's the latter, entering `sudo ln -s /usr/bin/nodejs /usr/bin/node` into
your terminal may fix the issue.
#### You can also use Alternatives
On some variants (mostly Debian based distros) it's preferable for you to use
Alternatives so that changes to the binary paths can be fixed or altered easily:
```sh
sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 1 --slave /usr/bin/js js /usr/bin/nodejs
```
### Linux build error reports in atom/atom
* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Alinux&type=Issues)
to get a list of reports about build errors on Linux.
-13
Ver Arquivo
@@ -66,19 +66,6 @@ If none of this works, do install Github for Windows and use its Git shell. Make
* https://github.com/TooTallNate/node-gyp/issues/297
* https://code.google.com/p/gyp/issues/detail?id=393
* `script/build` stops at installing runas with 'Failed at the runas@0.5.4 install script.'
See the next item.
* `error MSB8020: The build tools for Visual Studio 2010 (Platform Toolset = 'v100') cannot be found.`
* If you're building atom with Visual Studio 2013 try executing the following
command in your Git shell and then re-run `script/build`:
```
$env:GYP_MSVS_VERSION=2013
```
* Other `node-gyp` errors on first build attempt, even though the right node and python versions are installed.
* Do try the build command one more time, as experience shows it often works on second try in many of these cases.
+3 -3
Ver Arquivo
@@ -53,7 +53,7 @@ module.exports =
convert: ->
# This assumes the active pane item is an editor
editor = atom.workspace.getActivePaneItem()
editor = atom.workspace.activePaneItem
editor.insertText('Hello, World!')
```
@@ -131,8 +131,8 @@ inserting 'Hello, World!' convert the selected text to ASCII art.
```coffeescript
convert: ->
# This assumes the active pane item is an editor
editor = atom.workspace.getActivePaneItem()
selection = editor.getLastSelection()
editor = atom.workspace.activePaneItem
selection = editor.getSelection()
figlet = require 'figlet'
figlet selection.getText(), {font: "Larry 3D 2"}, (error, asciiArt) ->
+1 -1
Ver Arquivo
@@ -11,4 +11,4 @@
# atom.workspaceView.eachEditorView (editorView) ->
# editor = editorView.getEditor()
# if path.extname(editor.getPath()) is '.md'
# editor.setSoftWrapped(true)
# editor.setSoftWrap(true)
+1 -4
Ver Arquivo
@@ -13,9 +13,6 @@
# 'enter': 'editor:newline'
#
# '.workspace':
# 'ctrl-shift-p': 'core:move-up'
# 'ctrl-P': 'core:move-up'
# 'ctrl-p': 'core:move-down'
#
# You can find more information about keymaps in these guides:
# * https://atom.io/docs/latest/customizing-atom#customizing-key-bindings
# * https://atom.io/docs/latest/advanced/keymaps
+5 -7
Ver Arquivo
@@ -1,17 +1,12 @@
{Point, Range} = require 'text-buffer'
{deprecate} = require 'grim'
module.exports =
BufferedNodeProcess: require '../src/buffered-node-process'
BufferedProcess: require '../src/buffered-process'
GitRepository: require '../src/git-repository'
Git: require '../src/git'
Point: Point
Range: Range
Object.defineProperty module.exports, 'Git', get: ->
deprecate "Please require `GitRepository` instead of `Git`: `{GitRepository} = require 'atom'`"
module.exports.GitRepository
# The following classes can't be used from a Task handler and should therefore
# only be exported when not running as a child node process
unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
@@ -20,7 +15,10 @@ unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
module.exports.$ = $
module.exports.$$ = $$
module.exports.$$$ = $$$
module.exports.EditorView = require '../src/editor-view'
if atom.config.get('core.useReactMiniEditors')
module.exports.EditorView = require '../src/react-editor-view'
else
module.exports.EditorView = require '../src/editor-view'
module.exports.ScrollView = require '../src/scroll-view'
module.exports.SelectListView = require '../src/select-list-view'
module.exports.Task = require '../src/task'
+2
Ver Arquivo
@@ -12,6 +12,7 @@
# Sublime Parity
'tab': 'editor:indent'
'enter': 'editor:newline'
'num-enter': 'editor:newline'
'shift-tab': 'editor:outdent-selected-rows'
'ctrl-K': 'editor:delete-line'
@@ -26,6 +27,7 @@
'tab': 'core:focus-next'
'shift-tab': 'core:focus-previous'
'enter': 'native!'
'num-enter': 'native!'
'backspace': 'native!'
'shift-backspace': 'native!'
'delete': 'native!'
+2 -3
Ver Arquivo
@@ -21,6 +21,7 @@
'cmd-O': 'application:open-dev'
'cmd-alt-ctrl-s': 'application:run-all-specs'
'enter': 'core:confirm'
'num-enter': 'core:confirm'
'escape': 'core:cancel'
'up': 'core:move-up'
'down': 'core:move-down'
@@ -119,7 +120,7 @@
'cmd-shift-right': 'editor:select-to-end-of-line'
'alt-backspace': 'editor:delete-to-beginning-of-word'
'alt-delete': 'editor:delete-to-end-of-word'
'ctrl-a': 'editor:move-to-first-character-of-line'
'ctrl-a': 'editor:move-to-beginning-of-line'
'ctrl-e': 'editor:move-to-end-of-line'
'ctrl-k': 'editor:cut-to-end-of-line'
@@ -143,8 +144,6 @@
# Sublime Parity
'cmd-enter': 'editor:newline-below'
'cmd-shift-enter': 'editor:newline-above'
'alt-enter': 'editor:newline'
'shift-enter': 'editor:newline'
'cmd-]': 'editor:indent-selected-rows'
'cmd-[': 'editor:outdent-selected-rows'
'ctrl-cmd-up': 'editor:move-line-up'
+2 -3
Ver Arquivo
@@ -1,6 +1,7 @@
'body':
# Atom Specific
'enter': 'core:confirm'
'num-enter': 'core:confirm'
'escape': 'core:cancel'
'up': 'core:move-up'
'down': 'core:move-down'
@@ -10,12 +11,10 @@
'ctrl-shift-i': 'window:toggle-dev-tools'
'ctrl-alt-p': 'window:run-package-specs'
'ctrl-alt-s': 'application:run-all-specs'
'ctrl-alt-o': 'application:open-dev'
'ctrl-shift-o': 'application:open-folder'
'ctrl-shift-o': 'application:open-dev'
'F11': 'window:toggle-full-screen'
# Sublime Parity
'ctrl-,': 'application:show-settings'
'ctrl-N': 'application:new-window'
'ctrl-W': 'window:close'
'ctrl-o': 'application:open-file'
+2 -1
Ver Arquivo
@@ -5,6 +5,7 @@
# Atom Specific
'enter': 'core:confirm'
'num-enter': 'core:confirm'
'escape': 'core:cancel'
'up': 'core:move-up'
'down': 'core:move-down'
@@ -25,8 +26,8 @@
'ctrl-n': 'application:new-file'
'ctrl-s': 'core:save'
'ctrl-S': 'core:save-as'
'ctrl-f4': 'core:close'
'ctrl-w': 'core:close'
'ctrl-f4': 'core:close'
'ctrl-z': 'core:undo'
'ctrl-shift-z': 'core:redo'
'ctrl-y': 'core:redo'
-12
Ver Arquivo
@@ -18,8 +18,6 @@
{ type: 'separator' }
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
{ type: 'separator' }
{ label: 'Services', submenu: [] }
{ type: 'separator' }
{ label: 'Hide Atom', command: 'application:hide' }
{ label: 'Hide Others', command: 'application:hide-other-applications' }
{ label: 'Show All', command: 'application:unhide-all-applications' }
@@ -194,13 +192,3 @@
]
}
]
'context-menu':
'.overlayer':
'Undo': 'core:undo'
'Redo': 'core:redo'
'Cut': 'core:cut'
'Copy': 'core:copy'
'Paste': 'core:paste'
'Delete': 'core:delete'
'Select All': 'core:select-all'
+12 -18
Ver Arquivo
@@ -8,6 +8,8 @@
{ label: 'Open Folder...', command: 'application:open-folder' }
{ label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' }
{ type: 'separator' }
{ label: '&Preferences...', command: 'application:show-settings' }
{ type: 'separator' }
{ label: '&Save', command: 'core:save' }
{ label: 'Save &As...', command: 'core:save-as' }
{ label: 'Save A&ll', command: 'window:save-all' }
@@ -78,14 +80,6 @@
{ label: 'Fold Level 9', command: 'editor:fold-at-indent-level-9' }
]
}
{ type: 'separator' }
{ label: '&Preferences', command: 'application:show-settings' }
{ label: 'Open Your Config', command: 'application:open-your-config' }
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
{ type: 'separator' }
]
}
@@ -139,6 +133,16 @@
submenu: []
}
{
label: '&Window'
submenu: [
{ label: 'Mi&nimize', command: 'application:minimize' }
{ label: 'Ma&ximize', command: 'application:zoom' }
{ type: 'separator' }
{ label: 'Bring &All to Front', command: 'application:bring-all-windows-to-front' }
]
}
{
label: '&Help'
submenu: [
@@ -151,13 +155,3 @@
]
}
]
'context-menu':
'.overlayer':
'Undo': 'core:undo'
'Redo': 'core:redo'
'Cut': 'core:cut'
'Copy': 'core:copy'
'Paste': 'core:paste'
'Delete': 'core:delete'
'Select All': 'core:select-all'
+10 -10
Ver Arquivo
@@ -157,6 +157,16 @@
submenu: []
}
{
label: '&Window'
submenu: [
{ label: 'Mi&nimize', command: 'application:minimize' }
{ label: 'Ma&ximize', command: 'application:zoom' }
{ type: 'separator' }
{ label: 'Bring &All to Front', command: 'application:bring-all-windows-to-front' }
]
}
{
label: '&Help'
submenu: [
@@ -169,13 +179,3 @@
]
}
]
'context-menu':
'.overlayer':
'Undo': 'core:undo'
'Redo': 'core:redo'
'Cut': 'core:cut'
'Copy': 'core:copy'
'Paste': 'core:paste'
'Delete': 'core:delete'
'Select All': 'core:select-all'
+69 -70
Ver Arquivo
@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "0.130.0",
"version": "0.121.0",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@@ -17,129 +17,128 @@
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
}
],
"atomShellVersion": "0.16.2",
"atomShellVersion": "0.15.0",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^2.1.1",
"atom-keymap": "^1.0.0",
"bootstrap": "git+https://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
"clear-cut": "0.4.0",
"coffee-script": "1.7.0",
"coffeestack": "0.7.0",
"delegato": "^1",
"emissary": "^1.3.1",
"event-kit": "0.7.2",
"first-mate": "^2.1.2",
"emissary": "^1.2.1",
"first-mate": "^2.0.1",
"fs-plus": "^2.2.6",
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
"git-utils": "^2.1.4",
"grim": "0.12.0",
"fuzzaldrin": "^1.1",
"git-utils": "^2.1.3",
"grim": "0.11.0",
"guid": "0.0.10",
"jasmine-tagged": "^1.1.2",
"less-cache": "0.15.0",
"less-cache": "0.13.0",
"mixto": "^1",
"mkdirp": "0.3.5",
"nslog": "^1.0.1",
"oniguruma": "^3.0.4",
"oniguruma": "^3.0.3",
"optimist": "0.4.0",
"pathwatcher": "^2.1.3",
"pathwatcher": "^2.0.6",
"property-accessors": "^1",
"q": "^1.0.1",
"random-words": "0.0.1",
"react-atom-fork": "^0.11.1",
"reactionary-atom-fork": "^1.0.0",
"runas": "1.0.1",
"scandal": "1.0.2",
"scandal": "1.0.0",
"scoped-property-store": "^0.9.0",
"scrollbar-style": "^1.0.2",
"scrollbar-style": "^1.0.1",
"season": "^1.0.2",
"semver": "1.1.4",
"serializable": "^1",
"space-pen": "3.4.7",
"space-pen": "3.2.0",
"temp": "0.7.0",
"text-buffer": "^3.2.6",
"theorist": "^1.0.2",
"text-buffer": "^3.0.0",
"theorist": "^1",
"underscore-plus": "^1.5.1",
"vm-compatibility-layer": "0.1.0"
},
"packageDependencies": {
"atom-dark-syntax": "0.19.0",
"atom-dark-ui": "0.35.0",
"atom-dark-ui": "0.33.0",
"atom-light-syntax": "0.20.0",
"atom-light-ui": "0.30.0",
"base16-tomorrow-dark-theme": "0.21.0",
"atom-light-ui": "0.29.0",
"base16-tomorrow-dark-theme": "0.20.0",
"base16-tomorrow-light-theme": "0.4.0",
"solarized-dark-syntax": "0.22.0",
"solarized-light-syntax": "0.12.0",
"archive-view": "0.37.0",
"autocomplete": "0.32.0",
"autoflow": "0.18.0",
"autosave": "0.17.0",
"background-tips": "0.16.0",
"bookmarks": "0.28.0",
"bracket-matcher": "0.58.0",
"command-palette": "0.25.0",
"deprecation-cop": "0.10.0",
"dev-live-reload": "0.34.0",
"exception-reporting": "0.20.0",
"archive-view": "0.35.0",
"autocomplete": "0.29.0",
"autoflow": "0.17.0",
"autosave": "0.14.0",
"background-tips": "0.15.0",
"bookmarks": "0.27.0",
"bracket-matcher": "0.51.0",
"command-palette": "0.24.0",
"deprecation-cop": "0.7.0",
"dev-live-reload": "0.33.0",
"exception-reporting": "0.19.0",
"feedback": "0.33.0",
"find-and-replace": "0.138.0",
"fuzzy-finder": "0.58.0",
"git-diff": "0.39.0",
"go-to-line": "0.25.0",
"grammar-selector": "0.34.0",
"find-and-replace": "0.127.0",
"fuzzy-finder": "0.57.0",
"git-diff": "0.37.0",
"go-to-line": "0.23.0",
"grammar-selector": "0.27.0",
"image-view": "0.36.0",
"incompatible-packages": "0.9.0",
"keybinding-resolver": "0.20.0",
"incompatible-packages": "0.3.0",
"keybinding-resolver": "0.18.0",
"link": "0.25.0",
"markdown-preview": "0.103.0",
"metrics": "0.34.0",
"open-on-github": "0.30.0",
"markdown-preview": "0.94.0",
"metrics": "0.33.0",
"open-on-github": "0.29.0",
"package-generator": "0.31.0",
"release-notes": "0.36.0",
"settings-view": "0.145.0",
"snippets": "0.52.0",
"spell-check": "0.42.0",
"status-bar": "0.45.0",
"styleguide": "0.30.0",
"symbols-view": "0.66.0",
"tabs": "0.52.0",
"settings-view": "0.137.0",
"snippets": "0.50.0",
"spell-check": "0.40.0",
"status-bar": "0.41.0",
"styleguide": "0.29.0",
"symbols-view": "0.63.0",
"tabs": "0.48.0",
"timecop": "0.22.0",
"tree-view": "0.126.0",
"tree-view": "0.112.0",
"update-package-dependencies": "0.6.0",
"welcome": "0.18.0",
"welcome": "0.17.0",
"whitespace": "0.25.0",
"wrap-guide": "0.22.0",
"language-c": "0.28.0",
"language-coffee-script": "0.34.0",
"wrap-guide": "0.21.0",
"language-c": "0.26.0",
"language-coffee-script": "0.28.0",
"language-css": "0.17.0",
"language-gfm": "0.50.0",
"language-gfm": "0.44.0",
"language-git": "0.9.0",
"language-go": "0.17.0",
"language-html": "0.26.0",
"language-hyperlink": "0.12.0",
"language-go": "0.16.0",
"language-html": "0.22.0",
"language-hyperlink": "0.10.0",
"language-java": "0.11.0",
"language-javascript": "0.40.0",
"language-javascript": "0.39.0",
"language-json": "0.8.0",
"language-less": "0.15.0",
"language-make": "0.12.0",
"language-mustache": "0.10.0",
"language-less": "0.13.0",
"language-make": "0.10.0",
"language-objective-c": "0.11.0",
"language-perl": "0.9.0",
"language-php": "0.16.0",
"language-php": "0.15.0",
"language-property-list": "0.7.0",
"language-python": "0.19.0",
"language-ruby": "0.38.0",
"language-ruby-on-rails": "0.18.0",
"language-sass": "0.21.0",
"language-python": "0.18.0",
"language-ruby": "0.33.0",
"language-ruby-on-rails": "0.15.0",
"language-sass": "0.14.0",
"language-shellscript": "0.8.0",
"language-source": "0.8.0",
"language-sql": "0.11.0",
"language-sql": "0.9.0",
"language-text": "0.6.0",
"language-todo": "0.11.0",
"language-todo": "0.10.0",
"language-toml": "0.12.0",
"language-xml": "0.21.0",
"language-yaml": "0.17.0"
"language-xml": "0.17.0",
"language-yaml": "0.13.0"
},
"private": true,
"scripts": {
+1 -2
Ver Arquivo
@@ -5,5 +5,4 @@ Exec=<%= installDir %>/share/atom/atom %U
Icon=<%= iconName %>
Type=Application
StartupNotify=true
Categories=GNOME;GTK;Utility;TextEditor;Development;
MimeType=text/plain;
Categories=GNOME;GTK;Utility;TextEditor;
+1 -1
Ver Arquivo
@@ -3,6 +3,6 @@ Version: <%= version %>
Section: <%= section %>
Priority: optional
Architecture: <%= arch %>
Installed-Size: <%= installedSize %>
Installed-Size: `du -ks usr|cut -f 1`
Maintainer: <%= maintainer %>
Description: <%= description %>
Arquivo binário não exibido.
Arquivo binário não exibido.
+4 -1
Ver Arquivo
@@ -5,6 +5,9 @@ var path = require('path');
process.chdir(path.dirname(__dirname));
if (process.platform == 'linux')
throw new Error('cibuild can not run on linux yet!');
var homeDir = process.platform == 'win32' ? process.env.USERPROFILE : process.env.HOME;
function loadEnvironmentVariables(filePath) {
@@ -24,7 +27,7 @@ function loadEnvironmentVariables(filePath) {
function readEnvironmentVariables() {
if (process.platform === 'win32')
loadEnvironmentVariables(path.resolve('/jenkins/config/atomcredentials'));
else if (process.platform === 'darwin') {
else {
loadEnvironmentVariables('/var/lib/jenkins/config/atomcredentials');
loadEnvironmentVariables('/var/lib/jenkins/config/xcodekeychain');
}
-13
Ver Arquivo
@@ -1,13 +0,0 @@
#!/bin/bash
set -e
export ATOM_ACCESS_TOKEN=$BUILD_ATOM_LINUX_ACCESS_TOKEN
if [ -d /usr/local/share/nodenv ]; then
export NODENV_ROOT=/usr/local/share/nodenv
export PATH=/usr/local/share/nodenv/bin:/usr/local/share/nodenv/shims:$PATH
export NODENV_VERSION="v0.10.21"
fi
script/cibuild
+4 -5
Ver Arquivo
@@ -13,22 +13,21 @@ CONTROL_FILE="$3"
DESKTOP_FILE="$4"
ICON_FILE="$5"
DEB_PATH="$6"
FILE_MODE=755
TARGET_ROOT="`mktemp -d`"
chmod 755 "$TARGET_ROOT"
TARGET="$TARGET_ROOT/atom-$VERSION-$ARCH"
mkdir -m $FILE_MODE -p "$TARGET/usr"
mkdir -p "$TARGET/usr"
env INSTALL_PREFIX="$TARGET/usr" script/grunt install
mkdir -m $FILE_MODE -p "$TARGET/DEBIAN"
mkdir -p "$TARGET/DEBIAN"
cp "$CONTROL_FILE" "$TARGET/DEBIAN/control"
mkdir -m $FILE_MODE -p "$TARGET/usr/share/applications"
mkdir -p "$TARGET/usr/share/applications"
cp "$DESKTOP_FILE" "$TARGET/usr/share/applications"
mkdir -m $FILE_MODE -p "$TARGET/usr/share/pixmaps"
mkdir -p "$TARGET/usr/share/pixmaps"
cp "$ICON_FILE" "$TARGET/usr/share/pixmaps"
dpkg-deb -b "$TARGET"
+1 -1
Ver Arquivo
@@ -31,7 +31,7 @@ function verifyNode(cb) {
var nodeMajorVersion = +versionArray[0];
var nodeMinorVersion = +versionArray[1];
if (nodeMajorVersion === 0 && nodeMinorVersion < 10) {
error = "node v0.10 is required to build Atom, node " + nodeVersion + " is installed.";
error = "node v0.10 is required to build Atom.";
cb(error);
}
else {
+2 -2
Ver Arquivo
@@ -119,14 +119,14 @@ class AtomReporter extends View
grim.clearDeprecations()
handleEvents: ->
$(document).on "click", ".spec-toggle", ({currentTarget}) ->
$(document).on "click", ".spec-toggle", ({currentTarget}) =>
element = $(currentTarget)
specFailures = element.parent().find('.spec-failures')
specFailures.toggle()
element.toggleClass('folded')
false
$(document).on "click", ".deprecation-toggle", ({currentTarget}) ->
$(document).on "click", ".deprecation-toggle", ({currentTarget}) =>
element = $(currentTarget)
deprecationList = $(document).find('.deprecation-list')
deprecationList.toggle()
+16 -48
Ver Arquivo
@@ -8,23 +8,6 @@ describe "the `atom` global", ->
beforeEach ->
atom.workspaceView = new WorkspaceView
describe 'window sizing methods', ->
describe '::getPosition and ::setPosition', ->
it 'sets the position of the window, and can retrieve the position just set', ->
atom.setPosition(22, 45)
expect(atom.getPosition()).toEqual x: 22, y: 45
describe '::getSize and ::setSize', ->
originalSize = null
beforeEach ->
originalSize = atom.getSize()
afterEach ->
atom.setSize(originalSize.width, originalSize.height)
it 'sets the size of the window, and can retrieve the size just set', ->
atom.setSize(100, 400)
expect(atom.getSize()).toEqual width: 100, height: 400
describe "package lifecycle methods", ->
describe ".loadPackage(name)", ->
it "continues if the package has an invalid package.json", ->
@@ -258,15 +241,15 @@ describe "the `atom` global", ->
two = atom.themes.stringToId(two)
three = atom.themes.stringToId(three)
expect(atom.themes.stylesheetElementForId(one)).toBeNull()
expect(atom.themes.stylesheetElementForId(two)).toBeNull()
expect(atom.themes.stylesheetElementForId(three)).toBeNull()
expect(atom.themes.stylesheetElementForId(one)).not.toExist()
expect(atom.themes.stylesheetElementForId(two)).not.toExist()
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
atom.packages.activatePackage("package-with-stylesheets-manifest")
expect(atom.themes.stylesheetElementForId(one)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(two)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(three)).toBeNull()
expect(atom.themes.stylesheetElementForId(one)).toExist()
expect(atom.themes.stylesheetElementForId(two)).toExist()
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
expect($('#jasmine-content').css('font-size')).toBe '1px'
describe "when the metadata does not contain a 'stylesheets' manifest", ->
@@ -280,14 +263,14 @@ describe "the `atom` global", ->
two = atom.themes.stringToId(two)
three = atom.themes.stringToId(three)
expect(atom.themes.stylesheetElementForId(one)).toBeNull()
expect(atom.themes.stylesheetElementForId(two)).toBeNull()
expect(atom.themes.stylesheetElementForId(three)).toBeNull()
expect(atom.themes.stylesheetElementForId(one)).not.toExist()
expect(atom.themes.stylesheetElementForId(two)).not.toExist()
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
atom.packages.activatePackage("package-with-stylesheets")
expect(atom.themes.stylesheetElementForId(one)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(two)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(three)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(one)).toExist()
expect(atom.themes.stylesheetElementForId(two)).toExist()
expect(atom.themes.stylesheetElementForId(three)).toExist()
expect($('#jasmine-content').css('font-size')).toBe '3px'
describe "grammar loading", ->
@@ -367,7 +350,7 @@ describe "the `atom` global", ->
atom.packages.deactivatePackage("package-that-throws-on-activate")
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's serialize method", ->
it "absorbs exceptions that are thrown by the package module's serialize methods", ->
spyOn(console, 'error')
waitsForPromise ->
@@ -382,16 +365,6 @@ describe "the `atom` global", ->
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's deactivate method", ->
spyOn(console, 'error')
waitsForPromise ->
atom.packages.activatePackage("package-that-throws-on-deactivate")
runs ->
expect(-> atom.packages.deactivatePackage("package-that-throws-on-deactivate")).not.toThrow()
expect(console.error).toHaveBeenCalled()
it "removes the package's grammars", ->
waitsForPromise ->
atom.packages.activatePackage('package-with-grammars')
@@ -520,14 +493,13 @@ describe "the `atom` global", ->
expect(atom.config.get('core.disabledPackages')).toContain packageName
describe "with themes", ->
reloadedHandler = null
beforeEach ->
waitsForPromise ->
atom.themes.activateThemes()
afterEach ->
atom.themes.deactivateThemes()
atom.config.unobserve('core.themes')
it ".enablePackage() and .disablePackage() enables and disables a theme", ->
packageName = 'theme-with-package-file'
@@ -545,17 +517,13 @@ describe "the `atom` global", ->
expect(atom.config.get('core.themes')).toContain packageName
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
reloadedHandler = jasmine.createSpy('reloadedHandler')
reloadedHandler.reset()
atom.themes.onDidReloadAll reloadedHandler
# disabling of theme
pack = atom.packages.disablePackage(packageName)
waitsFor ->
reloadedHandler.callCount is 1
not (pack in atom.packages.getActivePackages())
runs ->
expect(atom.packages.getActivePackages()).not.toContain pack
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
-126
Ver Arquivo
@@ -1,126 +0,0 @@
CommandRegistry = require '../src/command-registry'
describe "CommandRegistry", ->
[registry, parent, child, grandchild] = []
beforeEach ->
parent = document.createElement("div")
child = document.createElement("div")
grandchild = document.createElement("div")
parent.classList.add('parent')
child.classList.add('child')
grandchild.classList.add('grandchild')
child.appendChild(grandchild)
parent.appendChild(child)
document.querySelector('#jasmine-content').appendChild(parent)
registry = new CommandRegistry(parent)
describe "command dispatch", ->
it "invokes callbacks with selectors matching the target", ->
called = false
registry.add '.grandchild', 'command', (event) ->
expect(this).toBe grandchild
expect(event.type).toBe 'command'
expect(event.eventPhase).toBe Event.BUBBLING_PHASE
expect(event.target).toBe grandchild
expect(event.currentTarget).toBe grandchild
called = true
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(called).toBe true
it "invokes callbacks with selectors matching ancestors of the target", ->
calls = []
registry.add '.child', 'command', (event) ->
expect(this).toBe child
expect(event.target).toBe grandchild
expect(event.currentTarget).toBe child
calls.push('child')
registry.add '.parent', 'command', (event) ->
expect(this).toBe parent
expect(event.target).toBe grandchild
expect(event.currentTarget).toBe parent
calls.push('parent')
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['child', 'parent']
it "orders multiple matching listeners for an element by selector specificity", ->
child.classList.add('foo', 'bar')
calls = []
registry.add '.foo.bar', 'command', -> calls.push('.foo.bar')
registry.add '.foo', 'command', -> calls.push('.foo')
registry.add '.bar', 'command', -> calls.push('.bar') # specificity ties favor commands added later, like CSS
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['.foo.bar', '.bar', '.foo']
it "stops bubbling through ancestors when .stopPropagation() is called on the event", ->
calls = []
registry.add '.parent', 'command', -> calls.push('parent')
registry.add '.child', 'command', -> calls.push('child-2')
registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopPropagation()
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['child-1', 'child-2']
it "stops invoking callbacks when .stopImmediatePropagation() is called on the event", ->
calls = []
registry.add '.parent', 'command', -> calls.push('parent')
registry.add '.child', 'command', -> calls.push('child-2')
registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopImmediatePropagation()
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['child-1']
it "allows listeners to be removed via a disposable returned by ::add", ->
calls = []
disposable1 = registry.add '.parent', 'command', -> calls.push('parent')
disposable2 = registry.add '.child', 'command', -> calls.push('child')
disposable1.dispose()
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['child']
calls = []
disposable2.dispose()
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual []
it "allows multiple commands to be registered under one selector when called with an object", ->
calls = []
disposable = registry.add '.child',
'command-1': -> calls.push('command-1')
'command-2': -> calls.push('command-2')
grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true))
grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true))
expect(calls).toEqual ['command-1', 'command-2']
calls = []
disposable.dispose()
grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true))
grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true))
expect(calls).toEqual []
describe "::findCommands({target})", ->
it "returns commands that can be invoked on the target or its ancestors", ->
registry.add '.parent', 'namespace:command-1', ->
registry.add '.child', 'namespace:command-2', ->
registry.add '.grandchild', 'namespace:command-3', ->
registry.add '.grandchild.no-match', 'namespace:command-4', ->
expect(registry.findCommands(target: grandchild)[0..2]).toEqual [
{name: 'namespace:command-3', displayName: 'Namespace: Command 3'}
{name: 'namespace:command-2', displayName: 'Namespace: Command 2'}
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
]
-12
Ver Arquivo
@@ -23,18 +23,6 @@ describe "Config", ->
retrievedValue.array[1].b = 2.1
expect(atom.config.get('value')).toEqual(array: [1, b: 2, 3])
it "merges defaults into the returned value if both the assigned value and the default value are objects", ->
atom.config.setDefaults("foo", a: 1, b: 2)
atom.config.set("foo", a: 3)
expect(atom.config.get("foo")).toEqual {a: 3, b: 2}
atom.config.set("foo", 7)
expect(atom.config.get("foo")).toBe 7
atom.config.set("bar.baz", a: 3)
atom.config.setDefaults("bar", baz: 7)
expect(atom.config.get("bar.baz")).toEqual {a: 3}
describe ".set(keyPath, value)", ->
it "allows a key path's value to be written", ->
expect(atom.config.set("foo.bar.baz", 42)).toBe 42
+1 -2
Ver Arquivo
@@ -6,8 +6,7 @@ describe "ContextMenuManager", ->
[contextMenu] = []
beforeEach ->
{resourcePath} = atom.getLoadSettings()
contextMenu = new ContextMenuManager({resourcePath})
contextMenu = new ContextMenuManager
describe "adding definitions", ->
it 'loads', ->
+136 -148
Ver Arquivo
@@ -9,7 +9,7 @@ describe "DisplayBuffer", ->
buffer = atom.project.bufferForPathSync('sample.js')
displayBuffer = new DisplayBuffer({buffer, tabLength})
changeHandler = jasmine.createSpy 'changeHandler'
displayBuffer.onDidChange changeHandler
displayBuffer.on 'changed', changeHandler
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
@@ -58,7 +58,7 @@ describe "DisplayBuffer", ->
describe "soft wrapping", ->
beforeEach ->
displayBuffer.setSoftWrapped(true)
displayBuffer.setSoftWrap(true)
displayBuffer.setEditorWidthInChars(50)
changeHandler.reset()
@@ -67,48 +67,48 @@ describe "DisplayBuffer", ->
it "uses the preferred line length as the soft wrap column when it is less than the configured soft wrap column", ->
atom.config.set('editor.preferredLineLength', 100)
atom.config.set('editor.softWrapAtPreferredLineLength', true)
expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return '
expect(displayBuffer.lineForRow(10).text).toBe ' return '
atom.config.set('editor.preferredLineLength', 5)
expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe 'funct'
expect(displayBuffer.lineForRow(10).text).toBe 'funct'
atom.config.set('editor.softWrapAtPreferredLineLength', false)
expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return '
expect(displayBuffer.lineForRow(10).text).toBe ' return '
describe "when the line is shorter than the max line length", ->
it "renders the line unchanged", ->
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe buffer.lineForRow(0)
expect(displayBuffer.lineForRow(0).text).toBe buffer.lineForRow(0)
describe "when the line is empty", ->
it "renders the empty line", ->
expect(displayBuffer.tokenizedLineForScreenRow(13).text).toBe ''
expect(displayBuffer.lineForRow(13).text).toBe ''
describe "when there is a non-whitespace character at the max length boundary", ->
describe "when there is whitespace before the boundary", ->
it "wraps the line at the end of the first whitespace preceding the boundary", ->
expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return '
expect(displayBuffer.tokenizedLineForScreenRow(11).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
expect(displayBuffer.lineForRow(10).text).toBe ' return '
expect(displayBuffer.lineForRow(11).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
describe "when there is no whitespace before the boundary", ->
it "wraps the line exactly at the boundary since there's no more graceful place to wrap it", ->
buffer.setTextInRange([[0, 0], [1, 0]], 'abcdefghijklmnopqrstuvwxyz\n')
displayBuffer.setEditorWidthInChars(10)
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe 'abcdefghij'
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe 'klmnopqrst'
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe 'uvwxyz'
expect(displayBuffer.lineForRow(0).text).toBe 'abcdefghij'
expect(displayBuffer.lineForRow(1).text).toBe 'klmnopqrst'
expect(displayBuffer.lineForRow(2).text).toBe 'uvwxyz'
describe "when there is a whitespace character at the max length boundary", ->
it "wraps the line at the first non-whitespace character following the boundary", ->
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe ' var pivot = items.shift(), current, left = [], '
expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe 'right = [];'
expect(displayBuffer.lineForRow(3).text).toBe ' var pivot = items.shift(), current, left = [], '
expect(displayBuffer.lineForRow(4).text).toBe 'right = [];'
describe "when there are hard tabs", ->
beforeEach ->
buffer.setText(buffer.getText().replace(new RegExp(' ', 'g'), '\t'))
it "correctly tokenizes the hard tabs", ->
expect(displayBuffer.tokenizedLineForScreenRow(3).tokens[0].isHardTab).toBeTruthy()
expect(displayBuffer.tokenizedLineForScreenRow(3).tokens[1].isHardTab).toBeTruthy()
expect(displayBuffer.lineForRow(3).tokens[0].isHardTab).toBeTruthy()
expect(displayBuffer.lineForRow(3).tokens[1].isHardTab).toBeTruthy()
describe "when the buffer changes", ->
describe "when buffer lines are updated", ->
@@ -121,57 +121,57 @@ describe "DisplayBuffer", ->
describe "when the update makes a soft-wrapped line shorter than the max line length", ->
it "rewraps the line and emits a change event", ->
buffer.delete([[6, 24], [6, 42]])
expect(displayBuffer.tokenizedLineForScreenRow(7).text).toBe ' current < pivot ? : right.push(current);'
expect(displayBuffer.tokenizedLineForScreenRow(8).text).toBe ' }'
expect(displayBuffer.lineForRow(7).text).toBe ' current < pivot ? : right.push(current);'
expect(displayBuffer.lineForRow(8).text).toBe ' }'
expect(changeHandler).toHaveBeenCalled()
[[event]]= changeHandler.argsForCall
expect(event).toEqual(start: 7, end: 8, screenDelta: -1, bufferDelta: 0)
describe "when the update causes a line to soft wrap an additional time", ->
describe "when the update causes a line to softwrap an additional time", ->
it "rewraps the line and emits a change event", ->
buffer.insert([6, 28], '1234567890')
expect(displayBuffer.tokenizedLineForScreenRow(7).text).toBe ' current < pivot ? '
expect(displayBuffer.tokenizedLineForScreenRow(8).text).toBe 'left1234567890.push(current) : '
expect(displayBuffer.tokenizedLineForScreenRow(9).text).toBe 'right.push(current);'
expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' }'
expect(displayBuffer.lineForRow(7).text).toBe ' current < pivot ? '
expect(displayBuffer.lineForRow(8).text).toBe 'left1234567890.push(current) : '
expect(displayBuffer.lineForRow(9).text).toBe 'right.push(current);'
expect(displayBuffer.lineForRow(10).text).toBe ' }'
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, screenDelta: 1, bufferDelta: 0)
describe "when buffer lines are inserted", ->
it "inserts / updates wrapped lines and emits a change event", ->
buffer.insert([6, 21], '1234567890 abcdefghij 1234567890\nabcdefghij')
expect(displayBuffer.tokenizedLineForScreenRow(7).text).toBe ' current < pivot1234567890 abcdefghij '
expect(displayBuffer.tokenizedLineForScreenRow(8).text).toBe '1234567890'
expect(displayBuffer.tokenizedLineForScreenRow(9).text).toBe 'abcdefghij ? left.push(current) : '
expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe 'right.push(current);'
expect(displayBuffer.lineForRow(7).text).toBe ' current < pivot1234567890 abcdefghij '
expect(displayBuffer.lineForRow(8).text).toBe '1234567890'
expect(displayBuffer.lineForRow(9).text).toBe 'abcdefghij ? left.push(current) : '
expect(displayBuffer.lineForRow(10).text).toBe 'right.push(current);'
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, screenDelta: 2, bufferDelta: 1)
describe "when buffer lines are removed", ->
it "removes lines and emits a change event", ->
buffer.setTextInRange([[3, 21], [7, 5]], ';')
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe ' var pivot = items;'
expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe ' return '
expect(displayBuffer.tokenizedLineForScreenRow(5).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
expect(displayBuffer.tokenizedLineForScreenRow(6).text).toBe ' };'
expect(displayBuffer.lineForRow(3).text).toBe ' var pivot = items;'
expect(displayBuffer.lineForRow(4).text).toBe ' return '
expect(displayBuffer.lineForRow(5).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
expect(displayBuffer.lineForRow(6).text).toBe ' };'
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 9, screenDelta: -6, bufferDelta: -4)
describe "when a newline is inserted, deleted, and re-inserted at the end of a wrapped line (regression)", ->
it "correctly renders the original wrapped line", ->
buffer = atom.project.buildBufferSync(null, '')
displayBuffer = new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30, softWrapped: true})
displayBuffer = new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30, softWrap: true})
buffer.insert([0, 0], "the quick brown fox jumps over the lazy dog.")
buffer.insert([0, Infinity], '\n')
buffer.delete([[0, Infinity], [1, 0]])
buffer.insert([0, Infinity], '\n')
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe "the quick brown fox jumps over "
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "the lazy dog."
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe ""
expect(displayBuffer.lineForRow(0).text).toBe "the quick brown fox jumps over "
expect(displayBuffer.lineForRow(1).text).toBe "the lazy dog."
expect(displayBuffer.lineForRow(2).text).toBe ""
describe "position translation", ->
it "translates positions accounting for wrapped lines", ->
@@ -204,9 +204,9 @@ describe "DisplayBuffer", ->
describe ".setEditorWidthInChars(length)", ->
it "changes the length at which lines are wrapped and emits a change event for all screen lines", ->
displayBuffer.setEditorWidthInChars(40)
expect(tokensText displayBuffer.tokenizedLineForScreenRow(4).tokens).toBe 'left = [], right = [];'
expect(tokensText displayBuffer.tokenizedLineForScreenRow(5).tokens).toBe ' while(items.length > 0) {'
expect(tokensText displayBuffer.tokenizedLineForScreenRow(12).tokens).toBe 'sort(left).concat(pivot).concat(sort(rig'
expect(tokensText displayBuffer.lineForRow(4).tokens).toBe 'left = [], right = [];'
expect(tokensText displayBuffer.lineForRow(5).tokens).toBe ' while(items.length > 0) {'
expect(tokensText displayBuffer.lineForRow(12).tokens).toBe 'sort(left).concat(pivot).concat(sort(rig'
expect(changeHandler).toHaveBeenCalledWith(start: 0, end: 15, screenDelta: 3, bufferDelta: 0)
it "only allows positive widths to be assigned", ->
@@ -220,10 +220,10 @@ describe "DisplayBuffer", ->
displayBuffer.setWidth(50)
displayBuffer.manageScrollPosition = true
displayBuffer.setSoftWrapped(false)
displayBuffer.setSoftWrap(false)
displayBuffer.setScrollLeft(Infinity)
expect(displayBuffer.getScrollLeft()).toBeGreaterThan 0
displayBuffer.setSoftWrapped(true)
displayBuffer.setSoftWrap(true)
expect(displayBuffer.getScrollLeft()).toBe 0
displayBuffer.setScrollLeft(10)
expect(displayBuffer.getScrollLeft()).toBe 0
@@ -234,7 +234,7 @@ describe "DisplayBuffer", ->
buffer.release()
buffer = atom.project.bufferForPathSync('two-hundred.txt')
displayBuffer = new DisplayBuffer({buffer, tabLength})
displayBuffer.onDidChange changeHandler
displayBuffer.on 'changed', changeHandler
describe "when folds are created and destroyed", ->
describe "when a fold spans multiple lines", ->
@@ -242,7 +242,7 @@ describe "DisplayBuffer", ->
fold = displayBuffer.createFold(4, 7)
expect(fold).toBeDefined()
[line4, line5] = displayBuffer.tokenizedLinesForScreenRows(4, 5)
[line4, line5] = displayBuffer.linesForRows(4, 5)
expect(line4.fold).toBe fold
expect(line4.text).toMatch /^4-+/
expect(line5.text).toBe '8'
@@ -251,7 +251,7 @@ describe "DisplayBuffer", ->
changeHandler.reset()
fold.destroy()
[line4, line5] = displayBuffer.tokenizedLinesForScreenRows(4, 5)
[line4, line5] = displayBuffer.linesForRows(4, 5)
expect(line4.fold).toBeUndefined()
expect(line4.text).toMatch /^4-+/
expect(line5.text).toBe '5'
@@ -262,7 +262,7 @@ describe "DisplayBuffer", ->
it "renders a fold placeholder for the folded line but does not skip any lines", ->
fold = displayBuffer.createFold(4, 4)
[line4, line5] = displayBuffer.tokenizedLinesForScreenRows(4, 5)
[line4, line5] = displayBuffer.linesForRows(4, 5)
expect(line4.fold).toBe fold
expect(line4.text).toMatch /^4-+/
expect(line5.text).toBe '5'
@@ -275,7 +275,7 @@ describe "DisplayBuffer", ->
fold.destroy()
[line4, line5] = displayBuffer.tokenizedLinesForScreenRows(4, 5)
[line4, line5] = displayBuffer.linesForRows(4, 5)
expect(line4.fold).toBeUndefined()
expect(line4.text).toMatch /^4-+/
expect(line5.text).toBe '5'
@@ -287,13 +287,13 @@ describe "DisplayBuffer", ->
innerFold = displayBuffer.createFold(6, 7)
outerFold = displayBuffer.createFold(4, 8)
[line4, line5] = displayBuffer.tokenizedLinesForScreenRows(4, 5)
[line4, line5] = displayBuffer.linesForRows(4, 5)
expect(line4.fold).toBe outerFold
expect(line4.text).toMatch /4-+/
expect(line5.text).toMatch /9-+/
outerFold.destroy()
[line4, line5, line6, line7] = displayBuffer.tokenizedLinesForScreenRows(4, 7)
[line4, line5, line6, line7] = displayBuffer.linesForRows(4, 7)
expect(line4.fold).toBeUndefined()
expect(line4.text).toMatch /^4-+/
expect(line5.text).toBe '5'
@@ -305,7 +305,7 @@ describe "DisplayBuffer", ->
innerFold = displayBuffer.createFold(4, 6)
outerFold = displayBuffer.createFold(4, 8)
[line4, line5] = displayBuffer.tokenizedLinesForScreenRows(4, 5)
[line4, line5] = displayBuffer.linesForRows(4, 5)
expect(line4.fold).toBe outerFold
expect(line4.text).toMatch /4-+/
expect(line5.text).toMatch /9-+/
@@ -326,14 +326,14 @@ describe "DisplayBuffer", ->
innerFold = displayBuffer.createFold(2, 5)
expect(changeHandler).not.toHaveBeenCalled()
[line0, line1] = displayBuffer.tokenizedLinesForScreenRows(0, 1)
[line0, line1] = displayBuffer.linesForRows(0, 1)
expect(line0.fold).toBe outerFold
expect(line1.fold).toBeUndefined()
changeHandler.reset()
innerFold.destroy()
expect(changeHandler).not.toHaveBeenCalled()
[line0, line1] = displayBuffer.tokenizedLinesForScreenRows(0, 1)
[line0, line1] = displayBuffer.linesForRows(0, 1)
expect(line0.fold).toBe outerFold
expect(line1.fold).toBeUndefined()
@@ -342,8 +342,8 @@ describe "DisplayBuffer", ->
fold2 = displayBuffer.createFold(4, 9)
fold1 = displayBuffer.createFold(0, 4)
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toMatch /^0/
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toMatch /^10/
expect(displayBuffer.lineForRow(0).text).toMatch /^0/
expect(displayBuffer.lineForRow(1).text).toMatch /^10/
describe "when there is another display buffer pointing to the same buffer", ->
it "does not create folds in the other display buffer", ->
@@ -363,19 +363,19 @@ describe "DisplayBuffer", ->
buffer.setTextInRange([[1, 0], [5, 1]], 'party!')
it "removes the fold and replaces the selection with the new text", ->
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe "0"
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "party!"
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBe fold2
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(0).text).toBe "0"
expect(displayBuffer.lineForRow(1).text).toBe "party!"
expect(displayBuffer.lineForRow(2).fold).toBe fold2
expect(displayBuffer.lineForRow(3).text).toMatch /^9-+/
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 3, screenDelta: -2, bufferDelta: -4)
describe "when the changes is subsequently undone", ->
xit "restores destroyed folds", ->
buffer.undo()
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe '2'
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBe fold1
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe '5'
expect(displayBuffer.lineForRow(2).text).toBe '2'
expect(displayBuffer.lineForRow(2).fold).toBe fold1
expect(displayBuffer.lineForRow(3).text).toBe '5'
describe "when the old range surrounds two nested folds", ->
it "removes both folds and replaces the selection with the new text", ->
@@ -384,9 +384,9 @@ describe "DisplayBuffer", ->
buffer.setTextInRange([[1, 0], [10, 0]], 'goodbye')
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe "0"
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "goodbye10"
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe "11"
expect(displayBuffer.lineForRow(0).text).toBe "0"
expect(displayBuffer.lineForRow(1).text).toBe "goodbye10"
expect(displayBuffer.lineForRow(2).text).toBe "11"
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 3, screenDelta: -2, bufferDelta: -9)
@@ -403,42 +403,42 @@ describe "DisplayBuffer", ->
it "updates the buffer and re-positions subsequent folds", ->
buffer.setTextInRange([[0, 0], [1, 1]], 'abc')
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe "abc"
expect(displayBuffer.tokenizedLineForScreenRow(1).fold).toBe fold1
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe "5"
expect(displayBuffer.tokenizedLineForScreenRow(3).fold).toBe fold2
expect(displayBuffer.tokenizedLineForScreenRow(4).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(0).text).toBe "abc"
expect(displayBuffer.lineForRow(1).fold).toBe fold1
expect(displayBuffer.lineForRow(2).text).toBe "5"
expect(displayBuffer.lineForRow(3).fold).toBe fold2
expect(displayBuffer.lineForRow(4).text).toMatch /^9-+/
expect(changeHandler).toHaveBeenCalledWith(start: 0, end: 1, screenDelta: -1, bufferDelta: -1)
changeHandler.reset()
fold1.destroy()
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe "abc"
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "2"
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toMatch /^4-+/
expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe "5"
expect(displayBuffer.tokenizedLineForScreenRow(5).fold).toBe fold2
expect(displayBuffer.tokenizedLineForScreenRow(6).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(0).text).toBe "abc"
expect(displayBuffer.lineForRow(1).text).toBe "2"
expect(displayBuffer.lineForRow(3).text).toMatch /^4-+/
expect(displayBuffer.lineForRow(4).text).toBe "5"
expect(displayBuffer.lineForRow(5).fold).toBe fold2
expect(displayBuffer.lineForRow(6).text).toMatch /^9-+/
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 1, screenDelta: 2, bufferDelta: 0)
describe "when the old range straddles the beginning of a fold", ->
it "destroys the fold", ->
buffer.setTextInRange([[1, 1], [3, 0]], "a\nb\nc\nd\n")
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe '1a'
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe 'b'
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBeUndefined()
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe 'c'
expect(displayBuffer.lineForRow(1).text).toBe '1a'
expect(displayBuffer.lineForRow(2).text).toBe 'b'
expect(displayBuffer.lineForRow(2).fold).toBeUndefined()
expect(displayBuffer.lineForRow(3).text).toBe 'c'
describe "when the old range follows a fold", ->
it "re-positions the screen ranges for the change event based on the preceding fold", ->
buffer.setTextInRange([[10, 0], [11, 0]], 'abc')
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "1"
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBe fold1
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe "5"
expect(displayBuffer.tokenizedLineForScreenRow(4).fold).toBe fold2
expect(displayBuffer.tokenizedLineForScreenRow(5).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(1).text).toBe "1"
expect(displayBuffer.lineForRow(2).fold).toBe fold1
expect(displayBuffer.lineForRow(3).text).toBe "5"
expect(displayBuffer.lineForRow(4).fold).toBe fold2
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
expect(changeHandler).toHaveBeenCalledWith(start: 6, end: 7, screenDelta: -1, bufferDelta: -1)
@@ -449,12 +449,12 @@ describe "DisplayBuffer", ->
expect(fold1.getStartRow()).toBe 2
expect(fold1.getEndRow()).toBe 5
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "1"
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe "2"
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBe fold1
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toMatch "5"
expect(displayBuffer.tokenizedLineForScreenRow(4).fold).toBe fold2
expect(displayBuffer.tokenizedLineForScreenRow(5).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(1).text).toBe "1"
expect(displayBuffer.lineForRow(2).text).toBe "2"
expect(displayBuffer.lineForRow(2).fold).toBe fold1
expect(displayBuffer.lineForRow(3).text).toMatch "5"
expect(displayBuffer.lineForRow(4).fold).toBe fold2
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 2, screenDelta: 0, bufferDelta: 1)
@@ -464,12 +464,12 @@ describe "DisplayBuffer", ->
expect(fold1.getStartRow()).toBe 2
expect(fold1.getEndRow()).toBe 7
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "1"
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe "2"
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBe fold1
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toMatch "5"
expect(displayBuffer.tokenizedLineForScreenRow(4).fold).toBe fold2
expect(displayBuffer.tokenizedLineForScreenRow(5).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(1).text).toBe "1"
expect(displayBuffer.lineForRow(2).text).toBe "2"
expect(displayBuffer.lineForRow(2).fold).toBe fold1
expect(displayBuffer.lineForRow(3).text).toMatch "5"
expect(displayBuffer.lineForRow(4).fold).toBe fold2
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 2, screenDelta: 0, bufferDelta: 3)
@@ -478,21 +478,21 @@ describe "DisplayBuffer", ->
it "destroys the fold", ->
fold2.destroy()
buffer.setTextInRange([[3, 0], [6, 0]], 'a\n')
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe '2'
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBeUndefined()
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe 'a'
expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe '6'
expect(displayBuffer.lineForRow(2).text).toBe '2'
expect(displayBuffer.lineForRow(2).fold).toBeUndefined()
expect(displayBuffer.lineForRow(3).text).toBe 'a'
expect(displayBuffer.lineForRow(4).text).toBe '6'
describe "when the old range is contained to a single line in-between two folds", ->
it "re-renders the line with the placeholder and re-positions the second fold", ->
buffer.insert([5, 0], 'abc\n')
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe "1"
expect(displayBuffer.tokenizedLineForScreenRow(2).fold).toBe fold1
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toMatch "abc"
expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe "5"
expect(displayBuffer.tokenizedLineForScreenRow(5).fold).toBe fold2
expect(displayBuffer.tokenizedLineForScreenRow(6).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(1).text).toBe "1"
expect(displayBuffer.lineForRow(2).fold).toBe fold1
expect(displayBuffer.lineForRow(3).text).toMatch "abc"
expect(displayBuffer.lineForRow(4).text).toBe "5"
expect(displayBuffer.lineForRow(5).fold).toBe fold2
expect(displayBuffer.lineForRow(6).text).toMatch /^9-+/
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 3, screenDelta: 1, bufferDelta: 1)
@@ -545,15 +545,15 @@ describe "DisplayBuffer", ->
displayBuffer.createFold(1, 9)
displayBuffer.createFold(11, 12)
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe '1'
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe '10'
expect(displayBuffer.lineForRow(1).text).toBe '1'
expect(displayBuffer.lineForRow(2).text).toBe '10'
displayBuffer.unfoldBufferRow(2)
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe '1'
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe '2'
expect(displayBuffer.tokenizedLineForScreenRow(7).fold).toBeDefined()
expect(displayBuffer.tokenizedLineForScreenRow(8).text).toMatch /^9-+/
expect(displayBuffer.tokenizedLineForScreenRow(10).fold).toBeDefined()
expect(displayBuffer.lineForRow(1).text).toBe '1'
expect(displayBuffer.lineForRow(2).text).toBe '2'
expect(displayBuffer.lineForRow(7).fold).toBeDefined()
expect(displayBuffer.lineForRow(8).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(10).fold).toBeDefined()
describe ".outermostFoldsInBufferRowRange(startRow, endRow)", ->
it "returns the outermost folds entirely contained in the given row range, exclusive of end row", ->
@@ -568,7 +568,7 @@ describe "DisplayBuffer", ->
describe "::clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", ->
beforeEach ->
displayBuffer.setSoftWrapped(true)
displayBuffer.setSoftWrap(true)
displayBuffer.setEditorWidthInChars(50)
it "allows valid positions", ->
@@ -643,7 +643,7 @@ describe "DisplayBuffer", ->
it "correctly translates positions on soft wrapped lines containing tabs", ->
buffer.setText('\t\taa bb cc dd ee ff gg')
displayBuffer.setSoftWrapped(true)
displayBuffer.setSoftWrap(true)
displayBuffer.setEditorWidthInChars(10)
expect(displayBuffer.screenPositionForBufferPosition([0, 10], wrapAtSoftNewlines: true)).toEqual [1, 0]
expect(displayBuffer.bufferPositionForScreenPosition([1, 0])).toEqual [0, 10]
@@ -686,7 +686,7 @@ describe "DisplayBuffer", ->
expect(marker2.getScreenRange()).toEqual [[5, 4], [5, 10]]
it "emits a 'marker-created' event on the DisplayBuffer whenever a marker is created", ->
displayBuffer.onDidCreateMarker markerCreatedHandler = jasmine.createSpy("markerCreatedHandler")
displayBuffer.on 'marker-created', markerCreatedHandler = jasmine.createSpy("markerCreatedHandler")
marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]])
expect(markerCreatedHandler).toHaveBeenCalledWith(marker1)
@@ -722,7 +722,7 @@ describe "DisplayBuffer", ->
beforeEach ->
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
marker.onDidChange markerChangedHandler = jasmine.createSpy("markerChangedHandler")
marker.on 'changed', markerChangedHandler = jasmine.createSpy("markerChangedHandler")
it "triggers the 'changed' event whenever the markers head's screen position changes in the buffer or on screen", ->
marker.setHeadScreenPosition([8, 20])
@@ -859,8 +859,8 @@ describe "DisplayBuffer", ->
it "updates markers before emitting buffer change events, but does not notify their observers until the change event", ->
marker2 = displayBuffer.markBufferRange([[8, 1], [8, 1]])
marker2.onDidChange marker2ChangedHandler = jasmine.createSpy("marker2ChangedHandler")
displayBuffer.onDidChange changeHandler = jasmine.createSpy("changeHandler").andCallFake -> onDisplayBufferChange()
marker2.on 'changed', marker2ChangedHandler = jasmine.createSpy("marker2ChangedHandler")
displayBuffer.on 'changed', changeHandler = jasmine.createSpy("changeHandler").andCallFake -> onDisplayBufferChange()
# New change ----
@@ -886,7 +886,7 @@ describe "DisplayBuffer", ->
marker2ChangedHandler.reset()
marker3 = displayBuffer.markBufferRange([[8, 1], [8, 2]])
marker3.onDidChange marker3ChangedHandler = jasmine.createSpy("marker3ChangedHandler")
marker3.on 'changed', marker3ChangedHandler = jasmine.createSpy("marker3ChangedHandler")
onDisplayBufferChange = ->
# calls change handler first
@@ -932,7 +932,7 @@ describe "DisplayBuffer", ->
expect(marker3ChangedHandler).toHaveBeenCalled()
it "updates the position of markers before emitting change events that aren't caused by a buffer change", ->
displayBuffer.onDidChange changeHandler = jasmine.createSpy("changeHandler").andCallFake ->
displayBuffer.on 'changed', changeHandler = jasmine.createSpy("changeHandler").andCallFake ->
# calls change handler first
expect(markerChangedHandler).not.toHaveBeenCalled()
# but still updates the markers
@@ -998,20 +998,20 @@ describe "DisplayBuffer", ->
expect(marker.isValid()).toBeFalsy()
expect(displayBuffer.getMarker(marker.id)).toBeUndefined()
it "notifies ::onDidDestroy observers when markers are destroyed", ->
it "emits 'destroyed' events when markers are destroyed", ->
destroyedHandler = jasmine.createSpy("destroyedHandler")
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
marker.onDidDestroy destroyedHandler
marker.on 'destroyed', destroyedHandler
marker.destroy()
expect(destroyedHandler).toHaveBeenCalled()
destroyedHandler.reset()
marker2 = displayBuffer.markScreenRange([[5, 4], [5, 10]])
marker2.onDidDestroy destroyedHandler
marker2.on 'destroyed', destroyedHandler
buffer.getMarker(marker2.id).destroy()
expect(destroyedHandler).toHaveBeenCalled()
describe "Marker::copy(attributes)", ->
describe "DisplayBufferMarker::copy(attributes)", ->
it "creates a copy of the marker with the given attributes merged in", ->
initialMarkerCount = displayBuffer.getMarkerCount()
marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]], a: 1, b: 2)
@@ -1020,10 +1020,10 @@ describe "DisplayBuffer", ->
marker2 = marker1.copy(b: 3)
expect(marker2.getBufferRange()).toEqual marker1.getBufferRange()
expect(displayBuffer.getMarkerCount()).toBe initialMarkerCount + 2
expect(marker1.getProperties()).toEqual a: 1, b: 2
expect(marker2.getProperties()).toEqual a: 1, b: 3
expect(marker1.getAttributes()).toEqual a: 1, b: 2
expect(marker2.getAttributes()).toEqual a: 1, b: 3
describe "Marker::getPixelRange()", ->
describe "DisplayBufferMarker::getPixelRange()", ->
it "returns the start and end positions of the marker based on the line height and character widths assigned to the DisplayBuffer", ->
marker = displayBuffer.markScreenRange([[5, 10], [6, 4]])
@@ -1037,28 +1037,16 @@ describe "DisplayBuffer", ->
expect(start.top).toBe 5 * 20
expect(start.left).toBe (4 * 10) + (6 * 11)
describe 'when there are multiple DisplayBuffers for a buffer', ->
describe 'when a marker is created', ->
it 'the second display buffer will not emit a marker-created event when the marker has been deleted in the first marker-created event', ->
displayBuffer2 = new DisplayBuffer({buffer, tabLength})
displayBuffer.onDidCreateMarker markerCreated1 = jasmine.createSpy().andCallFake (marker) -> marker.destroy()
displayBuffer2.onDidCreateMarker markerCreated2 = jasmine.createSpy()
displayBuffer.markBufferRange([[0, 0], [1, 5]], {})
expect(markerCreated1).toHaveBeenCalled()
expect(markerCreated2).not.toHaveBeenCalled()
describe "decorations", ->
[marker, decoration, decorationProperties] = []
[marker, decoration, decorationParams] = []
beforeEach ->
marker = displayBuffer.markBufferRange([[2, 13], [3, 15]])
decorationProperties = {type: 'gutter', class: 'one'}
decoration = displayBuffer.decorateMarker(marker, decorationProperties)
decorationParams = {type: 'gutter', class: 'one'}
decoration = displayBuffer.decorateMarker(marker, decorationParams)
it "can add decorations associated with markers and remove them", ->
expect(decoration).toBeDefined()
expect(decoration.getProperties()).toBe decorationProperties
expect(decoration.getParams()).toBe decorationParams
expect(displayBuffer.decorationForId(decoration.id)).toBe decoration
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
@@ -1073,12 +1061,12 @@ describe "DisplayBuffer", ->
describe "when a decoration is updated via Decoration::update()", ->
it "emits an 'updated' event containing the new and old params", ->
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
decoration.setProperties type: 'gutter', class: 'two'
decoration.on 'updated', updatedSpy = jasmine.createSpy()
decoration.update type: 'gutter', class: 'two'
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
expect(oldProperties).toEqual decorationProperties
expect(newProperties).toEqual type: 'gutter', class: 'two', id: decoration.id
{oldParams, newParams} = updatedSpy.mostRecentCall.args[0]
expect(oldParams).toEqual decorationParams
expect(newParams).toEqual type: 'gutter', class: 'two', id: decoration.id
describe "::setScrollTop", ->
beforeEach ->
@@ -1171,7 +1159,7 @@ describe "DisplayBuffer", ->
it "recomputes the scroll width when the scoped character widths change in a batch", ->
operatorWidth = 20
displayBuffer.onDidChangeCharacterWidths changedSpy = jasmine.createSpy()
displayBuffer.on 'character-widths-changed', changedSpy = jasmine.createSpy()
displayBuffer.batchCharacterMeasurement ->
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth)
+53 -168
Ver Arquivo
@@ -1,7 +1,7 @@
_ = require 'underscore-plus'
{extend, flatten, toArray, last} = _
EditorView = require '../src/editor-view'
ReactEditorView = require '../src/react-editor-view'
EditorComponent = require '../src/editor-component'
nbsp = String.fromCharCode(160)
@@ -34,7 +34,7 @@ describe "EditorComponent", ->
contentNode = document.querySelector('#jasmine-content')
contentNode.style.width = '1000px'
wrapperView = new EditorView(editor, {lineOverdrawMargin})
wrapperView = new ReactEditorView(editor, {lineOverdrawMargin})
wrapperView.attachToDom()
wrapperNode = wrapperView.element
@@ -65,9 +65,9 @@ describe "EditorComponent", ->
linesNode = componentNode.querySelector('.lines')
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expect(componentNode.querySelectorAll('.line').length).toBe 6 + 2 # no margin above
expect(component.lineNodeForScreenRow(0).textContent).toBe editor.tokenizedLineForScreenRow(0).text
expect(component.lineNodeForScreenRow(0).textContent).toBe editor.lineForScreenRow(0).text
expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0
expect(component.lineNodeForScreenRow(5).textContent).toBe editor.tokenizedLineForScreenRow(5).text
expect(component.lineNodeForScreenRow(5).textContent).toBe editor.lineForScreenRow(5).text
expect(component.lineNodeForScreenRow(5).offsetTop).toBe 5 * lineHeightInPixels
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
@@ -77,9 +77,9 @@ describe "EditorComponent", ->
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, #{-4.5 * lineHeightInPixels}px, 0px)"
expect(componentNode.querySelectorAll('.line').length).toBe 6 + 4 # margin above and below
expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
expect(component.lineNodeForScreenRow(2).textContent).toBe editor.tokenizedLineForScreenRow(2).text
expect(component.lineNodeForScreenRow(2).textContent).toBe editor.lineForScreenRow(2).text
expect(component.lineNodeForScreenRow(9).offsetTop).toBe 9 * lineHeightInPixels
expect(component.lineNodeForScreenRow(9).textContent).toBe editor.tokenizedLineForScreenRow(9).text
expect(component.lineNodeForScreenRow(9).textContent).toBe editor.lineForScreenRow(9).text
it "updates the top position of subsequent lines when lines are inserted or removed", ->
editor.getBuffer().deleteRows(0, 1)
@@ -111,11 +111,11 @@ describe "EditorComponent", ->
buffer.insert([0, 0], '\n\n')
nextAnimationFrame()
expect(component.lineNodeForScreenRow(3).textContent).toBe editor.tokenizedLineForScreenRow(3).text
expect(component.lineNodeForScreenRow(3).textContent).toBe editor.lineForScreenRow(3).text
buffer.delete([[0, 0], [3, 0]])
nextAnimationFrame()
expect(component.lineNodeForScreenRow(3).textContent).toBe editor.tokenizedLineForScreenRow(3).text
expect(component.lineNodeForScreenRow(3).textContent).toBe editor.lineForScreenRow(3).text
it "updates the top position of lines when the line height changes", ->
initialLineHeightInPixels = editor.getLineHeightInPixels()
@@ -197,50 +197,6 @@ describe "EditorComponent", ->
nextAnimationFrame()
expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
it "applies .leading-whitespace for lines with leading spaces and/or tabs", ->
editor.setText(' a')
nextAnimationFrame()
leafNodes = getLeafNodes(component.lineNodeForScreenRow(0))
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true
expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe false
editor.setText('\ta')
nextAnimationFrame()
leafNodes = getLeafNodes(component.lineNodeForScreenRow(0))
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true
expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe false
it "applies .trailing-whitespace for lines with trailing spaces and/or tabs", ->
editor.setText(' ')
nextAnimationFrame()
leafNodes = getLeafNodes(component.lineNodeForScreenRow(0))
expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false
editor.setText('\t')
nextAnimationFrame()
leafNodes = getLeafNodes(component.lineNodeForScreenRow(0))
expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false
editor.setText('a ')
nextAnimationFrame()
leafNodes = getLeafNodes(component.lineNodeForScreenRow(0))
expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false
editor.setText('a\t')
nextAnimationFrame()
leafNodes = getLeafNodes(component.lineNodeForScreenRow(0))
expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false
describe "when showInvisibles is enabled", ->
invisibles = null
@@ -253,32 +209,25 @@ describe "EditorComponent", ->
atom.config.set("editor.showInvisibles", true)
atom.config.set("editor.invisibles", invisibles)
nextAnimationFrame()
it "re-renders the lines when the showInvisibles config option changes", ->
editor.setText " a line with tabs\tand spaces \n"
editor.setText " a line with tabs\tand spaces "
nextAnimationFrame()
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab}and spaces#{invisibles.space}#{invisibles.eol}"
atom.config.set("editor.showInvisibles", false)
nextAnimationFrame()
expect(component.lineNodeForScreenRow(0).textContent).toBe " a line with tabs and spaces "
atom.config.set("editor.showInvisibles", true)
nextAnimationFrame()
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab}and spaces#{invisibles.space}#{invisibles.eol}"
it "displays leading/trailing spaces, tabs, and newlines as visible characters", ->
editor.setText " a line with tabs\tand spaces \n"
it "displays spaces, tabs, and newlines as visible characters", ->
editor.setText " a line with tabs\tand spaces "
nextAnimationFrame()
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab}and spaces#{invisibles.space}#{invisibles.eol}"
leafNodes = getLeafNodes(component.lineNodeForScreenRow(0))
expect(leafNodes[0].classList.contains('invisible-character')).toBe true
expect(leafNodes[leafNodes.length - 1].classList.contains('invisible-character')).toBe true
it "displays newlines as their own token outside of the other tokens' scopes", ->
editor.setText "var\n"
editor.setText "var"
nextAnimationFrame()
expect(component.lineNodeForScreenRow(0).innerHTML).toBe "<span class=\"source js\"><span class=\"storage modifier js\">var</span></span><span class=\"invisible-character\">#{invisibles.eol}</span>"
@@ -292,12 +241,10 @@ describe "EditorComponent", ->
it "renders an nbsp on empty lines when the line-ending character is an empty string", ->
atom.config.set("editor.invisibles", eol: '')
nextAnimationFrame()
expect(component.lineNodeForScreenRow(10).textContent).toBe nbsp
it "renders an nbsp on empty lines when the line-ending character is false", ->
atom.config.set("editor.invisibles", eol: false)
nextAnimationFrame()
it "renders an nbsp on empty lines when no line-ending character is defined", ->
atom.config.set("editor.invisibles", eol: null)
expect(component.lineNodeForScreenRow(10).textContent).toBe nbsp
it "interleaves invisible line-ending characters with indent guides on empty lines", ->
@@ -321,8 +268,8 @@ describe "EditorComponent", ->
describe "when soft wrapping is enabled", ->
beforeEach ->
editor.setText "a line that wraps \n"
editor.setSoftWrapped(true)
editor.setText "a line that wraps "
editor.setSoftWrap(true)
nextAnimationFrame()
componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
component.measureHeightAndWidth()
@@ -501,7 +448,7 @@ describe "EditorComponent", ->
expect(component.lineNumberNodeForScreenRow(6).offsetTop).toBe 6 * lineHeightInPixels
it "renders • characters for soft-wrapped lines", ->
editor.setSoftWrapped(true)
editor.setSoftWrap(true)
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 30 * charWidth + 'px'
component.measureHeightAndWidth()
@@ -645,7 +592,7 @@ describe "EditorComponent", ->
describe "cursor rendering", ->
it "renders the currently visible cursors, translated relative to the scroll position", ->
cursor1 = editor.getLastCursor()
cursor1 = editor.getCursor()
cursor1.setScreenPosition([0, 5])
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
@@ -754,7 +701,7 @@ describe "EditorComponent", ->
expect(cursorsNode.classList.contains('blink-off')).toBe false
# Stop blinking after moving the cursor
editor.moveRight()
editor.moveCursorRight()
expect(cursorsNode.classList.contains('blink-off')).toBe false
advanceClock(component.props.cursorBlinkResumeDelay)
@@ -858,8 +805,8 @@ describe "EditorComponent", ->
it "does not render empty selections", ->
editor.addSelectionForBufferRange([[2, 2], [2, 2]])
nextAnimationFrame()
expect(editor.getSelections()[0].isEmpty()).toBe true
expect(editor.getSelections()[1].isEmpty()).toBe true
expect(editor.getSelection(0).isEmpty()).toBe true
expect(editor.getSelection(1).isEmpty()).toBe true
expect(componentNode.querySelectorAll('.selection').length).toBe 0
@@ -937,7 +884,7 @@ describe "EditorComponent", ->
it "only applies decorations to screen rows that are spanned by their marker when lines are soft-wrapped", ->
editor.setText("a line that wraps, ok")
editor.setSoftWrapped(true)
editor.setSoftWrap(true)
componentNode.style.width = 16 * charWidth + 'px'
component.measureHeightAndWidth()
nextAnimationFrame()
@@ -1178,7 +1125,7 @@ describe "EditorComponent", ->
it "renders the decoration's new params", ->
expect(componentNode.querySelector('.test-highlight')).toBeTruthy()
decoration.setProperties(type: 'highlight', class: 'new-test-highlight')
decoration.update(type: 'highlight', class: 'new-test-highlight')
nextAnimationFrame()
expect(componentNode.querySelector('.test-highlight')).toBeFalsy()
@@ -1362,12 +1309,6 @@ describe "EditorComponent", ->
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8]), {target}))
expect(editor.isFoldedAtBufferRow 4).toBe false
describe "when the horizontal scrollbar is interacted with", ->
it "clicking on the scrollbar does not move the cursor", ->
target = horizontalScrollbarNode
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8]), {target}))
expect(editor.getCursorScreenPosition()).toEqual [0, 0]
describe "mouse interactions on the gutter", ->
gutterNode = null
@@ -1375,19 +1316,9 @@ describe "EditorComponent", ->
gutterNode = componentNode.querySelector('.gutter')
describe "when the gutter is clicked", ->
it "selects the clicked row", ->
it "moves the cursor to the beginning of the clicked row", ->
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(4)))
expect(editor.getSelectedScreenRange()).toEqual [[4, 0], [5, 0]]
describe "when the gutter is meta-clicked", ->
it "creates a new selection for the clicked row", ->
editor.setSelectedScreenRange([[3, 0], [3, 2]])
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(4), metaKey: true))
expect(editor.getSelectedScreenRanges()).toEqual [[[3, 0], [3, 2]], [[4, 0], [5, 0]]]
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(6), metaKey: true))
expect(editor.getSelectedScreenRanges()).toEqual [[[3, 0], [3, 2]], [[4, 0], [5, 0]], [[6, 0], [7, 0]]]
expect(editor.getCursorScreenPosition()).toEqual [4, 0]
describe "when the gutter is shift-clicked", ->
beforeEach ->
@@ -1420,40 +1351,6 @@ describe "EditorComponent", ->
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(2)))
expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [7, 0]]
describe "when the gutter is meta-clicked and dragged", ->
beforeEach ->
editor.setSelectedScreenRange([[3, 0], [3, 2]])
describe "when dragging downward", ->
it "selects the rows between the start and end of the drag", ->
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(4), metaKey: true))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6), metaKey: true))
nextAnimationFrame()
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(6), metaKey: true))
expect(editor.getSelectedScreenRanges()).toEqual [[[3, 0], [3, 2]], [[4, 0], [7, 0]]]
it "merges overlapping selections", ->
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2), metaKey: true))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6), metaKey: true))
nextAnimationFrame()
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(6), metaKey: true))
expect(editor.getSelectedScreenRanges()).toEqual [[[2, 0], [7, 0]]]
describe "when dragging upward", ->
it "selects the rows between the start and end of the drag", ->
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(6), metaKey: true))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(4), metaKey: true))
nextAnimationFrame()
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(4), metaKey: true))
expect(editor.getSelectedScreenRanges()).toEqual [[[3, 0], [3, 2]], [[4, 0], [7, 0]]]
it "merges overlapping selections", ->
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(6), metaKey: true))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(2), metaKey: true))
nextAnimationFrame()
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(2), metaKey: true))
expect(editor.getSelectedScreenRanges()).toEqual [[[2, 0], [7, 0]]]
describe "when the gutter is shift-clicked and dragged", ->
describe "when the shift-click is below the existing selection's tail", ->
describe "when dragging downward", ->
@@ -1525,7 +1422,7 @@ describe "EditorComponent", ->
cursor = null
beforeEach ->
cursor = editor.getLastCursor()
cursor = editor.getCursor()
cursor.setScreenPosition([0, 0])
it "adds the 'has-selection' class to the editor when there is a selection", ->
@@ -1645,7 +1542,6 @@ describe "EditorComponent", ->
height: 8px;
}
"""
nextAnimationFrame()
scrollbarCornerNode = componentNode.querySelector('.scrollbar-corner')
expect(verticalScrollbarNode.offsetWidth).toBe 8
@@ -1871,28 +1767,28 @@ describe "EditorComponent", ->
it "inserts the newest character in the input's value into the buffer", ->
componentNode.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode))
nextAnimationFrame()
expect(editor.lineTextForBufferRow(0)).toBe 'xvar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'xvar quicksort = function () {'
componentNode.dispatchEvent(buildTextInputEvent(data: 'y', target: inputNode))
nextAnimationFrame()
expect(editor.lineTextForBufferRow(0)).toBe 'xyvar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'xyvar quicksort = function () {'
it "replaces the last character if the length of the input's value doesn't increase, as occurs with the accented character menu", ->
componentNode.dispatchEvent(buildTextInputEvent(data: 'u', target: inputNode))
nextAnimationFrame()
expect(editor.lineTextForBufferRow(0)).toBe 'uvar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'uvar quicksort = function () {'
# simulate the accented character suggestion's selection of the previous character
inputNode.setSelectionRange(0, 1)
componentNode.dispatchEvent(buildTextInputEvent(data: 'ü', target: inputNode))
nextAnimationFrame()
expect(editor.lineTextForBufferRow(0)).toBe 'üvar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'üvar quicksort = function () {'
it "does not handle input events when input is disabled", ->
component.setInputEnabled(false)
componentNode.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode))
expect(nextAnimationFrame).toBe noAnimationFrame
expect(editor.lineTextForBufferRow(0)).toBe 'var quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
describe "when IME composition is used to insert international characters", ->
inputNode = null
@@ -1910,46 +1806,46 @@ describe "EditorComponent", ->
it "inserts the chosen completion", ->
componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode))
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'svar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'svar quicksort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'sdvar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'sdvar quicksort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode))
componentNode.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe '速度var quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe '速度var quicksort = function () {'
it "reverts back to the original text when the completion helper is dismissed", ->
componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode))
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'svar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'svar quicksort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'sdvar quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'sdvar quicksort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'var quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
it "allows multiple accented character to be inserted with the ' on a US international layout", ->
inputNode.value = "'"
inputNode.setSelectionRange(0, 1)
componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode))
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: "'", target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe "'var quicksort = function () {"
expect(editor.lineForBufferRow(0)).toBe "'var quicksort = function () {"
componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode))
componentNode.dispatchEvent(buildTextInputEvent(data: 'á', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe "ávar quicksort = function () {"
expect(editor.lineForBufferRow(0)).toBe "ávar quicksort = function () {"
inputNode.value = "'"
inputNode.setSelectionRange(0, 1)
componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode))
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: "'", target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe "á'var quicksort = function () {"
expect(editor.lineForBufferRow(0)).toBe "á'var quicksort = function () {"
componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode))
componentNode.dispatchEvent(buildTextInputEvent(data: 'á', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe "áávar quicksort = function () {"
expect(editor.lineForBufferRow(0)).toBe "áávar quicksort = function () {"
describe "when a string is selected", ->
beforeEach ->
@@ -1958,25 +1854,25 @@ describe "EditorComponent", ->
it "inserts the chosen completion", ->
componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode))
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'var ssort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var ssort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'var sdsort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var sdsort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode))
componentNode.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'var 速度sort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var 速度sort = function () {'
it "reverts back to the original text when the completion helper is dismissed", ->
componentNode.dispatchEvent(buildIMECompositionEvent('compositionstart', target: inputNode))
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'var ssort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var ssort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'var sdsort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var sdsort = function () {'
componentNode.dispatchEvent(buildIMECompositionEvent('compositionend', target: inputNode))
expect(editor.lineTextForBufferRow(0)).toBe 'var quicksort = function () {'
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
describe "commands", ->
describe "editor:consolidate-selections", ->
@@ -1999,7 +1895,7 @@ describe "EditorComponent", ->
hiddenParent.style.display = 'none'
contentNode.appendChild(hiddenParent)
wrapperView = new EditorView(editor, {lineOverdrawMargin})
wrapperView = new ReactEditorView(editor, {lineOverdrawMargin})
wrapperNode = wrapperView.element
wrapperView.appendTo(hiddenParent)
@@ -2041,7 +1937,6 @@ describe "EditorComponent", ->
wrapperView.hide()
component.setFontSize(22)
editor.getBuffer().insert([0, 0], 'a') # regression test against atom/atom#3318
wrapperView.show()
editor.setCursorBufferPosition([0, Infinity])
@@ -2110,7 +2005,7 @@ describe "EditorComponent", ->
describe "soft wrapping", ->
beforeEach ->
editor.setSoftWrapped(true)
editor.setSoftWrap(true)
nextAnimationFrame()
it "updates the wrap location when the editor is resized", ->
@@ -2208,13 +2103,9 @@ describe "EditorComponent", ->
it "adds the 'mini' class to the wrapper view", ->
expect(wrapperNode.classList.contains('mini')).toBe true
it "does not have an opaque background on lines", ->
expect(component.refs.lines.getDOMNode().getAttribute('style')).not.toContain 'background-color'
it "does not render invisible characters", ->
atom.config.set('editor.invisibles', eol: 'E')
atom.config.set('editor.showInvisibles', true)
nextAnimationFrame()
component.setInvisibles(eol: 'E')
component.setShowInvisibles(true)
expect(component.lineNodeForScreenRow(0).textContent).toBe 'var quicksort = function () {'
it "does not assign an explicit line-height on the editor contents", ->
@@ -2236,10 +2127,10 @@ describe "EditorComponent", ->
describe "legacy editor compatibility", ->
it "triggers the screen-lines-changed event before the editor:display-update event", ->
editor.setSoftWrapped(true)
editor.setSoftWrap(true)
callingOrder = []
editor.onDidChange -> callingOrder.push 'screen-lines-changed'
editor.on 'screen-lines-changed', -> callingOrder.push 'screen-lines-changed'
wrapperView.on 'editor:display-updated', -> callingOrder.push 'editor:display-updated'
editor.insertText("HELLO! HELLO!\n HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! ")
nextAnimationFrame()
@@ -2253,12 +2144,6 @@ describe "EditorComponent", ->
setEditorWidthInChars(wrapperView, 10)
expect(componentNode.querySelector('.scroll-view').offsetWidth).toBe charWidth * 10
describe "grammar data attributes", ->
it "adds and updates the grammar data attribute based on the current grammar", ->
expect(wrapperNode.dataset.grammar).toBe 'source js'
editor.setGrammar(atom.syntax.nullGrammar)
expect(wrapperNode.dataset.grammar).toBe 'text plain null-grammar'
buildMouseEvent = (type, properties...) ->
properties = extend({bubbles: true, cancelable: true}, properties...)
properties.detail ?= 1
+344 -636
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -1,4 +0,0 @@
module.exports =
activate: ->
deactivate: -> throw new Error('Top that')
serialize: ->
+31 -63
Ver Arquivo
@@ -1,5 +1,5 @@
temp = require 'temp'
GitRepository = require '../src/git-repository'
Git = require '../src/git'
fs = require 'fs-plus'
path = require 'path'
Task = require '../src/task'
@@ -10,7 +10,7 @@ copyRepository = ->
fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
workingDirPath
describe "GitRepository", ->
describe "Git", ->
repo = null
beforeEach ->
@@ -22,28 +22,28 @@ describe "GitRepository", ->
describe "@open(path)", ->
it "returns null when no repository is found", ->
expect(GitRepository.open(path.join(temp.dir, 'nogit.txt'))).toBeNull()
expect(Git.open(path.join(temp.dir, 'nogit.txt'))).toBeNull()
describe "new GitRepository(path)", ->
describe "new Git(path)", ->
it "throws an exception when no repository is found", ->
expect(-> new GitRepository(path.join(temp.dir, 'nogit.txt'))).toThrow()
expect(-> new Git(path.join(temp.dir, 'nogit.txt'))).toThrow()
describe ".getPath()", ->
it "returns the repository path for a .git directory path", ->
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git', 'HEAD'))
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'master.git', 'HEAD'))
expect(repo.getPath()).toBe path.join(__dirname, 'fixtures', 'git', 'master.git')
it "returns the repository path for a repository path", ->
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git'))
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'master.git'))
expect(repo.getPath()).toBe path.join(__dirname, 'fixtures', 'git', 'master.git')
describe ".isPathIgnored(path)", ->
it "returns true for an ignored path", ->
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'ignore.git'))
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'ignore.git'))
expect(repo.isPathIgnored('a.txt')).toBeTruthy()
it "returns false for a non-ignored path", ->
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'ignore.git'))
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'ignore.git'))
expect(repo.isPathIgnored('b.txt')).toBeFalsy()
describe ".isPathModified(path)", ->
@@ -51,7 +51,7 @@ describe "GitRepository", ->
beforeEach ->
workingDirPath = copyRepository()
repo = new GitRepository(workingDirPath)
repo = new Git(workingDirPath)
filePath = path.join(workingDirPath, 'a.txt')
newPath = path.join(workingDirPath, 'new-path.txt')
@@ -75,7 +75,7 @@ describe "GitRepository", ->
beforeEach ->
workingDirPath = copyRepository()
repo = new GitRepository(workingDirPath)
repo = new Git(workingDirPath)
filePath = path.join(workingDirPath, 'a.txt')
newPath = path.join(workingDirPath, 'new-path.txt')
fs.writeFileSync(newPath, "i'm new here")
@@ -92,7 +92,7 @@ describe "GitRepository", ->
beforeEach ->
workingDirPath = copyRepository()
repo = new GitRepository(workingDirPath)
repo = new Git(workingDirPath)
filePath = path.join(workingDirPath, 'a.txt')
it "no longer reports a path as modified after checkout", ->
@@ -111,49 +111,17 @@ describe "GitRepository", ->
fs.writeFileSync(filePath, 'ch ch changes')
repo.getPathStatus(filePath)
statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatus statusHandler
repo.on 'status-changed', statusHandler
repo.checkoutHead(filePath)
expect(statusHandler.callCount).toBe 1
expect(statusHandler.argsForCall[0][0]).toEqual {path: filePath, pathStatus: 0}
expect(statusHandler.argsForCall[0][0..1]).toEqual [filePath, 0]
repo.checkoutHead(filePath)
expect(statusHandler.callCount).toBe 1
describe ".checkoutHeadForEditor(editor)", ->
[filePath, editor] = []
beforeEach ->
workingDirPath = copyRepository()
repo = new GitRepository(workingDirPath)
filePath = path.join(workingDirPath, 'a.txt')
fs.writeFileSync(filePath, 'ch ch changes')
waitsForPromise ->
atom.workspace.open(filePath)
runs ->
editor = atom.workspace.getActiveEditor()
it "displays a confirmation dialog by default", ->
spyOn(atom, 'confirm').andCallFake ({buttons}) -> buttons.OK()
atom.config.set('editor.confirmCheckoutHeadRevision', true)
repo.checkoutHeadForEditor(editor)
expect(fs.readFileSync(filePath, 'utf8')).toBe ''
it "does not display a dialog when confirmation is disabled", ->
spyOn(atom, 'confirm')
atom.config.set('editor.confirmCheckoutHeadRevision', false)
repo.checkoutHeadForEditor(editor)
expect(fs.readFileSync(filePath, 'utf8')).toBe ''
expect(atom.confirm).not.toHaveBeenCalled()
describe ".destroy()", ->
it "throws an exception when any method is called after it is called", ->
repo = new GitRepository(require.resolve('./fixtures/git/master.git/HEAD'))
repo = new Git(require.resolve('./fixtures/git/master.git/HEAD'))
repo.destroy()
expect(-> repo.getShortHead()).toThrow()
@@ -162,16 +130,16 @@ describe "GitRepository", ->
beforeEach ->
workingDirectory = copyRepository()
repo = new GitRepository(workingDirectory)
repo = new Git(workingDirectory)
filePath = path.join(workingDirectory, 'file.txt')
it "trigger a status-changed event when the new status differs from the last cached one", ->
statusHandler = jasmine.createSpy("statusHandler")
repo.onDidChangeStatus statusHandler
repo.on 'status-changed', statusHandler
fs.writeFileSync(filePath, '')
status = repo.getPathStatus(filePath)
expect(statusHandler.callCount).toBe 1
expect(statusHandler.argsForCall[0][0]).toEqual {path: filePath, pathStatus: status}
expect(statusHandler.argsForCall[0][0..1]).toEqual [filePath, status]
fs.writeFileSync(filePath, 'abc')
status = repo.getPathStatus(filePath)
@@ -182,7 +150,7 @@ describe "GitRepository", ->
beforeEach ->
workingDirectory = copyRepository()
repo = new GitRepository(workingDirectory)
repo = new Git(workingDirectory)
directoryPath = path.join(workingDirectory, 'dir')
filePath = path.join(directoryPath, 'b.txt')
@@ -197,7 +165,7 @@ describe "GitRepository", ->
beforeEach ->
workingDirectory = copyRepository()
repo = new GitRepository(workingDirectory)
repo = new Git(workingDirectory)
modifiedPath = path.join(workingDirectory, 'file.txt')
newPath = path.join(workingDirectory, 'untracked.txt')
cleanPath = path.join(workingDirectory, 'other.txt')
@@ -208,7 +176,7 @@ describe "GitRepository", ->
it "returns status information for all new and modified files", ->
fs.writeFileSync(modifiedPath, 'making this path modified')
statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses statusHandler
repo.on 'statuses-changed', statusHandler
repo.refreshStatus()
waitsFor ->
@@ -232,19 +200,19 @@ describe "GitRepository", ->
editor.insertNewline()
statusHandler = jasmine.createSpy('statusHandler')
atom.project.getRepo().onDidChangeStatus statusHandler
atom.project.getRepo().on 'status-changed', statusHandler
editor.save()
expect(statusHandler.callCount).toBe 1
expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256}
expect(statusHandler).toHaveBeenCalledWith editor.getPath(), 256
it "emits a status-changed event when a buffer is reloaded", ->
fs.writeFileSync(editor.getPath(), 'changed')
statusHandler = jasmine.createSpy('statusHandler')
atom.project.getRepo().onDidChangeStatus statusHandler
atom.project.getRepo().on 'status-changed', statusHandler
editor.getBuffer().reload()
expect(statusHandler.callCount).toBe 1
expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256}
expect(statusHandler).toHaveBeenCalledWith editor.getPath(), 256
editor.getBuffer().reload()
expect(statusHandler.callCount).toBe 1
@@ -252,11 +220,11 @@ describe "GitRepository", ->
fs.writeFileSync(editor.getPath(), 'changed')
statusHandler = jasmine.createSpy('statusHandler')
atom.project.getRepo().onDidChangeStatus statusHandler
editor.getBuffer().emitter.emit 'did-change-path'
atom.project.getRepo().on 'status-changed', statusHandler
editor.getBuffer().emit 'path-changed'
expect(statusHandler.callCount).toBe 1
expect(statusHandler).toHaveBeenCalledWith {path: editor.getPath(), pathStatus: 256}
editor.getBuffer().emitter.emit 'did-change-path'
expect(statusHandler).toHaveBeenCalledWith editor.getPath(), 256
editor.getBuffer().emit 'path-changed'
expect(statusHandler.callCount).toBe 1
describe "when a project is deserialized", ->
@@ -283,7 +251,7 @@ describe "GitRepository", ->
buffer.append('changes')
statusHandler = jasmine.createSpy('statusHandler')
project2.getRepo().onDidChangeStatus statusHandler
project2.getRepo().on 'status-changed', statusHandler
buffer.save()
expect(statusHandler.callCount).toBe 1
expect(statusHandler).toHaveBeenCalledWith {path: buffer.getPath(), pathStatus: 256}
expect(statusHandler).toHaveBeenCalledWith buffer.getPath(), 256
-9
Ver Arquivo
@@ -7,8 +7,6 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
{TerminalReporter} = require 'jasmine-tagged'
disableFocusMethods() if process.env.JANKY_SHA1
TimeReporter = require './time-reporter'
timeReporter = new TimeReporter()
@@ -40,10 +38,3 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
$('body').append $$ -> @div id: 'jasmine-content'
jasmineEnv.execute()
disableFocusMethods = ->
['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) ->
focusMethod = window[methodName]
window[methodName] = (description) ->
error = new Error('Focused spec is running on CI')
focusMethod description, -> throw error
+18 -40
Ver Arquivo
@@ -70,28 +70,6 @@ describe "LanguageMode", ->
expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull()
expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7]
describe ".rowRangeForCommentAtBufferRow(bufferRow)", ->
it "returns the start/end rows of the foldable comment starting at the given row", ->
buffer.setText("//this is a multi line comment\n//another line")
expect(languageMode.rowRangeForCommentAtBufferRow(0)).toEqual [0, 1]
expect(languageMode.rowRangeForCommentAtBufferRow(1)).toEqual [0, 1]
buffer.setText("//this is a multi line comment\n//another line\n//and one more")
expect(languageMode.rowRangeForCommentAtBufferRow(0)).toEqual [0, 2]
expect(languageMode.rowRangeForCommentAtBufferRow(1)).toEqual [0, 2]
buffer.setText("//this is a multi line comment\n\n//with an empty line")
expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined()
expect(languageMode.rowRangeForCommentAtBufferRow(1)).toBeUndefined()
expect(languageMode.rowRangeForCommentAtBufferRow(2)).toBeUndefined()
buffer.setText("//this is a single line comment\n")
expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined()
expect(languageMode.rowRangeForCommentAtBufferRow(1)).toBeUndefined()
buffer.setText("//this is a single line comment")
expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined()
describe "suggestedIndentForBufferRow", ->
it "returns the suggested indentation based on auto-indent/outdent rules", ->
expect(languageMode.suggestedIndentForBufferRow(0)).toBe 0
@@ -297,46 +275,46 @@ describe "LanguageMode", ->
it "folds every foldable line", ->
languageMode.foldAll()
fold1 = editor.tokenizedLineForScreenRow(0).fold
fold1 = editor.lineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 12]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(1).fold
fold2 = editor.lineForScreenRow(1).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 9]
fold2.destroy()
fold3 = editor.tokenizedLineForScreenRow(4).fold
fold3 = editor.lineForScreenRow(4).fold
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [4, 7]
describe ".foldBufferRow(bufferRow)", ->
describe "when bufferRow can be folded", ->
it "creates a fold based on the syntactic region starting at the given row", ->
languageMode.foldBufferRow(1)
fold = editor.tokenizedLineForScreenRow(1).fold
fold = editor.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
describe "when bufferRow can't be folded", ->
it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
languageMode.foldBufferRow(8)
fold = editor.tokenizedLineForScreenRow(1).fold
fold = editor.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
describe "when the bufferRow is already folded", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
languageMode.foldBufferRow(2)
expect(editor.tokenizedLineForScreenRow(1).fold).toBeDefined()
expect(editor.tokenizedLineForScreenRow(0).fold).not.toBeDefined()
expect(editor.lineForScreenRow(1).fold).toBeDefined()
expect(editor.lineForScreenRow(0).fold).not.toBeDefined()
languageMode.foldBufferRow(1)
expect(editor.tokenizedLineForScreenRow(0).fold).toBeDefined()
expect(editor.lineForScreenRow(0).fold).toBeDefined()
describe "when the bufferRow is in a multi-line comment", ->
it "searches upward and downward for surrounding comment lines and folds them as a single fold", ->
buffer.insert([1,0], " //this is a comment\n // and\n //more docs\n\n//second comment")
languageMode.foldBufferRow(1)
fold = editor.tokenizedLineForScreenRow(1).fold
fold = editor.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 3
@@ -344,7 +322,7 @@ describe "LanguageMode", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
buffer.insert([1,0], " //this is a single line comment\n")
languageMode.foldBufferRow(1)
fold = editor.tokenizedLineForScreenRow(0).fold
fold = editor.lineForScreenRow(0).fold
expect(fold.getStartRow()).toBe 0
expect(fold.getEndRow()).toBe 13
@@ -379,38 +357,38 @@ describe "LanguageMode", ->
it "folds every foldable line", ->
languageMode.foldAll()
fold1 = editor.tokenizedLineForScreenRow(0).fold
fold1 = editor.lineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(1).fold
fold2 = editor.lineForScreenRow(1).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 4]
fold3 = editor.tokenizedLineForScreenRow(2).fold.destroy()
fold3 = editor.lineForScreenRow(2).fold.destroy()
fold4 = editor.tokenizedLineForScreenRow(3).fold
fold4 = editor.lineForScreenRow(3).fold
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]
describe ".foldAllAtIndentLevel()", ->
it "folds every foldable range at a given indentLevel", ->
languageMode.foldAllAtIndentLevel(2)
fold1 = editor.tokenizedLineForScreenRow(6).fold
fold1 = editor.lineForScreenRow(6).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [6, 8]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(11).fold
fold2 = editor.lineForScreenRow(11).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
fold2.destroy()
it "does not fold anything but the indentLevel", ->
languageMode.foldAllAtIndentLevel(0)
fold1 = editor.tokenizedLineForScreenRow(0).fold
fold1 = editor.lineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(5).fold
fold2 = editor.lineForScreenRow(5).fold
expect(fold2).toBeFalsy()
describe ".isFoldableAtBufferRow(bufferRow)", ->
+1 -4
Ver Arquivo
@@ -5,9 +5,6 @@ ThemePackage = require '../src/theme-package'
describe "Package", ->
describe "when the package contains incompatible native modules", ->
beforeEach ->
spyOn(atom, 'inDevMode').andReturn(false)
it "does not activate it", ->
packagePath = atom.project.resolve('packages/package-with-incompatible-native-module')
pack = new Package(packagePath)
@@ -102,6 +99,6 @@ describe "Package", ->
theme.activate()
it "deactivated event fires on .deactivate()", ->
theme.onDidDeactivate spy = jasmine.createSpy()
theme.on 'deactivated', spy = jasmine.createSpy()
theme.deactivate()
expect(spy).toHaveBeenCalled()
+21 -70
Ver Arquivo
@@ -27,91 +27,42 @@ describe "PaneContainer", ->
it "preserves the active pane across serialization, independent of focus", ->
pane3A.activate()
expect(containerA.getActivePane()).toBe pane3A
expect(containerA.activePane).toBe pane3A
containerB = containerA.testSerialization()
[pane1B, pane2B, pane3B] = containerB.getPanes()
expect(containerB.getActivePane()).toBe pane3B
expect(containerB.activePane).toBe pane3B
it "does not allow the root pane to be destroyed", ->
container = new PaneContainer
container.getRoot().destroy()
expect(container.getRoot()).toBeDefined()
expect(container.getRoot().isDestroyed()).toBe false
describe "::getActivePane()", ->
describe "::activePane", ->
[container, pane1, pane2] = []
beforeEach ->
container = new PaneContainer
pane1 = container.getRoot()
pane1 = container.root
it "returns the first pane if no pane has been made active", ->
expect(container.getActivePane()).toBe pane1
expect(pane1.isActive()).toBe true
it "references the first pane if no pane has been made active", ->
expect(container.activePane).toBe pane1
expect(pane1.active).toBe true
it "returns the most pane on which ::activate() was most recently called", ->
it "references the most pane on which ::activate was most recently called", ->
pane2 = pane1.splitRight()
pane2.activate()
expect(container.getActivePane()).toBe pane2
expect(pane1.isActive()).toBe false
expect(pane2.isActive()).toBe true
expect(container.activePane).toBe pane2
expect(pane1.active).toBe false
expect(pane2.active).toBe true
pane1.activate()
expect(container.getActivePane()).toBe pane1
expect(pane1.isActive()).toBe true
expect(pane2.isActive()).toBe false
expect(container.activePane).toBe pane1
expect(pane1.active).toBe true
expect(pane2.active).toBe false
it "returns the next pane if the current active pane is destroyed", ->
it "is reassigned to the next pane if the current active pane is destroyed", ->
pane2 = pane1.splitRight()
pane2.activate()
pane2.destroy()
expect(container.getActivePane()).toBe pane1
expect(pane1.isActive()).toBe true
expect(container.activePane).toBe pane1
expect(pane1.active).toBe true
describe "::onDidChangeActivePaneItem()", ->
[container, pane1, pane2, observed] = []
beforeEach ->
container = new PaneContainer(root: new Pane(items: [new Object, new Object]))
container.getRoot().splitRight(items: [new Object, new Object])
[pane1, pane2] = container.getPanes()
observed = []
container.onDidChangeActivePaneItem (item) -> observed.push(item)
it "invokes observers when the active item of the active pane changes", ->
pane2.activateNextItem()
pane2.activateNextItem()
expect(observed).toEqual [pane2.itemAtIndex(1), pane2.itemAtIndex(0)]
it "invokes observers when the active pane changes", ->
pane1.activate()
pane2.activate()
expect(observed).toEqual [pane1.itemAtIndex(0), pane2.itemAtIndex(0)]
describe "::observePanes()", ->
it "invokes observers with all current and future panes", ->
container = new PaneContainer
container.getRoot().splitRight()
[pane1, pane2] = container.getPanes()
observed = []
container.observePanes (pane) -> observed.push(pane)
pane3 = pane2.splitDown()
pane4 = pane2.splitRight()
expect(observed).toEqual [pane1, pane2, pane3, pane4]
describe "::observePaneItems()", ->
it "invokes observers with all current and future pane items", ->
container = new PaneContainer(root: new Pane(items: [new Object, new Object]))
container.getRoot().splitRight(items: [new Object])
[pane1, pane2] = container.getPanes()
observed = []
container.observePaneItems (pane) -> observed.push(pane)
pane3 = pane2.splitDown(items: [new Object])
pane3.addItems([new Object, new Object])
expect(observed).toEqual container.getPaneItems()
it "does not allow the root pane to be destroyed", ->
pane1.destroy()
expect(container.root).toBe pane1
expect(pane1.isDestroyed()).toBe false
+105 -216
Ver Arquivo
@@ -21,83 +21,39 @@ describe "Pane", ->
describe "construction", ->
it "sets the active item to the first item", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
expect(pane.getActiveItem()).toBe pane.itemAtIndex(0)
expect(pane.activeItem).toBe pane.items[0]
it "compacts the items array", ->
pane = new Pane(items: [undefined, new Item("A"), null, new Item("B")])
expect(pane.getItems().length).toBe 2
expect(pane.getActiveItem()).toBe pane.itemAtIndex(0)
describe "::activate()", ->
[container, pane1, pane2] = []
beforeEach ->
container = new PaneContainer(root: new Pane)
container.getRoot().splitRight()
[pane1, pane2] = container.getPanes()
it "changes the active pane on the container", ->
expect(container.getActivePane()).toBe pane2
pane1.activate()
expect(container.getActivePane()).toBe pane1
pane2.activate()
expect(container.getActivePane()).toBe pane2
it "invokes ::onDidChangeActivePane observers on the container", ->
observed = []
container.onDidChangeActivePane (activePane) -> observed.push(activePane)
pane1.activate()
pane1.activate()
pane2.activate()
pane1.activate()
expect(observed).toEqual [pane1, pane2, pane1]
it "invokes ::onDidChangeActive observers on the relevant panes", ->
observed = []
pane1.onDidChangeActive (active) -> observed.push(active)
pane1.activate()
pane2.activate()
expect(observed).toEqual [true, false]
it "invokes ::onDidActivate() observers", ->
eventCount = 0
pane1.onDidActivate -> eventCount++
pane1.activate()
pane1.activate()
pane2.activate()
expect(eventCount).toBe 2
expect(pane.items.length).toBe 2
expect(pane.activeItem).toBe pane.items[0]
describe "::addItem(item, index)", ->
it "adds the item at the given index", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
[item1, item2] = pane.getItems()
[item1, item2] = pane.items
item3 = new Item("C")
pane.addItem(item3, 1)
expect(pane.getItems()).toEqual [item1, item3, item2]
expect(pane.items).toEqual [item1, item3, item2]
it "adds the item after the active item if no index is provided", ->
it "adds the item after the active item ", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
[item1, item2, item3] = pane.getItems()
[item1, item2, item3] = pane.items
pane.activateItem(item2)
item4 = new Item("D")
pane.addItem(item4)
expect(pane.getItems()).toEqual [item1, item2, item4, item3]
expect(pane.items).toEqual [item1, item2, item4, item3]
it "sets the active item after adding the first item", ->
pane = new Pane
item = new Item("A")
pane.addItem(item)
expect(pane.getActiveItem()).toBe item
it "invokes ::onDidAddItem() observers", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
events = []
pane.onDidAddItem (event) -> events.push(event)
pane.on 'item-added', -> events.push('item-added')
pane.$activeItem.changes.onValue -> events.push('active-item-changed')
item = new Item("C")
pane.addItem(item, 1)
expect(events).toEqual [{item, index: 1}]
pane.addItem(item)
expect(pane.activeItem).toBe item
expect(events).toEqual ['item-added', 'active-item-changed']
describe "::activateItem(item)", ->
pane = null
@@ -106,102 +62,83 @@ describe "Pane", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
it "changes the active item to the current item", ->
expect(pane.getActiveItem()).toBe pane.itemAtIndex(0)
pane.activateItem(pane.itemAtIndex(1))
expect(pane.getActiveItem()).toBe pane.itemAtIndex(1)
expect(pane.activeItem).toBe pane.items[0]
pane.activateItem(pane.items[1])
expect(pane.activeItem).toBe pane.items[1]
it "adds the given item if it isn't present in ::items", ->
item = new Item("C")
pane.activateItem(item)
expect(item in pane.getItems()).toBe true
expect(pane.getActiveItem()).toBe item
it "invokes ::onDidChangeActiveItem() observers", ->
observed = []
pane.onDidChangeActiveItem (item) -> observed.push(item)
pane.activateItem(pane.itemAtIndex(1))
expect(observed).toEqual [pane.itemAtIndex(1)]
expect(item in pane.items).toBe true
expect(pane.activeItem).toBe item
describe "::activateNextItem() and ::activatePreviousItem()", ->
it "sets the active item to the next/previous item, looping around at either end", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
[item1, item2, item3] = pane.getItems()
[item1, item2, item3] = pane.items
expect(pane.getActiveItem()).toBe item1
expect(pane.activeItem).toBe item1
pane.activatePreviousItem()
expect(pane.getActiveItem()).toBe item3
expect(pane.activeItem).toBe item3
pane.activatePreviousItem()
expect(pane.getActiveItem()).toBe item2
expect(pane.activeItem).toBe item2
pane.activateNextItem()
expect(pane.getActiveItem()).toBe item3
expect(pane.activeItem).toBe item3
pane.activateNextItem()
expect(pane.getActiveItem()).toBe item1
expect(pane.activeItem).toBe item1
describe "::activateItemAtIndex(index)", ->
it "activates the item at the given index", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
[item1, item2, item3] = pane.getItems()
[item1, item2, item3] = pane.items
pane.activateItemAtIndex(2)
expect(pane.getActiveItem()).toBe item3
expect(pane.activeItem).toBe item3
pane.activateItemAtIndex(1)
expect(pane.getActiveItem()).toBe item2
expect(pane.activeItem).toBe item2
pane.activateItemAtIndex(0)
expect(pane.getActiveItem()).toBe item1
expect(pane.activeItem).toBe item1
# Doesn't fail with out-of-bounds indices
pane.activateItemAtIndex(100)
expect(pane.getActiveItem()).toBe item1
expect(pane.activeItem).toBe item1
pane.activateItemAtIndex(-1)
expect(pane.getActiveItem()).toBe item1
expect(pane.activeItem).toBe item1
describe "::destroyItem(item)", ->
[pane, item1, item2, item3] = []
beforeEach ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
[item1, item2, item3] = pane.getItems()
[item1, item2, item3] = pane.items
it "removes the item from the items list and destroyes it", ->
expect(pane.getActiveItem()).toBe item1
it "removes the item from the items list", ->
expect(pane.activeItem).toBe item1
pane.destroyItem(item2)
expect(item2 in pane.getItems()).toBe false
expect(item2.isDestroyed()).toBe true
expect(pane.getActiveItem()).toBe item1
expect(item2 in pane.items).toBe false
expect(pane.activeItem).toBe item1
pane.destroyItem(item1)
expect(item1 in pane.getItems()).toBe false
expect(item1.isDestroyed()).toBe true
it "invokes ::onWillDestroyItem() observers before destroying the item", ->
events = []
pane.onWillDestroyItem (event) ->
expect(item2.isDestroyed()).toBe false
events.push(event)
pane.destroyItem(item2)
expect(item2.isDestroyed()).toBe true
expect(events).toEqual [{item: item2, index: 1}]
it "invokes ::onDidRemoveItem() observers", ->
events = []
pane.onDidRemoveItem (event) -> events.push(event)
pane.destroyItem(item2)
expect(events).toEqual [{item: item2, index: 1, destroyed: true}]
expect(item1 in pane.items).toBe false
describe "when the destroyed item is the active item and is the first item", ->
it "activates the next item", ->
expect(pane.getActiveItem()).toBe item1
expect(pane.activeItem).toBe item1
pane.destroyItem(item1)
expect(pane.getActiveItem()).toBe item2
expect(pane.activeItem).toBe item2
describe "when the destroyed item is the active item and is not the first item", ->
beforeEach ->
pane.activateItem(item2)
it "activates the previous item", ->
expect(pane.getActiveItem()).toBe item2
expect(pane.activeItem).toBe item2
pane.destroyItem(item2)
expect(pane.getActiveItem()).toBe item1
expect(pane.activeItem).toBe item1
it "emits 'item-removed' with the item, its index, and true indicating the item is being destroyed", ->
pane.on 'item-removed', itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
pane.destroyItem(item2)
expect(itemRemovedHandler).toHaveBeenCalledWith(item2, 1, true)
describe "if the item is modified", ->
itemUri = null
@@ -220,7 +157,7 @@ describe "Pane", ->
pane.destroyItem(item1)
expect(item1.save).toHaveBeenCalled()
expect(item1 in pane.getItems()).toBe false
expect(item1 in pane.items).toBe false
expect(item1.isDestroyed()).toBe true
describe "when the item has no uri", ->
@@ -233,7 +170,7 @@ describe "Pane", ->
expect(atom.showSaveDialogSync).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalledWith("/selected/path")
expect(item1 in pane.getItems()).toBe false
expect(item1 in pane.items).toBe false
expect(item1.isDestroyed()).toBe true
describe "if the [Don't Save] option is selected", ->
@@ -242,7 +179,7 @@ describe "Pane", ->
pane.destroyItem(item1)
expect(item1.save).not.toHaveBeenCalled()
expect(item1 in pane.getItems()).toBe false
expect(item1 in pane.items).toBe false
expect(item1.isDestroyed()).toBe true
describe "if the [Cancel] option is selected", ->
@@ -251,7 +188,7 @@ describe "Pane", ->
pane.destroyItem(item1)
expect(item1.save).not.toHaveBeenCalled()
expect(item1 in pane.getItems()).toBe true
expect(item1 in pane.items).toBe true
expect(item1.isDestroyed()).toBe false
describe "when the last item is destroyed", ->
@@ -260,7 +197,7 @@ describe "Pane", ->
expect(atom.config.get('core.destroyEmptyPanes')).toBe false
pane.destroyItem(item) for item in pane.getItems()
expect(pane.isDestroyed()).toBe false
expect(pane.getActiveItem()).toBeUndefined()
expect(pane.activeItem).toBeUndefined()
expect(-> pane.saveActiveItem()).not.toThrow()
expect(-> pane.saveActiveItemAs()).not.toThrow()
@@ -273,10 +210,10 @@ describe "Pane", ->
describe "::destroyActiveItem()", ->
it "destroys the active item", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
activeItem = pane.getActiveItem()
activeItem = pane.activeItem
pane.destroyActiveItem()
expect(activeItem.isDestroyed()).toBe true
expect(activeItem in pane.getItems()).toBe false
expect(activeItem in pane.items).toBe false
it "does not throw an exception if there are no more items", ->
pane = new Pane
@@ -285,40 +222,27 @@ describe "Pane", ->
describe "::destroyItems()", ->
it "destroys all items", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
[item1, item2, item3] = pane.getItems()
[item1, item2, item3] = pane.items
pane.destroyItems()
expect(item1.isDestroyed()).toBe true
expect(item2.isDestroyed()).toBe true
expect(item3.isDestroyed()).toBe true
expect(pane.getItems()).toEqual []
describe "::observeItems()", ->
it "invokes the observer with all current and future items", ->
pane = new Pane(items: [new Item, new Item])
[item1, item2] = pane.getItems()
observed = []
pane.observeItems (item) -> observed.push(item)
item3 = new Item
pane.addItem(item3)
expect(observed).toEqual [item1, item2, item3]
expect(pane.items).toEqual []
describe "when an item emits a destroyed event", ->
it "removes it from the list of items", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
[item1, item2, item3] = pane.getItems()
pane.itemAtIndex(1).destroy()
expect(pane.getItems()).toEqual [item1, item3]
[item1, item2, item3] = pane.items
pane.items[1].destroy()
expect(pane.items).toEqual [item1, item3]
describe "::destroyInactiveItems()", ->
it "destroys all items but the active item", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
[item1, item2, item3] = pane.getItems()
[item1, item2, item3] = pane.items
pane.activateItem(item2)
pane.destroyInactiveItems()
expect(pane.getItems()).toEqual [item2]
expect(pane.items).toEqual [item2]
describe "::saveActiveItem()", ->
pane = null
@@ -329,30 +253,30 @@ describe "Pane", ->
describe "when the active item has a uri", ->
beforeEach ->
pane.getActiveItem().uri = "test"
pane.activeItem.uri = "test"
describe "when the active item has a save method", ->
it "saves the current item", ->
pane.getActiveItem().save = jasmine.createSpy("save")
pane.activeItem.save = jasmine.createSpy("save")
pane.saveActiveItem()
expect(pane.getActiveItem().save).toHaveBeenCalled()
expect(pane.activeItem.save).toHaveBeenCalled()
describe "when the current item has no save method", ->
it "does nothing", ->
expect(pane.getActiveItem().save).toBeUndefined()
expect(pane.activeItem.save).toBeUndefined()
pane.saveActiveItem()
describe "when the current item has no uri", ->
describe "when the current item has a saveAs method", ->
it "opens a save dialog and saves the current item as the selected path", ->
pane.getActiveItem().saveAs = jasmine.createSpy("saveAs")
pane.activeItem.saveAs = jasmine.createSpy("saveAs")
pane.saveActiveItem()
expect(atom.showSaveDialogSync).toHaveBeenCalled()
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith('/selected/path')
expect(pane.activeItem.saveAs).toHaveBeenCalledWith('/selected/path')
describe "when the current item has no saveAs method", ->
it "does nothing", ->
expect(pane.getActiveItem().saveAs).toBeUndefined()
expect(pane.activeItem.saveAs).toBeUndefined()
pane.saveActiveItem()
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
@@ -365,22 +289,22 @@ describe "Pane", ->
describe "when the current item has a saveAs method", ->
it "opens the save dialog and calls saveAs on the item with the selected path", ->
pane.getActiveItem().path = __filename
pane.getActiveItem().saveAs = jasmine.createSpy("saveAs")
pane.activeItem.path = __filename
pane.activeItem.saveAs = jasmine.createSpy("saveAs")
pane.saveActiveItemAs()
expect(atom.showSaveDialogSync).toHaveBeenCalledWith(__filename)
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith('/selected/path')
expect(pane.activeItem.saveAs).toHaveBeenCalledWith('/selected/path')
describe "when the current item does not have a saveAs method", ->
it "does nothing", ->
expect(pane.getActiveItem().saveAs).toBeUndefined()
expect(pane.activeItem.saveAs).toBeUndefined()
pane.saveActiveItemAs()
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
describe "::itemForUri(uri)", ->
it "returns the item for which a call to .getUri() returns the given uri", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")])
[item1, item2, item3] = pane.getItems()
[item1, item2, item3] = pane.items
item1.uri = "a"
item2.uri = "b"
expect(pane.itemForUri("a")).toBe item1
@@ -388,32 +312,24 @@ describe "Pane", ->
expect(pane.itemForUri("bogus")).toBeUndefined()
describe "::moveItem(item, index)", ->
[pane, item1, item2, item3, item4] = []
beforeEach ->
it "moves the item to the given index and emits an 'item-moved' event with the item and its new index", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")])
[item1, item2, item3, item4] = pane.getItems()
[item1, item2, item3, item4] = pane.items
pane.on 'item-moved', itemMovedHandler = jasmine.createSpy("itemMovedHandler")
it "moves the item to the given index and invokes ::onDidMoveItem observers", ->
pane.moveItem(item1, 2)
expect(pane.getItems()).toEqual [item2, item3, item1, item4]
expect(itemMovedHandler).toHaveBeenCalledWith(item1, 2)
itemMovedHandler.reset()
pane.moveItem(item2, 3)
expect(pane.getItems()).toEqual [item3, item1, item4, item2]
expect(itemMovedHandler).toHaveBeenCalledWith(item2, 3)
itemMovedHandler.reset()
pane.moveItem(item2, 1)
expect(pane.getItems()).toEqual [item3, item2, item1, item4]
it "invokes ::onDidMoveItem() observers", ->
events = []
pane.onDidMoveItem (event) -> events.push(event)
pane.moveItem(item1, 2)
pane.moveItem(item2, 3)
expect(events).toEqual [
{item: item1, oldIndex: 0, newIndex: 2}
{item: item2, oldIndex: 0, newIndex: 3}
]
expect(itemMovedHandler).toHaveBeenCalledWith(item2, 1)
describe "::moveItemToPane(item, pane, index)", ->
[container, pane1, pane2] = []
@@ -423,20 +339,13 @@ describe "Pane", ->
pane1 = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
container = new PaneContainer(root: pane1)
pane2 = pane1.splitRight(items: [new Item("D"), new Item("E")])
[item1, item2, item3] = pane1.getItems()
[item4, item5] = pane2.getItems()
[item1, item2, item3] = pane1.items
[item4, item5] = pane2.items
it "moves the item to the given pane at the given index", ->
pane1.moveItemToPane(item2, pane2, 1)
expect(pane1.getItems()).toEqual [item1, item3]
expect(pane2.getItems()).toEqual [item4, item2, item5]
it "invokes ::onDidRemoveItem() observers", ->
events = []
pane1.onDidRemoveItem (event) -> events.push(event)
pane1.moveItemToPane(item2, pane2, 1)
expect(events).toEqual [{item: item2, index: 1, destroyed: false}]
expect(pane1.items).toEqual [item1, item3]
expect(pane2.items).toEqual [item4, item2, item5]
describe "when the moved item the last item in the source pane", ->
beforeEach ->
@@ -459,27 +368,22 @@ describe "Pane", ->
[pane1, container] = []
beforeEach ->
pane1 = new Pane(items: [new Item("A")])
pane1 = new Pane(items: ["A"])
container = new PaneContainer(root: pane1)
describe "::splitLeft(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a row and inserts a new pane to the left of itself", ->
pane2 = pane1.splitLeft(items: [new Item("B")])
pane3 = pane1.splitLeft(items: [new Item("C")])
pane2 = pane1.splitLeft(items: ["B"])
pane3 = pane1.splitLeft(items: ["C"])
expect(container.root.orientation).toBe 'horizontal'
expect(container.root.children).toEqual [pane2, pane3, pane1]
describe "when `copyActiveItem: true` is passed in the params", ->
it "duplicates the active item", ->
pane2 = pane1.splitLeft(copyActiveItem: true)
expect(pane2.getActiveItem()).toEqual pane1.getActiveItem()
describe "when the parent is a column", ->
it "replaces itself with a row and inserts a new pane to the left of itself", ->
pane1.splitDown()
pane2 = pane1.splitLeft(items: [new Item("B")])
pane3 = pane1.splitLeft(items: [new Item("C")])
pane2 = pane1.splitLeft(items: ["B"])
pane3 = pane1.splitLeft(items: ["C"])
row = container.root.children[0]
expect(row.orientation).toBe 'horizontal'
expect(row.children).toEqual [pane2, pane3, pane1]
@@ -487,21 +391,16 @@ describe "Pane", ->
describe "::splitRight(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a row and inserts a new pane to the right of itself", ->
pane2 = pane1.splitRight(items: [new Item("B")])
pane3 = pane1.splitRight(items: [new Item("C")])
pane2 = pane1.splitRight(items: ["B"])
pane3 = pane1.splitRight(items: ["C"])
expect(container.root.orientation).toBe 'horizontal'
expect(container.root.children).toEqual [pane1, pane3, pane2]
describe "when `copyActiveItem: true` is passed in the params", ->
it "duplicates the active item", ->
pane2 = pane1.splitRight(copyActiveItem: true)
expect(pane2.getActiveItem()).toEqual pane1.getActiveItem()
describe "when the parent is a column", ->
it "replaces itself with a row and inserts a new pane to the right of itself", ->
pane1.splitDown()
pane2 = pane1.splitRight(items: [new Item("B")])
pane3 = pane1.splitRight(items: [new Item("C")])
pane2 = pane1.splitRight(items: ["B"])
pane3 = pane1.splitRight(items: ["C"])
row = container.root.children[0]
expect(row.orientation).toBe 'horizontal'
expect(row.children).toEqual [pane1, pane3, pane2]
@@ -509,21 +408,16 @@ describe "Pane", ->
describe "::splitUp(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a column and inserts a new pane above itself", ->
pane2 = pane1.splitUp(items: [new Item("B")])
pane3 = pane1.splitUp(items: [new Item("C")])
pane2 = pane1.splitUp(items: ["B"])
pane3 = pane1.splitUp(items: ["C"])
expect(container.root.orientation).toBe 'vertical'
expect(container.root.children).toEqual [pane2, pane3, pane1]
describe "when `copyActiveItem: true` is passed in the params", ->
it "duplicates the active item", ->
pane2 = pane1.splitUp(copyActiveItem: true)
expect(pane2.getActiveItem()).toEqual pane1.getActiveItem()
describe "when the parent is a row", ->
it "replaces itself with a column and inserts a new pane above itself", ->
pane1.splitRight()
pane2 = pane1.splitUp(items: [new Item("B")])
pane3 = pane1.splitUp(items: [new Item("C")])
pane2 = pane1.splitUp(items: ["B"])
pane3 = pane1.splitUp(items: ["C"])
column = container.root.children[0]
expect(column.orientation).toBe 'vertical'
expect(column.children).toEqual [pane2, pane3, pane1]
@@ -531,21 +425,16 @@ describe "Pane", ->
describe "::splitDown(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a column and inserts a new pane below itself", ->
pane2 = pane1.splitDown(items: [new Item("B")])
pane3 = pane1.splitDown(items: [new Item("C")])
pane2 = pane1.splitDown(items: ["B"])
pane3 = pane1.splitDown(items: ["C"])
expect(container.root.orientation).toBe 'vertical'
expect(container.root.children).toEqual [pane1, pane3, pane2]
describe "when `copyActiveItem: true` is passed in the params", ->
it "duplicates the active item", ->
pane2 = pane1.splitDown(copyActiveItem: true)
expect(pane2.getActiveItem()).toEqual pane1.getActiveItem()
describe "when the parent is a row", ->
it "replaces itself with a column and inserts a new pane below itself", ->
pane1.splitRight()
pane2 = pane1.splitDown(items: [new Item("B")])
pane3 = pane1.splitDown(items: [new Item("C")])
pane2 = pane1.splitDown(items: ["B"])
pane3 = pane1.splitDown(items: ["C"])
column = container.root.children[0]
expect(column.orientation).toBe 'vertical'
expect(column.children).toEqual [pane1, pane3, pane2]
@@ -566,7 +455,7 @@ describe "Pane", ->
pane2 = pane1.splitRight()
it "destroys the pane's destroyable items", ->
[item1, item2] = pane1.getItems()
[item1, item2] = pane1.items
pane1.destroy()
expect(item1.isDestroyed()).toBe true
expect(item2.isDestroyed()).toBe true
@@ -604,12 +493,12 @@ describe "Pane", ->
it "can serialize and deserialize the pane and all its items", ->
newPane = pane.testSerialization()
expect(newPane.getItems()).toEqual pane.getItems()
expect(newPane.items).toEqual pane.items
it "restores the active item on deserialization", ->
pane.activateItemAtIndex(1)
newPane = pane.testSerialization()
expect(newPane.getActiveItem()).toEqual newPane.itemAtIndex(1)
expect(newPane.activeItem).toEqual newPane.items[1]
it "does not include items that cannot be deserialized", ->
spyOn(console, 'warn')
@@ -617,8 +506,8 @@ describe "Pane", ->
pane.activateItem(unserializable)
newPane = pane.testSerialization()
expect(newPane.getActiveItem()).toEqual pane.itemAtIndex(0)
expect(newPane.getItems().length).toBe pane.getItems().length - 1
expect(newPane.activeItem).toEqual pane.items[0]
expect(newPane.items.length).toBe pane.items.length - 1
it "includes the pane's focus state in the serialized state", ->
pane.focus()
+17 -48
Ver Arquivo
@@ -1,7 +1,6 @@
PaneContainerView = require '../src/pane-container-view'
PaneView = require '../src/pane-view'
fs = require 'fs-plus'
{Emitter} = require 'event-kit'
{$, View} = require 'atom'
path = require 'path'
temp = require 'temp'
@@ -13,14 +12,9 @@ describe "PaneView", ->
@deserialize: ({id, text}) -> new TestView({id, text})
@content: ({id, text}) -> @div class: 'test-view', id: id, tabindex: -1, text
initialize: ({@id, @text}) ->
@emitter = new Emitter
serialize: -> { deserializer: 'TestView', @id, @text }
getUri: -> @id
isEqual: (other) -> other? and @id == other.id and @text == other.text
changeTitle: ->
@emitter.emit 'did-change-title', 'title'
onDidChangeTitle: (callback) ->
@emitter.on 'did-change-title', callback
beforeEach ->
atom.deserializers.add(TestView)
@@ -35,7 +29,7 @@ describe "PaneView", ->
runs ->
pane = container.getRoot()
paneModel = pane.getModel()
paneModel = pane.model
paneModel.addItems([view1, editor1, view2, editor2])
afterEach ->
@@ -43,7 +37,7 @@ describe "PaneView", ->
describe "when the active pane item changes", ->
it "hides all item views except the active one", ->
expect(pane.getActiveItem()).toBe view1
expect(pane.activeItem).toBe view1
expect(view1.css('display')).not.toBe 'none'
pane.activateItem(view2)
@@ -54,7 +48,7 @@ describe "PaneView", ->
itemChangedHandler = jasmine.createSpy("itemChangedHandler")
container.on 'pane:active-item-changed', itemChangedHandler
expect(pane.getActiveItem()).toBe view1
expect(pane.activeItem).toBe view1
paneModel.activateItem(view2)
paneModel.activateItem(view2)
@@ -151,47 +145,22 @@ describe "PaneView", ->
expect(view1.data('preservative')).toBe 1234
describe "when the title of the active item changes", ->
describe 'when there is no onDidChangeTitle method', ->
beforeEach ->
view1.onDidChangeTitle = null
view2.onDidChangeTitle = null
it "emits pane:active-item-title-changed", ->
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
pane.activateItem(view2)
pane.activateItem(view1)
expect(pane.activeItem).toBe view1
it "emits pane:active-item-title-changed", ->
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
view2.trigger 'title-changed'
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
expect(pane.getActiveItem()).toBe view1
view1.trigger 'title-changed'
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
activeItemTitleChangedHandler.reset()
view2.trigger 'title-changed'
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
view1.trigger 'title-changed'
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
activeItemTitleChangedHandler.reset()
pane.activateItem(view2)
view2.trigger 'title-changed'
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
describe 'when there is a onDidChangeTitle method', ->
it "emits pane:active-item-title-changed", ->
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
expect(pane.getActiveItem()).toBe view1
view2.changeTitle()
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
view1.changeTitle()
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
activeItemTitleChangedHandler.reset()
pane.activateItem(view2)
view2.changeTitle()
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
pane.activateItem(view2)
view2.trigger 'title-changed'
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
describe "when an unmodifed buffer's path is deleted", ->
it "removes the pane item", ->
@@ -277,7 +246,7 @@ describe "PaneView", ->
it "transfers focus to the active view", ->
focusHandler = jasmine.createSpy("focusHandler")
pane.getActiveItem().on 'focus', focusHandler
pane.activeItem.on 'focus', focusHandler
pane.focus()
expect(focusHandler).toHaveBeenCalled()
@@ -290,7 +259,7 @@ describe "PaneView", ->
describe "when a pane is split", ->
it "builds the appropriate pane-row and pane-column views", ->
pane1 = pane
pane1Model = pane.getModel()
pane1Model = pane.model
pane.activateItem(editor1)
pane2Model = pane1Model.splitRight(items: [pane1Model.copyActiveItem()])
-5
Ver Arquivo
@@ -190,11 +190,6 @@ describe "Project", ->
expect(atom.project.getPath()?).toBeFalsy()
expect(atom.project.getRootDirectory()?).toBeFalsy()
it "normalizes the path to remove consecutive slashes, ., and .. segments", ->
atom.project.setPath("#{require.resolve('./fixtures/dir/a')}#{path.sep}b#{path.sep}#{path.sep}..")
expect(atom.project.getPath()).toEqual path.dirname(require.resolve('./fixtures/dir/a'))
expect(atom.project.getRootDirectory().path).toEqual path.dirname(require.resolve('./fixtures/dir/a'))
describe ".replace()", ->
[filePath, commentFilePath, sampleContent, sampleCommentContent] = []
+6 -6
Ver Arquivo
@@ -35,7 +35,7 @@ describe "Editor", ->
logLines()
throw new Error("Invalid buffer row #{actualBufferRow} for screen row #{screenRow}", )
actualScreenLine = editor.tokenizedLineForScreenRow(screenRow)
actualScreenLine = editor.lineForScreenRow(screenRow)
unless actualScreenLine.text is referenceScreenLine.text
logLines()
throw new Error("Invalid line text at screen row #{screenRow}")
@@ -50,9 +50,9 @@ describe "Editor", ->
randomlyMutateEditor = ->
if Math.random() < .2
softWrapped = not editor.isSoftWrapped()
steps.push(['setSoftWrapped', softWrapped])
editor.setSoftWrapped(softWrapped)
softWrap = not editor.getSoftWrap()
steps.push(['setSoftWrap', softWrap])
editor.setSoftWrap(softWrap)
else
range = getRandomRange()
text = getRandomText()
@@ -79,11 +79,11 @@ describe "Editor", ->
text
getReferenceScreenLines = ->
if editor.isSoftWrapped()
if editor.getSoftWrap()
screenLines = []
bufferRows = []
for bufferRow in [0..tokenizedBuffer.getLastRow()]
for screenLine in softWrapLine(tokenizedBuffer.tokenizedLineForRow(bufferRow))
for screenLine in softWrapLine(tokenizedBuffer.lineForScreenRow(bufferRow))
screenLines.push(screenLine)
bufferRows.push(bufferRow)
else
+3 -3
Ver Arquivo
@@ -6,7 +6,7 @@ describe "Selection", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
editor = new Editor(buffer: buffer, tabLength: 2)
selection = editor.getLastSelection()
selection = editor.getSelection()
afterEach ->
buffer.destroy()
@@ -57,10 +57,10 @@ describe "Selection", ->
expect(selection.isReversed()).toBeFalsy()
describe "when only the selection's tail is moved (regression)", ->
it "notifies ::onDidChangeRange observers", ->
it "emits the 'screen-range-changed' event", ->
selection.setBufferRange([[2, 0], [2, 10]], reversed: true)
changeScreenRangeHandler = jasmine.createSpy('changeScreenRangeHandler')
selection.onDidChangeRange changeScreenRangeHandler
selection.on 'screen-range-changed', changeScreenRangeHandler
buffer.insert([2, 5], 'abc')
expect(changeScreenRangeHandler).toHaveBeenCalled()
+4 -13
Ver Arquivo
@@ -21,7 +21,6 @@ clipboard = require 'clipboard'
atom.themes.loadBaseStylesheets()
atom.themes.requireStylesheet '../static/jasmine'
atom.themes.initialLoadComplete = true
fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
atom.packages.packageDirPaths.unshift(fixturePackagesPath)
@@ -29,7 +28,7 @@ atom.keymaps.loadBundledKeymaps()
keyBindingsToRestore = atom.keymaps.getKeyBindings()
$(window).on 'core:close', -> window.close()
$(window).on 'beforeunload', ->
$(window).on 'unload', ->
atom.storeWindowDimensions()
atom.saveSync()
$('html,body').css('overflow', 'auto')
@@ -64,7 +63,6 @@ beforeEach ->
atom.project = new Project(path: projectPath)
atom.workspace = new Workspace()
atom.keymaps.keyBindings = _.clone(keyBindingsToRestore)
atom.commands.setRootNode(document.body)
window.resetTimeouts()
atom.packages.packageStates = {}
@@ -97,6 +95,7 @@ beforeEach ->
config.set "editor.autoIndent", false
config.set "core.disabledPackages", ["package-that-throws-an-exception",
"package-with-broken-package-json", "package-with-broken-keymap"]
config.set "core.useReactEditor", true
config.save.reset()
atom.config = config
@@ -121,8 +120,6 @@ beforeEach ->
addCustomMatchers(this)
afterEach ->
atom.commands.clear()
atom.packages.deactivatePackages()
atom.menu.template = []
@@ -141,7 +138,8 @@ afterEach ->
jasmine.unspy(atom, 'saveSync')
ensureNoPathSubscriptions()
atom.syntax.clearObservers()
atom.syntax.off()
ensureNoDeprecatedFunctionsCalled() if isCoreSpec
waits(0) # yield to ui thread to make screen update more frequently
ensureNoPathSubscriptions = ->
@@ -213,13 +211,6 @@ addCustomMatchers = (spec) ->
element = element.get(0) if element.jquery
element.webkitMatchesSelector(":focus") or element.querySelector(":focus")
toShow: ->
notText = if @isNot then " not" else ""
element = @actual
element = element.get(0) if element.jquery
@message = -> return "Expected element '#{element}' or its descendants #{notText} to show."
element.style.display in ['block', 'inline-block', 'static', 'fixed']
window.keyIdentifierForKey = (key) ->
if key.length > 1 # named key
key
+26 -28
Ver Arquivo
@@ -1,32 +1,30 @@
textUtils = require '../src/text-utils'
describe 'text utilities', ->
describe '.hasPairedCharacter(string)', ->
it 'returns true when the string contains a surrogate pair or variation sequence', ->
expect(textUtils.hasPairedCharacter('abc')).toBe false
expect(textUtils.hasPairedCharacter('a\uD835\uDF97b\uD835\uDF97c')).toBe true
expect(textUtils.hasPairedCharacter('\uD835\uDF97')).toBe true
expect(textUtils.hasPairedCharacter('\u2714\uFE0E')).toBe true
expect(textUtils.hasPairedCharacter('\uD835')).toBe false
expect(textUtils.hasPairedCharacter('\uDF97')).toBe false
expect(textUtils.hasPairedCharacter('\uFE0E')).toBe false
expect(textUtils.hasPairedCharacter('\uFE0E\uFE0E')).toBe false
describe '.getCharacterCount(string)', ->
it 'returns the number of full characters in the string', ->
expect(textUtils.getCharacterCount('abc')).toBe 3
expect(textUtils.getCharacterCount('a\uD835\uDF97b\uD835\uDF97c')).toBe 5
expect(textUtils.getCharacterCount('\uD835\uDF97')).toBe 1
expect(textUtils.getCharacterCount('\uD835')).toBe 1
expect(textUtils.getCharacterCount('\uDF97')).toBe 1
describe '.isPairedCharacter(string, index)', ->
it 'returns true when the index is the start of a high/low surrogate pair or variation sequence', ->
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe true
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe false
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe false
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe true
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe false
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe false
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 0)).toBe false
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 1)).toBe true
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 2)).toBe false
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 3)).toBe false
expect(textUtils.isPairedCharacter('\uD835')).toBe false
expect(textUtils.isPairedCharacter('\uDF97')).toBe false
expect(textUtils.isPairedCharacter('\uFE0E')).toBe false
expect(textUtils.isPairedCharacter('\uFE0E')).toBe false
expect(textUtils.isPairedCharacter('\uFE0E\uFE0E')).toBe false
describe '.hasSurrogatePair(string)', ->
it 'returns true when the string contains a surrogate pair', ->
expect(textUtils.hasSurrogatePair('abc')).toBe false
expect(textUtils.hasSurrogatePair('a\uD835\uDF97b\uD835\uDF97c')).toBe true
expect(textUtils.hasSurrogatePair('\uD835\uDF97')).toBe true
expect(textUtils.hasSurrogatePair('\uD835')).toBe false
expect(textUtils.hasSurrogatePair('\uDF97')).toBe false
describe '.isSurrogatePair(string, index)', ->
it 'returns true when the index is the start of a high/low surrogate pair', ->
expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false
expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe true
expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe false
expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe false
expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe true
expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe false
expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe false
expect(textUtils.isSurrogatePair('\uD835')).toBe false
expect(textUtils.isSurrogatePair('\uDF97')).toBe false
+11 -84
Ver Arquivo
@@ -69,7 +69,7 @@ describe "ThemeManager", ->
describe "when the core.themes config value changes", ->
it "add/removes stylesheets to reflect the new config value", ->
themeManager.onDidReloadAll reloadHandler = jasmine.createSpy()
themeManager.on 'reloaded', reloadHandler = jasmine.createSpy()
spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null
waitsForPromise ->
@@ -131,8 +131,8 @@ describe "ThemeManager", ->
describe "requireStylesheet(path)", ->
it "synchronously loads css at the given path and installs a style tag for it in the head", ->
themeManager.onDidChangeStylesheets stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.onDidAddStylesheet stylesheetAddedHandler = jasmine.createSpy("stylesheetAddedHandler")
themeManager.on 'stylesheets-changed', stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.on 'stylesheet-added', stylesheetAddedHandler = jasmine.createSpy("stylesheetAddedHandler")
cssPath = atom.project.resolve('css.css')
lengthBefore = $('head style').length
@@ -193,8 +193,8 @@ describe "ThemeManager", ->
themeManager.requireStylesheet(cssPath)
expect($(document.body).css('font-weight')).toBe("bold")
themeManager.onDidRemoveStylesheet stylesheetRemovedHandler = jasmine.createSpy("stylesheetRemovedHandler")
themeManager.onDidChangeStylesheets stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.on 'stylesheet-removed', stylesheetRemovedHandler = jasmine.createSpy("stylesheetRemovedHandler")
themeManager.on 'stylesheets-changed', stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.removeStylesheet(cssPath)
@@ -217,7 +217,7 @@ describe "ThemeManager", ->
themeManager.activateThemes()
it "loads the correct values from the theme's ui-variables file", ->
themeManager.onDidReloadAll reloadHandler = jasmine.createSpy()
themeManager.on 'reloaded', reloadHandler = jasmine.createSpy()
atom.config.set('core.themes', ['theme-with-ui-variables'])
waitsFor ->
@@ -234,7 +234,7 @@ describe "ThemeManager", ->
describe "when there is a theme with incomplete variables", ->
it "loads the correct values from the fallback ui-variables", ->
themeManager.onDidReloadAll reloadHandler = jasmine.createSpy()
themeManager.on 'reloaded', reloadHandler = jasmine.createSpy()
atom.config.set('core.themes', ['theme-with-incomplete-ui-variables'])
waitsFor ->
@@ -247,21 +247,6 @@ describe "ThemeManager", ->
# from within the theme itself
expect($(".editor").css("background-color")).toBe "rgb(0, 152, 255)"
describe "theme classes on the workspace", ->
it 'adds theme-* classes to the workspace for each active theme', ->
expect(atom.workspaceView).toHaveClass 'theme-atom-dark-ui'
themeManager.onDidReloadAll reloadHandler = jasmine.createSpy()
atom.config.set('core.themes', ['theme-with-ui-variables'])
waitsFor ->
reloadHandler.callCount > 0
runs ->
# `theme-` twice as it prefixes the name with `theme-`
expect(atom.workspaceView).toHaveClass 'theme-theme-with-ui-variables'
expect(atom.workspaceView).not.toHaveClass 'theme-atom-dark-ui'
describe "when the user stylesheet changes", ->
it "reloads it", ->
[stylesheetRemovedHandler, stylesheetAddedHandler, stylesheetsChangedHandler] = []
@@ -273,9 +258,9 @@ describe "ThemeManager", ->
themeManager.activateThemes()
runs ->
themeManager.onDidChangeStylesheets stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.onDidRemoveStylesheet stylesheetRemovedHandler = jasmine.createSpy("stylesheetRemovedHandler")
themeManager.onDidAddStylesheet stylesheetAddedHandler = jasmine.createSpy("stylesheetAddedHandler")
themeManager.on 'stylesheets-changed', stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.on 'stylesheet-removed', stylesheetRemovedHandler = jasmine.createSpy("stylesheetRemovedHandler")
themeManager.on 'stylesheet-added', stylesheetAddedHandler = jasmine.createSpy("stylesheetAddedHandler")
spyOn(themeManager, 'loadUserStylesheet').andCallThrough()
expect($(document.body).css('border-style')).toBe 'dotted'
@@ -316,9 +301,7 @@ describe "ThemeManager", ->
themeManager.activateThemes()
runs ->
disposable = themeManager.onDidReloadAll ->
disposable.dispose()
reloaded = true
themeManager.once 'reloaded', -> reloaded = true
spyOn(console, 'warn')
expect(-> atom.config.set('core.themes', ['atom-light-ui', 'theme-really-does-not-exist'])).not.toThrow()
@@ -327,59 +310,3 @@ describe "ThemeManager", ->
runs ->
expect(console.warn.callCount).toBe 1
expect(console.warn.argsForCall[0][0].length).toBeGreaterThan 0
describe "when in safe mode", ->
beforeEach ->
themeManager = new ThemeManager({packageManager: atom.packages, resourcePath, configDirPath, safeMode: true})
describe 'when the enabled UI and syntax themes are bundled with Atom', ->
beforeEach ->
atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-syntax'])
waitsForPromise ->
themeManager.activateThemes()
it 'uses the enabled themes', ->
activeThemeNames = themeManager.getActiveNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-light-ui')
expect(activeThemeNames).toContain('atom-dark-syntax')
describe 'when the enabled UI and syntax themes are not bundled with Atom', ->
beforeEach ->
atom.config.set('core.themes', ['installed-dark-ui', 'installed-dark-syntax'])
waitsForPromise ->
themeManager.activateThemes()
it 'uses the default dark UI and syntax themes', ->
activeThemeNames = themeManager.getActiveNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-dark-ui')
expect(activeThemeNames).toContain('atom-dark-syntax')
describe 'when the enabled UI theme is not bundled with Atom', ->
beforeEach ->
atom.config.set('core.themes', ['installed-dark-ui', 'atom-light-syntax'])
waitsForPromise ->
themeManager.activateThemes()
it 'uses the default dark UI theme', ->
activeThemeNames = themeManager.getActiveNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-dark-ui')
expect(activeThemeNames).toContain('atom-light-syntax')
describe 'when the enabled syntax theme is not bundled with Atom', ->
beforeEach ->
atom.config.set('core.themes', ['atom-light-ui', 'installed-dark-syntax'])
waitsForPromise ->
themeManager.activateThemes()
it 'uses the default dark syntax theme', ->
activeThemeNames = themeManager.getActiveNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-light-ui')
expect(activeThemeNames).toContain('atom-dark-syntax')
+1 -1
Ver Arquivo
@@ -18,7 +18,7 @@ class TimeReporter extends jasmine.Reporter
log ?= (line) -> console.log(line)
log "Longest running suites:"
suites = _.map(window.timedSuites, (key, value) -> [value, key])
for suite in _.sortBy(suites, (suite) -> -suite[1])[0...number]
for suite in _.sortBy(suites, (suite) => -suite[1])[0...number]
time = Math.round(suite[1] / 100) / 10
log " #{suite[0]} (#{time}s)"
undefined
+156 -221
Ver Arquivo
@@ -1,5 +1,4 @@
TokenizedBuffer = require '../src/tokenized-buffer'
TextBuffer = require 'text-buffer'
_ = require 'underscore-plus'
describe "TokenizedBuffer", ->
@@ -13,9 +12,6 @@ describe "TokenizedBuffer", ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
afterEach ->
tokenizedBuffer?.destroy()
startTokenizing = (tokenizedBuffer) ->
tokenizedBuffer.setVisible(true)
@@ -41,7 +37,7 @@ describe "TokenizedBuffer", ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
startTokenizing(tokenizedBuffer)
tokenizedBuffer.onDidChange changeHandler = jasmine.createSpy('changeHandler')
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
afterEach ->
tokenizedBuffer.destroy()
@@ -49,38 +45,38 @@ describe "TokenizedBuffer", ->
describe "on construction", ->
it "initially creates un-tokenized screen lines, then tokenizes lines chunk at a time in the background", ->
line0 = tokenizedBuffer.tokenizedLineForRow(0)
line0 = tokenizedBuffer.lineForScreenRow(0)
expect(line0.tokens.length).toBe 1
expect(line0.tokens[0]).toEqual(value: line0.text, scopes: ['source.js'])
line11 = tokenizedBuffer.tokenizedLineForRow(11)
line11 = tokenizedBuffer.lineForScreenRow(11)
expect(line11.tokens.length).toBe 2
expect(line11.tokens[0]).toEqual(value: " ", scopes: ['source.js'], isAtomic: true)
expect(line11.tokens[1]).toEqual(value: "return sort(Array.apply(this, arguments));", scopes: ['source.js'])
# background tokenization has not begun
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack).toBeUndefined()
expect(tokenizedBuffer.lineForScreenRow(0).ruleStack).toBeUndefined()
# tokenize chunk 1
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.lineForScreenRow(0).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(4).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).ruleStack?).toBeFalsy()
expect(changeHandler).toHaveBeenCalledWith(start: 0, end: 4, delta: 0)
changeHandler.reset()
# tokenize chunk 2
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(9).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.lineForScreenRow(5).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(9).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(10).ruleStack?).toBeFalsy()
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 9, delta: 0)
changeHandler.reset()
# tokenize last chunk
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(12).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(10).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(12).ruleStack?).toBeTruthy()
expect(changeHandler).toHaveBeenCalledWith(start: 10, end: 12, delta: 0)
describe "when the buffer is partially tokenized", ->
@@ -134,8 +130,8 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.firstInvalidRow()).toBe 5
buffer.setTextInRange([[6, 0], [7, 0]], "\n\n\n")
expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.tokenizedLineForRow(7).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.lineForScreenRow(6).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.lineForScreenRow(7).ruleStack?).toBeFalsy()
changeHandler.reset()
expect(tokenizedBuffer.firstInvalidRow()).toBe 5
@@ -149,10 +145,10 @@ describe "TokenizedBuffer", ->
it "updates tokens to reflect the change", ->
buffer.setTextInRange([[0, 0], [2, 0]], "foo()\n7\n")
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.brace.round.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.js'])
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.brace.round.js'])
expect(tokenizedBuffer.lineForScreenRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.js'])
# line 2 is unchanged
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -164,7 +160,7 @@ describe "TokenizedBuffer", ->
buffer.insert([5, 30], '/* */')
changeHandler.reset()
buffer.insert([2, 0], '/*')
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js']
expect(tokenizedBuffer.lineForScreenRow(3).tokens[0].scopes).toEqual ['source.js']
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
@@ -172,9 +168,9 @@ describe "TokenizedBuffer", ->
changeHandler.reset()
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
@@ -185,23 +181,23 @@ describe "TokenizedBuffer", ->
buffer.insert([5, 0], '*/')
buffer.insert([1, 0], 'var ')
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(1).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
describe "when lines are both updated and removed", ->
it "updates tokens to reflect the change", ->
buffer.setTextInRange([[1, 0], [3, 0]], "foo()")
# previous line 0 remains
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.modifier.js'])
expect(tokenizedBuffer.lineForScreenRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.modifier.js'])
# previous line 3 should be combined with input to form line 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js'])
expect(tokenizedBuffer.lineForScreenRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
# lines below deleted regions should be shifted upward
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.lineForScreenRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -214,8 +210,8 @@ describe "TokenizedBuffer", ->
changeHandler.reset()
buffer.setTextInRange([[2, 0], [3, 0]], '/*')
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js']
expect(tokenizedBuffer.lineForScreenRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.lineForScreenRow(3).tokens[0].scopes).toEqual ['source.js']
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
@@ -223,8 +219,8 @@ describe "TokenizedBuffer", ->
changeHandler.reset()
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
@@ -235,19 +231,19 @@ describe "TokenizedBuffer", ->
buffer.setTextInRange([[1, 0], [2, 0]], "foo()\nbar()\nbaz()\nquux()")
# previous line 0 remains
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.modifier.js'])
expect(tokenizedBuffer.lineForScreenRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.modifier.js'])
# 3 new lines inserted
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0]).toEqual(value: 'bar', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0]).toEqual(value: 'baz', scopes: ['source.js'])
expect(tokenizedBuffer.lineForScreenRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js'])
expect(tokenizedBuffer.lineForScreenRow(2).tokens[0]).toEqual(value: 'bar', scopes: ['source.js'])
expect(tokenizedBuffer.lineForScreenRow(3).tokens[0]).toEqual(value: 'baz', scopes: ['source.js'])
# previous line 2 is joined with quux() on line 4
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0]).toEqual(value: 'quux', scopes: ['source.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.lineForScreenRow(4).tokens[0]).toEqual(value: 'quux', scopes: ['source.js'])
expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
# previous line 3 is pushed down to become line 5
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -264,17 +260,17 @@ describe "TokenizedBuffer", ->
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 2, end: 2, delta: 2)
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js']
expect(tokenizedBuffer.lineForScreenRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.lineForScreenRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].scopes).toEqual ['source.js']
changeHandler.reset()
advanceClock() # tokenize invalidated lines in background
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(6).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(7).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(8).tokens[0].scopes).not.toBe ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(6).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(7).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.lineForScreenRow(8).tokens[0].scopes).not.toBe ['source.js', 'comment.block.js']
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -285,13 +281,13 @@ describe "TokenizedBuffer", ->
it "tokenizes the initial chunk synchronously, then tokenizes the remaining lines in the background", ->
commentBlock = _.multiplyString("// a comment\n", tokenizedBuffer.chunkSize + 2)
buffer.insert([0,0], commentBlock)
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.lineForScreenRow(0).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(4).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).ruleStack?).toBeFalsy()
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(6).ruleStack?).toBeTruthy()
describe ".findOpeningBracket(closingBufferPosition)", ->
it "returns the position of the matching bracket, skipping any nested brackets", ->
@@ -302,18 +298,18 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.findClosingBracket([1, 29])).toEqual [9, 2]
it "tokenizes leading whitespace based on the new tab length", ->
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].isAtomic).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].value).toBe " "
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].isAtomic).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].value).toBe " "
tokenizedBuffer.setTabLength(4)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].isAtomic).toBeFalsy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].value).toBe " current "
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].isAtomic).toBeFalsy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].value).toBe " current "
describe "when the buffer contains hard-tabs", ->
beforeEach ->
@@ -335,7 +331,7 @@ describe "TokenizedBuffer", ->
it "renders each tab as its own atomic token with a value of size tabLength", ->
tabAsSpaces = _.multiplyString(' ', tokenizedBuffer.getTabLength())
screenLine0 = tokenizedBuffer.tokenizedLineForRow(0)
screenLine0 = tokenizedBuffer.lineForScreenRow(0)
expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
{ tokens } = screenLine0
@@ -347,7 +343,7 @@ describe "TokenizedBuffer", ->
expect(tokens[2].isAtomic).toBeTruthy()
expect(tokens[3].value).toBe ""
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
it "aligns the hard tabs to the correct tab stop column", ->
buffer.setText """
@@ -359,62 +355,62 @@ describe "TokenizedBuffer", ->
tokenizedBuffer.setTabLength(4)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 3
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 1
tokenizedBuffer.setTabLength(3)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 3
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 3
tokenizedBuffer.setTabLength(2)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 1
tokenizedBuffer.setTabLength(1)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 1
describe "when the buffer contains UTF-8 surrogate pairs", ->
beforeEach ->
@@ -435,7 +431,7 @@ describe "TokenizedBuffer", ->
buffer.release()
it "renders each UTF-8 surrogate pair as its own atomic token", ->
screenLine0 = tokenizedBuffer.tokenizedLineForRow(0)
screenLine0 = tokenizedBuffer.lineForScreenRow(0)
expect(screenLine0.text).toBe "'abc\uD835\uDF97def'"
{ tokens } = screenLine0
@@ -447,7 +443,7 @@ describe "TokenizedBuffer", ->
expect(tokens[3].value).toBe "def"
expect(tokens[4].value).toBe "'"
screenLine1 = tokenizedBuffer.tokenizedLineForRow(1)
screenLine1 = tokenizedBuffer.lineForScreenRow(1)
expect(screenLine1.text).toBe "//\uD835\uDF97xyz"
{ tokens } = screenLine1
@@ -468,7 +464,7 @@ describe "TokenizedBuffer", ->
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
tokenizedBuffer.onDidTokenize tokenizedHandler
tokenizedBuffer.on 'tokenized', tokenizedHandler
fullyTokenize(tokenizedBuffer)
expect(tokenizedHandler.callCount).toBe(1)
@@ -483,7 +479,7 @@ describe "TokenizedBuffer", ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
fullyTokenize(tokenizedBuffer)
tokenizedBuffer.onDidTokenize tokenizedHandler
tokenizedBuffer.on 'tokenized', tokenizedHandler
editor.getBuffer().insert([0, 0], "'")
fullyTokenize(tokenizedBuffer)
expect(tokenizedHandler).not.toHaveBeenCalled()
@@ -499,7 +495,7 @@ describe "TokenizedBuffer", ->
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
tokenizedBuffer.onDidTokenize tokenizedHandler
tokenizedBuffer.on 'tokenized', tokenizedHandler
fullyTokenize(tokenizedBuffer)
tokenizedHandler.reset()
@@ -525,7 +521,7 @@ describe "TokenizedBuffer", ->
tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb'))
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.tokenizedLineForRow(0)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
waitsForPromise ->
@@ -533,7 +529,7 @@ describe "TokenizedBuffer", ->
runs ->
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.tokenizedLineForRow(0)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
describe ".tokenForPosition(position)", ->
@@ -588,112 +584,51 @@ describe "TokenizedBuffer", ->
atom.config.set('editor.tabLength', 0)
expect(tokenizedBuffer.tokenForPosition([0,0]).value).toBe ' '
describe "when the invisibles value changes", ->
beforeEach ->
it "updates the tokens with the appropriate invisible characters", ->
buffer = new TextBuffer(text: " \t a line with tabs\tand \tspaces \t ")
tokenizedBuffer = new TokenizedBuffer({buffer})
fullyTokenize(tokenizedBuffer)
tokenizedBuffer.setInvisibles(space: 'S', tab: 'T')
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "SST Sa line with tabsTand T spacesSTS"
# Also needs to work for copies
expect(tokenizedBuffer.tokenizedLineForRow(0).copy().text).toBe "SST Sa line with tabsTand T spacesSTS"
it "assigns endOfLineInvisibles to tokenized lines", ->
buffer = new TextBuffer(text: "a line that ends in a carriage-return-line-feed \r\na line that ends in just a line-feed\na line with no ending")
tokenizedBuffer = new TokenizedBuffer({buffer})
atom.config.set('editor.showInvisibles', true)
tokenizedBuffer.setInvisibles(cr: 'R', eol: 'N')
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R', 'N']
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual ['N']
# Lines ending in soft wraps get no invisibles
[left, right] = tokenizedBuffer.tokenizedLineForRow(0).softWrapAt(20)
expect(left.endOfLineInvisibles).toBe null
expect(right.endOfLineInvisibles).toEqual ['R', 'N']
tokenizedBuffer.setInvisibles(cr: 'R', eol: false)
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R']
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual []
describe "leading and trailing whitespace", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
fullyTokenize(tokenizedBuffer)
it "assigns ::firstNonWhitespaceIndex on tokens that have leading whitespace", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0].firstNonWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].firstNonWhitespaceIndex).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].firstNonWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].firstNonWhitespaceIndex).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].firstNonWhitespaceIndex).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2].firstNonWhitespaceIndex).toBe null
it "sets ::hasLeadingWhitespace to true on tokens that have leading whitespace", ->
expect(tokenizedBuffer.lineForScreenRow(0).tokens[0].hasLeadingWhitespace).toBe false
expect(tokenizedBuffer.lineForScreenRow(1).tokens[0].hasLeadingWhitespace).toBe true
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].hasLeadingWhitespace).toBe false
expect(tokenizedBuffer.lineForScreenRow(2).tokens[0].hasLeadingWhitespace).toBe true
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].hasLeadingWhitespace).toBe true
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2].hasLeadingWhitespace).toBe false
# The 4th token *has* leading whitespace, but isn't entirely whitespace
buffer.insert([5, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3].firstNonWhitespaceIndex).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4].firstNonWhitespaceIndex).toBe null
expect(tokenizedBuffer.lineForScreenRow(5).tokens[3].hasLeadingWhitespace).toBe true
expect(tokenizedBuffer.lineForScreenRow(5).tokens[4].hasLeadingWhitespace).toBe false
# Lines that are *only* whitespace are not considered to have leading whitespace
buffer.insert([10, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(10).tokens[0].firstNonWhitespaceIndex).toBe null
expect(tokenizedBuffer.lineForScreenRow(10).tokens[0].hasLeadingWhitespace).toBe false
it "assigns ::firstTrailingWhitespaceIndex on tokens that have trailing whitespace", ->
it "sets ::hasTrailingWhitespace to true on tokens that have trailing whitespace", ->
buffer.insert([0, Infinity], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[11].firstTrailingWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[12].firstTrailingWhitespaceIndex).toBe 0
expect(tokenizedBuffer.lineForScreenRow(0).tokens[11].hasTrailingWhitespace).toBe false
expect(tokenizedBuffer.lineForScreenRow(0).tokens[12].hasTrailingWhitespace).toBe true
# The last token *has* trailing whitespace, but isn't entirely whitespace
buffer.setTextInRange([[2, 39], [2, 40]], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[14].firstTrailingWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[15].firstTrailingWhitespaceIndex).toBe 6
expect(tokenizedBuffer.lineForScreenRow(2).tokens[14].hasTrailingWhitespace).toBe false
expect(tokenizedBuffer.lineForScreenRow(2).tokens[15].hasTrailingWhitespace).toBe true
# Lines that are *only* whitespace are considered to have trailing whitespace
buffer.insert([10, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(10).tokens[0].firstTrailingWhitespaceIndex).toBe 0
expect(tokenizedBuffer.lineForScreenRow(10).tokens[0].hasTrailingWhitespace).toBe true
it "only marks trailing whitespace on the last segment of a soft-wrapped line", ->
buffer.insert([0, Infinity], ' ')
tokenizedLine = tokenizedBuffer.tokenizedLineForRow(0)
tokenizedLine = tokenizedBuffer.lineForScreenRow(0)
[segment1, segment2] = tokenizedLine.softWrapAt(16)
expect(segment1.tokens[5].value).toBe ' '
expect(segment1.tokens[5].firstTrailingWhitespaceIndex).toBe null
expect(segment1.tokens[5].hasTrailingWhitespace).toBe false
expect(segment2.tokens[6].value).toBe ' '
expect(segment2.tokens[6].firstTrailingWhitespaceIndex).toBe 0
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')
fullyTokenize(tokenizedBuffer)
line = tokenizedBuffer.tokenizedLineForRow(0).copy()
expect(line.tokens[0].firstNonWhitespaceIndex).toBe 2
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')
buffer.setText(" token ")
fullyTokenize(tokenizedBuffer)
token = tokenizedBuffer.tokenizedLines[0].tokens[0]
[leftToken, rightToken] = token.splitAt(1)
expect(leftToken.hasInvisibleCharacters).toBe true
expect(leftToken.firstNonWhitespaceIndex).toBe 1
expect(leftToken.firstTrailingWhitespaceIndex).toBe null
expect(leftToken.hasInvisibleCharacters).toBe true
expect(rightToken.firstNonWhitespaceIndex).toBe null
expect(rightToken.firstTrailingWhitespaceIndex).toBe 5
expect(segment2.tokens[6].hasTrailingWhitespace).toBe true
describe "indent level", ->
beforeEach ->
@@ -703,84 +638,84 @@ describe "TokenizedBuffer", ->
describe "when the line is non-empty", ->
it "has an indent level based on the leading whitespace on the line", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).indentLevel).toBe 0
expect(tokenizedBuffer.tokenizedLineForRow(1).indentLevel).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(0).indentLevel).toBe 0
expect(tokenizedBuffer.lineForScreenRow(1).indentLevel).toBe 1
expect(tokenizedBuffer.lineForScreenRow(2).indentLevel).toBe 2
buffer.insert([2, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2.5
expect(tokenizedBuffer.lineForScreenRow(2).indentLevel).toBe 2.5
describe "when the line is empty", ->
it "assumes the indentation level of the first non-empty line below or above if one exists", ->
buffer.insert([12, 0], ' ')
buffer.insert([12, Infinity], '\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(14).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(14).indentLevel).toBe 2
buffer.insert([1, Infinity], '\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(3).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(2).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(3).indentLevel).toBe 2
buffer.setText('\n\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(1).indentLevel).toBe 0
expect(tokenizedBuffer.lineForScreenRow(1).indentLevel).toBe 0
describe "when the changed lines are surrounded by whitespace-only lines", ->
it "updates the indentLevel of empty lines that precede the change", ->
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 0
expect(tokenizedBuffer.lineForScreenRow(12).indentLevel).toBe 0
buffer.insert([12, 0], '\n')
buffer.insert([13, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 1
expect(tokenizedBuffer.lineForScreenRow(12).indentLevel).toBe 1
it "updates empty line indent guides when the empty line is the last line", ->
buffer.insert([12, 2], '\n')
# The newline and he tab need to be in two different operations to surface the bug
buffer.insert([12, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 1
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 1
buffer.insert([12, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(14)).not.toBeDefined()
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(14)).not.toBeDefined()
it "updates the indentLevel of empty lines surrounding a change that inserts lines", ->
# create some new lines
buffer.insert([7, 0], '\n\n')
buffer.insert([5, 0], '\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(11).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(5).indentLevel).toBe 3
expect(tokenizedBuffer.lineForScreenRow(6).indentLevel).toBe 3
expect(tokenizedBuffer.lineForScreenRow(9).indentLevel).toBe 3
expect(tokenizedBuffer.lineForScreenRow(10).indentLevel).toBe 3
expect(tokenizedBuffer.lineForScreenRow(11).indentLevel).toBe 2
tokenizedBuffer.onDidChange changeHandler = jasmine.createSpy('changeHandler')
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
buffer.setTextInRange([[7, 0], [8, 65]], ' one\n two\n three\n four')
delete changeHandler.argsForCall[0][0].bufferChange
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: 2)
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(11).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(5).indentLevel).toBe 4
expect(tokenizedBuffer.lineForScreenRow(6).indentLevel).toBe 4
expect(tokenizedBuffer.lineForScreenRow(11).indentLevel).toBe 4
expect(tokenizedBuffer.lineForScreenRow(12).indentLevel).toBe 4
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 2
it "updates the indentLevel of empty lines surrounding a change that removes lines", ->
# create some new lines
buffer.insert([7, 0], '\n\n')
buffer.insert([5, 0], '\n\n')
tokenizedBuffer.onDidChange changeHandler = jasmine.createSpy('changeHandler')
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
buffer.setTextInRange([[7, 0], [8, 65]], ' ok')
delete changeHandler.argsForCall[0][0].bufferChange
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1)
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(7).indentLevel).toBe 2 # new text
expect(tokenizedBuffer.tokenizedLineForRow(8).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # }
expect(tokenizedBuffer.lineForScreenRow(5).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(6).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(7).indentLevel).toBe 2 # new text
expect(tokenizedBuffer.lineForScreenRow(8).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(9).indentLevel).toBe 2
expect(tokenizedBuffer.lineForScreenRow(10).indentLevel).toBe 2 # }
+7 -7
Ver Arquivo
@@ -10,13 +10,13 @@ describe "TokenizedLine", ->
atom.project.open('coffee.coffee').then (o) -> editor = o
it "returns true when the line is only whitespace", ->
expect(editor.tokenizedLineForScreenRow(3).isOnlyWhitespace()).toBe true
expect(editor.tokenizedLineForScreenRow(7).isOnlyWhitespace()).toBe true
expect(editor.tokenizedLineForScreenRow(23).isOnlyWhitespace()).toBe true
expect(editor.lineForScreenRow(3).isOnlyWhitespace()).toBe true
expect(editor.lineForScreenRow(7).isOnlyWhitespace()).toBe true
expect(editor.lineForScreenRow(23).isOnlyWhitespace()).toBe true
it "returns false when the line is not only whitespace", ->
expect(editor.tokenizedLineForScreenRow(0).isOnlyWhitespace()).toBe false
expect(editor.tokenizedLineForScreenRow(2).isOnlyWhitespace()).toBe false
expect(editor.lineForScreenRow(0).isOnlyWhitespace()).toBe false
expect(editor.lineForScreenRow(2).isOnlyWhitespace()).toBe false
describe "::getScopeTree()", ->
it "returns a tree whose inner nodes are scopes and whose leaf nodes are tokens in those scopes", ->
@@ -35,6 +35,6 @@ describe "TokenizedLine", ->
runs ->
tokenIndex = 0
tokens = editor.tokenizedLineForScreenRow(1).tokens
scopeTree = editor.tokenizedLineForScreenRow(1).getScopeTree()
tokens = editor.lineForScreenRow(1).tokens
scopeTree = editor.lineForScreenRow(1).getScopeTree()
ensureValidScopeTree(scopeTree)
+1 -34
Ver Arquivo
@@ -113,7 +113,6 @@ describe "Window", ->
expect(atom.state.project).toEqual projectState
expect(atom.saveSync).toHaveBeenCalled()
describe ".removeEditorWindow()", ->
it "unsubscribes from all buffers", ->
waitsForPromise ->
atom.workspace.open("sample.js")
@@ -124,7 +123,7 @@ describe "Window", ->
pane.splitRight(pane.copyActiveItem())
expect(atom.workspaceView.find('.editor').length).toBe 2
atom.removeEditorWindow()
atom.unloadEditorWindow()
expect(buffer.getSubscriptionCount()).toBe 0
@@ -256,35 +255,3 @@ describe "Window", ->
elements.trigger "core:focus-previous"
expect(elements.find("[tabindex=1]:focus")).toExist()
describe "the window:open-path event", ->
beforeEach ->
spyOn(atom.workspace, 'open')
describe "when the project does not have a path", ->
beforeEach ->
atom.project.setPath()
describe "when the opened path exists", ->
it "sets the project path to the opened path", ->
$(window).trigger('window:open-path', [{pathToOpen: __filename}])
expect(atom.project.getPath()).toBe __dirname
describe "when the opened path does not exist but its parent directory does", ->
it "sets the project path to the opened path's parent directory", ->
$(window).trigger('window:open-path', [{pathToOpen: path.join(__dirname, 'this-path-does-not-exist.txt')}])
expect(atom.project.getPath()).toBe __dirname
describe "when the opened path is a file", ->
it "opens it in the workspace", ->
$(window).trigger('window:open-path', [{pathToOpen: __filename}])
expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
describe "when the opened path is a directory", ->
it "does not open it in the workspace", ->
$(window).trigger('window:open-path', [{pathToOpen: __dirname}])
expect(atom.workspace.open.callCount).toBe 0
+61 -95
Ver Arquivo
@@ -8,12 +8,8 @@ describe "Workspace", ->
atom.workspace = workspace = new Workspace
describe "::open(uri, options)", ->
openEvents = null
beforeEach ->
openEvents = []
workspace.onDidOpen (event) -> openEvents.push(event)
spyOn(workspace.getActivePane(), 'activate').andCallThrough()
spyOn(workspace.activePane, 'activate').andCallThrough()
describe "when the 'searchAllPanes' option is false (default)", ->
describe "when called without a uri", ->
@@ -25,21 +21,18 @@ describe "Workspace", ->
runs ->
expect(editor1.getPath()).toBeUndefined()
expect(workspace.getActivePane().items).toEqual [editor1]
expect(workspace.getActivePaneItem()).toBe editor1
expect(workspace.getActivePane().activate).toHaveBeenCalled()
expect(openEvents).toEqual [{uri: undefined, pane: workspace.getActivePane(), item: editor1, index: 0}]
openEvents = []
expect(workspace.activePane.items).toEqual [editor1]
expect(workspace.activePaneItem).toBe editor1
expect(workspace.activePane.activate).toHaveBeenCalled()
waitsForPromise ->
workspace.open().then (editor) -> editor2 = editor
runs ->
expect(editor2.getPath()).toBeUndefined()
expect(workspace.getActivePane().items).toEqual [editor1, editor2]
expect(workspace.getActivePaneItem()).toBe editor2
expect(workspace.getActivePane().activate).toHaveBeenCalled()
expect(openEvents).toEqual [{uri: undefined, pane: workspace.getActivePane(), item: editor2, index: 1}]
expect(workspace.activePane.items).toEqual [editor1, editor2]
expect(workspace.activePaneItem).toBe editor2
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when called with a uri", ->
describe "when the active pane already has an editor for the given uri", ->
@@ -58,29 +51,8 @@ describe "Workspace", ->
runs ->
expect(editor).toBe editor1
expect(workspace.getActivePaneItem()).toBe editor
expect(workspace.getActivePane().activate).toHaveBeenCalled()
expect(openEvents).toEqual [
{
uri: atom.project.resolve('a')
item: editor1
pane: atom.workspace.getActivePane()
index: 0
}
{
uri: atom.project.resolve('b')
item: editor2
pane: atom.workspace.getActivePane()
index: 1
}
{
uri: atom.project.resolve('a')
item: editor1
pane: atom.workspace.getActivePane()
index: 0
}
]
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the active pane does not have an editor for the given uri", ->
it "adds and activates a new editor for the given path on the active pane", ->
@@ -90,9 +62,9 @@ describe "Workspace", ->
runs ->
expect(editor.getUri()).toBe atom.project.resolve('a')
expect(workspace.getActivePaneItem()).toBe editor
expect(workspace.getActivePane().items).toEqual [editor]
expect(workspace.getActivePane().activate).toHaveBeenCalled()
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.items).toEqual [editor]
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the 'searchAllPanes' option is true", ->
describe "when an editor for the given uri is already open on an inactive pane", ->
@@ -111,14 +83,14 @@ describe "Workspace", ->
workspace.open('b').then (o) -> editor2 = o
runs ->
expect(workspace.getActivePaneItem()).toBe editor2
expect(workspace.activePaneItem).toBe editor2
waitsForPromise ->
workspace.open('a', searchAllPanes: true)
runs ->
expect(workspace.getActivePane()).toBe pane1
expect(workspace.getActivePaneItem()).toBe editor1
expect(workspace.activePane).toBe pane1
expect(workspace.activePaneItem).toBe editor1
describe "when no editor for the given uri is open in any pane", ->
it "opens an editor for the given uri in the active pane", ->
@@ -127,21 +99,21 @@ describe "Workspace", ->
workspace.open('a', searchAllPanes: true).then (o) -> editor = o
runs ->
expect(workspace.getActivePaneItem()).toBe editor
expect(workspace.activePaneItem).toBe editor
describe "when the 'split' option is set", ->
describe "when the 'split' option is 'left'", ->
it "opens the editor in the leftmost pane of the current pane axis", ->
pane1 = workspace.getActivePane()
pane1 = workspace.activePane
pane2 = pane1.splitRight()
expect(workspace.getActivePane()).toBe pane2
expect(workspace.activePane).toBe pane2
editor = null
waitsForPromise ->
workspace.open('a', split: 'left').then (o) -> editor = o
runs ->
expect(workspace.getActivePane()).toBe pane1
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor]
expect(pane2.items).toEqual []
@@ -151,37 +123,37 @@ describe "Workspace", ->
workspace.open('a', split: 'left').then (o) -> editor = o
runs ->
expect(workspace.getActivePane()).toBe pane1
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor]
expect(pane2.items).toEqual []
describe "when a pane axis is the leftmost sibling of the current pane", ->
it "opens the new item in the current pane", ->
editor = null
pane1 = workspace.getActivePane()
pane1 = workspace.activePane
pane2 = pane1.splitLeft()
pane3 = pane2.splitDown()
pane1.activate()
expect(workspace.getActivePane()).toBe pane1
expect(workspace.activePane).toBe pane1
waitsForPromise ->
workspace.open('a', split: 'left').then (o) -> editor = o
runs ->
expect(workspace.getActivePane()).toBe pane1
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor]
describe "when the 'split' option is 'right'", ->
it "opens the editor in the rightmost pane of the current pane axis", ->
editor = null
pane1 = workspace.getActivePane()
pane1 = workspace.activePane
pane2 = null
waitsForPromise ->
workspace.open('a', split: 'right').then (o) -> editor = o
runs ->
pane2 = workspace.getPanes().filter((p) -> p != pane1)[0]
expect(workspace.getActivePane()).toBe pane2
expect(workspace.activePane).toBe pane2
expect(pane1.items).toEqual []
expect(pane2.items).toEqual [editor]
@@ -191,18 +163,18 @@ describe "Workspace", ->
workspace.open('a', split: 'right').then (o) -> editor = o
runs ->
expect(workspace.getActivePane()).toBe pane2
expect(workspace.activePane).toBe pane2
expect(pane1.items).toEqual []
expect(pane2.items).toEqual [editor]
describe "when a pane axis is the rightmost sibling of the current pane", ->
it "opens the new item in a new pane split to the right of the current pane", ->
editor = null
pane1 = workspace.getActivePane()
pane1 = workspace.activePane
pane2 = pane1.splitRight()
pane3 = pane2.splitDown()
pane1.activate()
expect(workspace.getActivePane()).toBe pane1
expect(workspace.activePane).toBe pane1
pane4 = null
waitsForPromise ->
@@ -210,7 +182,7 @@ describe "Workspace", ->
runs ->
pane4 = workspace.getPanes().filter((p) -> p != pane1)[0]
expect(workspace.getActivePane()).toBe pane4
expect(workspace.activePane).toBe pane4
expect(pane4.items).toEqual [editor]
expect(workspace.paneContainer.root.children[0]).toBe pane1
expect(workspace.paneContainer.root.children[1]).toBe pane4
@@ -231,21 +203,21 @@ describe "Workspace", ->
workspace.open("bar://baz").then (item) ->
expect(item).toEqual { bar: "bar://baz" }
it "notifies ::onDidAddTextEditor observers", ->
it "emits an 'editor-created' event", ->
absolutePath = require.resolve('./fixtures/dir/a')
newEditorHandler = jasmine.createSpy('newEditorHandler')
workspace.onDidAddTextEditor newEditorHandler
workspace.on 'editor-created', newEditorHandler
editor = null
waitsForPromise ->
workspace.open(absolutePath).then (e) -> editor = e
runs ->
expect(newEditorHandler.argsForCall[0][0].textEditor).toBe editor
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "::reopenItem()", ->
it "opens the uri associated with the last closed pane that isn't currently open", ->
pane = workspace.getActivePane()
pane = workspace.activePane
waitsForPromise ->
workspace.open('a').then ->
workspace.open('b').then ->
@@ -254,44 +226,44 @@ describe "Workspace", ->
runs ->
# does not reopen items with no uri
expect(workspace.getActivePaneItem().getUri()).toBeUndefined()
expect(workspace.activePaneItem.getUri()).toBeUndefined()
pane.destroyActiveItem()
waitsForPromise ->
workspace.reopenItem()
runs ->
expect(workspace.getActivePaneItem().getUri()).not.toBeUndefined()
expect(workspace.activePaneItem.getUri()).not.toBeUndefined()
# destroy all items
expect(workspace.getActivePaneItem().getUri()).toBe atom.project.resolve('file1')
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('file1')
pane.destroyActiveItem()
expect(workspace.getActivePaneItem().getUri()).toBe atom.project.resolve('b')
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('b')
pane.destroyActiveItem()
expect(workspace.getActivePaneItem().getUri()).toBe atom.project.resolve('a')
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('a')
pane.destroyActiveItem()
# reopens items with uris
expect(workspace.getActivePaneItem()).toBeUndefined()
expect(workspace.activePaneItem).toBeUndefined()
waitsForPromise ->
workspace.reopenItem()
runs ->
expect(workspace.getActivePaneItem().getUri()).toBe atom.project.resolve('a')
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('a')
# does not reopen items that are already open
waitsForPromise ->
workspace.open('b')
runs ->
expect(workspace.getActivePaneItem().getUri()).toBe atom.project.resolve('b')
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('b')
waitsForPromise ->
workspace.reopenItem()
runs ->
expect(workspace.getActivePaneItem().getUri()).toBe atom.project.resolve('file1')
expect(workspace.activePaneItem.getUri()).toBe atom.project.resolve('file1')
describe "::increase/decreaseFontSize()", ->
it "increases/decreases the font size without going below 1", ->
@@ -310,22 +282,7 @@ describe "Workspace", ->
describe "::openLicense()", ->
it "opens the license as plain-text in a buffer", ->
waitsForPromise -> workspace.openLicense()
runs -> expect(workspace.getActivePaneItem().getText()).toMatch /Copyright/
describe "::observeTextEditors()", ->
it "invokes the observer with current and future text editors", ->
observed = []
waitsForPromise -> workspace.open()
waitsForPromise -> workspace.open()
waitsForPromise -> workspace.openLicense()
runs ->
workspace.observeTextEditors (editor) -> observed.push(editor)
waitsForPromise -> workspace.open()
expect(observed).toEqual workspace.getTextEditors()
runs -> expect(workspace.activePaneItem.getText()).toMatch /Copyright/
describe "when an editor is destroyed", ->
it "removes the editor", ->
@@ -335,9 +292,23 @@ describe "Workspace", ->
workspace.open("a").then (e) -> editor = e
runs ->
expect(workspace.getTextEditors()).toHaveLength 1
expect(workspace.getEditors()).toHaveLength 1
editor.destroy()
expect(workspace.getTextEditors()).toHaveLength 0
expect(workspace.getEditors()).toHaveLength 0
describe "when an editor is copied", ->
it "emits an 'editor-created' event", ->
editor = null
handler = jasmine.createSpy('editorCreatedHandler')
workspace.on 'editor-created', handler
waitsForPromise ->
workspace.open("a").then (o) -> editor = o
runs ->
expect(handler.callCount).toBe 1
editorCopy = editor.copy()
expect(handler.callCount).toBe 2
it "stores the active grammars used by all the open editors", ->
waitsForPromise ->
@@ -346,19 +317,14 @@ describe "Workspace", ->
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
waitsForPromise ->
atom.packages.activatePackage('language-todo')
waitsForPromise ->
atom.workspace.open('sample.coffee')
runs ->
atom.workspace.getActiveEditor().setText """
i = /test/; #FIXME
"""
atom.workspace.getActiveEditor().setText('i = /test/;')
state = atom.workspace.serialize()
expect(state.packagesWithActiveGrammars).toEqual ['language-coffee-script', 'language-javascript', 'language-todo']
expect(state.packagesWithActiveGrammars).toEqual ['language-coffee-script', 'language-javascript']
jsPackage = atom.packages.getLoadedPackage('language-javascript')
coffeePackage = atom.packages.getLoadedPackage('language-coffee-script')
+11 -13
Ver Arquivo
@@ -42,7 +42,7 @@ describe "WorkspaceView", ->
runs ->
editorView1 = atom.workspaceView.getActiveView()
buffer = editorView1.getEditor().getBuffer()
editorView1.getPaneView().getModel().splitRight(copyActiveItem: true)
editorView1.splitRight()
expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
simulateReload()
@@ -195,29 +195,27 @@ describe "WorkspaceView", ->
atom.workspaceView.height(200)
atom.workspaceView.attachToDom()
rightEditorView = atom.workspaceView.getActiveView()
rightEditorView.getEditor().setText("\t \n")
rightEditorView.getPaneView().getModel().splitLeft(copyActiveItem: true)
leftEditorView = atom.workspaceView.getActiveView()
rightEditorView.getEditor().setText("\t ")
leftEditorView = rightEditorView.splitLeft()
expect(rightEditorView.find(".line:first").text()).toBe " "
expect(leftEditorView.find(".line:first").text()).toBe " "
{space, tab, eol} = atom.config.get('editor.invisibles')
{invisibles} = rightEditorView.component.state
{space, tab, eol} = invisibles
withInvisiblesShowing = "#{tab} #{space}#{space}#{eol}"
atom.workspaceView.trigger "window:toggle-invisibles"
expect(rightEditorView.find(".line:first").text()).toBe withInvisiblesShowing
expect(leftEditorView.find(".line:first").text()).toBe withInvisiblesShowing
leftEditorView.getPaneView().getModel().splitDown(copyActiveItem: true)
lowerLeftEditorView = atom.workspaceView.getActiveView()
lowerLeftEditorView = leftEditorView.splitDown()
expect(lowerLeftEditorView.find(".line:first").text()).toBe withInvisiblesShowing
atom.workspaceView.trigger "window:toggle-invisibles"
expect(rightEditorView.find(".line:first").text()).toBe " "
expect(leftEditorView.find(".line:first").text()).toBe " "
rightEditorView.getPaneView().getModel().splitDown(copyActiveItem: true)
lowerRightEditorView = atom.workspaceView.getActiveView()
lowerRightEditorView = rightEditorView.splitDown()
expect(lowerRightEditorView.find(".line:first").text()).toBe " "
describe ".eachEditorView(callback)", ->
@@ -244,7 +242,7 @@ describe "WorkspaceView", ->
atom.workspaceView.eachEditorView(callback)
count = 0
callbackEditor = null
atom.workspaceView.getActiveView().getPaneView().getModel().splitRight(copyActiveItem: true)
atom.workspaceView.getActiveView().splitRight()
expect(count).toBe 1
expect(callbackEditor).toBe atom.workspaceView.getActiveView()
@@ -262,10 +260,10 @@ describe "WorkspaceView", ->
subscription = atom.workspaceView.eachEditorView(callback)
expect(count).toBe 1
atom.workspaceView.getActiveView().getPaneView().getModel().splitRight(copyActiveItem: true)
atom.workspaceView.getActiveView().splitRight()
expect(count).toBe 2
subscription.off()
atom.workspaceView.getActiveView().getPaneView().getModel().splitRight(copyActiveItem: true)
atom.workspaceView.getActiveView().splitRight()
expect(count).toBe 2
describe "core:close", ->
@@ -274,7 +272,7 @@ describe "WorkspaceView", ->
paneView1 = atom.workspaceView.getActivePaneView()
editorView = atom.workspaceView.getActiveView()
editorView.getPaneView().getModel().splitRight(copyActiveItem: true)
editorView.splitRight()
paneView2 = atom.workspaceView.getActivePaneView()
expect(paneView1).not.toBe paneView2
+262 -386
Ver Arquivo
@@ -7,25 +7,39 @@ screen = require 'screen'
shell = require 'shell'
_ = require 'underscore-plus'
{deprecate} = require 'grim'
{Emitter} = require 'event-kit'
{deprecated} = require 'grim'
{Model} = require 'theorist'
fs = require 'fs-plus'
{$} = require './space-pen-extensions'
WindowEventHandler = require './window-event-handler'
# Essential: Atom global for dealing with packages, themes, menus, and the window.
# Public: Atom global for dealing with packages, themes, menus, and the window.
#
# An instance of this class is always available as the `atom` global.
#
# ## Useful properties available:
#
# * `atom.clipboard` - A {Clipboard} instance
# * `atom.config` - A {Config} instance
# * `atom.contextMenu` - A {ContextMenuManager} instance
# * `atom.deserializers` - A {DeserializerManager} instance
# * `atom.keymaps` - A {KeymapManager} instance
# * `atom.menu` - A {MenuManager} instance
# * `atom.packages` - A {PackageManager} instance
# * `atom.project` - A {Project} instance
# * `atom.syntax` - A {Syntax} instance
# * `atom.themes` - A {ThemeManager} instance
# * `atom.workspace` - A {Workspace} instance
# * `atom.workspaceView` - A {WorkspaceView} instance
module.exports =
class Atom extends Model
@version: 1 # Increment this when the serialization format changes
# Load or create the Atom environment in the given mode.
# Public: Load or create the Atom environment in the given mode.
#
# * `mode` A {String} mode that is either 'editor' or 'spec' depending on the
# kind of environment you want to build.
# mode - Pass 'editor' or 'spec' depending on the kind of environment you
# want to build.
#
# Returns an Atom instance, fully initialized
@loadOrCreate: (mode) ->
@@ -97,71 +111,20 @@ class Atom extends Model
remote.getCurrentWindow()
workspaceViewParentSelector: 'body'
lastUncaughtError: null
###
Section: Properties
###
# Public: A {CommandRegistry} instance
commands: null
# Public: A {Config} instance
config: null
# Public: A {Clipboard} instance
clipboard: null
# Public: A {ContextMenuManager} instance
contextMenu: null
# Public: A {MenuManager} instance
menu: null
# Public: A {KeymapManager} instance
keymaps: null
# Public: A {Project} instance
project: null
# Public: A {Syntax} instance
syntax: null
# Public: A {PackageManager} instance
packages: null
# Public: A {ThemeManager} instance
themes: null
# Public: A {DeserializerManager} instance
deserializers: null
# Public: A {Workspace} instance
workspace: null
# Public: A {WorkspaceView} instance
workspaceView: null
###
Section: Construction and Destruction
###
# Call .loadOrCreate instead
constructor: (@state) ->
@emitter = new Emitter
{@mode} = @state
DeserializerManager = require './deserializer-manager'
@deserializers = new DeserializerManager()
# Sets up the basic services that should be available in all modes
# (both spec and application).
#
# Call after this instance has been assigned to the `atom` global.
# Public: Sets up the basic services that should be available in all modes
# (both spec and application). Call after this instance has been assigned to
# the `atom` global.
initialize: ->
window.onerror = =>
@openDevTools()
@executeJavaScriptInDevTools('InspectorFrontendAPI.showConsole()')
@lastUncaughtError = Array::slice.call(arguments)
@emit 'uncaught-error', arguments...
@unsubscribe()
@@ -171,7 +134,6 @@ class Atom extends Model
Config = require './config'
KeymapManager = require './keymap-extensions'
CommandRegistry = require './command-registry'
PackageManager = require './package-manager'
Clipboard = require './clipboard'
Syntax = require './syntax'
@@ -193,16 +155,15 @@ class Atom extends Model
@config = new Config({configDirPath, resourcePath})
@keymaps = new KeymapManager({configDirPath, resourcePath})
@keymap = @keymaps # Deprecated
@commands = new CommandRegistry
@packages = new PackageManager({devMode, configDirPath, resourcePath, safeMode})
@themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath, safeMode})
@contextMenu = new ContextMenuManager({resourcePath, devMode})
@themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath})
@contextMenu = new ContextMenuManager(devMode)
@menu = new MenuManager({resourcePath})
@clipboard = new Clipboard()
@syntax = @deserializers.deserialize(@state.syntax) ? new Syntax()
@subscribe @packages.onDidActivateAll => @watchThemes()
@subscribe @packages, 'activated', => @watchThemes()
Project = require './project'
TextBuffer = require 'text-buffer'
@@ -213,196 +174,41 @@ class Atom extends Model
@windowEventHandler = new WindowEventHandler
###
Section: Event Subscription
###
# Deprecated: Callers should be converted to use atom.deserializers
registerRepresentationClass: ->
deprecated("Callers should be converted to use atom.deserializers")
# Extended: Invoke the given callback whenever {::beep} is called.
#
# * `callback` {Function} to be called whenever {::beep} is called.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidBeep: (callback) ->
@emitter.on 'did-beep', callback
# Deprecated: Callers should be converted to use atom.deserializers
registerRepresentationClasses: ->
deprecated("Callers should be converted to use atom.deserializers")
###
Section: Atom Details
###
setBodyPlatformClass: ->
document.body.classList.add("platform-#{process.platform}")
# Public: Is the current window in development mode?
inDevMode: ->
@getLoadSettings().devMode
# Public: Is the current window running specs?
inSpecMode: ->
@getLoadSettings().isSpec
# Public: Get the version of the Atom application.
#
# Returns the version text {String}.
getVersion: ->
@appVersion ?= @getLoadSettings().appVersion
# Public: Determine whether the current version is an official release.
isReleasedVersion: ->
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
# Public: Get the directory path to Atom's configuration area.
#
# Returns the absolute path to `~/.atom`.
getConfigDirPath: ->
@constructor.getConfigDirPath()
# Public: Get the time taken to completely load the current window.
#
# This time include things like loading and activating packages, creating
# DOM elements for the editor, and reading the config.
#
# Returns the {Number} of milliseconds taken to load the window or null
# if the window hasn't finished loading yet.
getWindowLoadTime: ->
@loadTime
# Public: Get the load settings for the current window.
#
# Returns an {Object} containing all the load setting key/value pairs.
getLoadSettings: ->
@constructor.getLoadSettings()
###
Section: Managing The Atom Window
###
# Essential: Open a new Atom window using the given options.
#
# Calling this method without an options parameter will open a prompt to pick
# a file/folder to open in the new window.
#
# * `options` An {Object} with the following keys:
# * `pathsToOpen` An {Array} of {String} paths to open.
# * `newWindow` A {Boolean}, true to always open a new window instead of
# reusing existing windows depending on the paths to open.
# * `devMode` A {Boolean}, true to open the window in development mode.
# Development mode loads the Atom source from the locally cloned
# repository and also loads all the packages in ~/.atom/dev/packages
# * `safeMode` A {Boolean}, true to open the window in safe mode. Safe
# mode prevents all packages installed to ~/.atom/packages from loading.
open: (options) ->
ipc.send('open', options)
# Essential: Close the current window.
close: ->
@getCurrentWindow().close()
# Essential: Get the size of current window.
#
# Returns an {Object} in the format `{width: 1000, height: 700}`
getSize: ->
[width, height] = @getCurrentWindow().getSize()
{width, height}
# Essential: Set the size of current window.
#
# * `width` The {Number} of pixels.
# * `height` The {Number} of pixels.
setSize: (width, height) ->
@getCurrentWindow().setSize(width, height)
# Essential: Get the position of current window.
#
# Returns an {Object} in the format `{x: 10, y: 20}`
getPosition: ->
[x, y] = @getCurrentWindow().getPosition()
{x, y}
# Essential: Set the position of current window.
#
# * `x` The {Number} of pixels.
# * `y` The {Number} of pixels.
setPosition: (x, y) ->
ipc.send('call-window-method', 'setPosition', x, y)
# Extended: Get the current window
# Public: Get the current window
getCurrentWindow: ->
@constructor.getCurrentWindow()
# Extended: Move current window to the center of the screen.
center: ->
ipc.send('call-window-method', 'center')
# Extended: Focus the current window.
focus: ->
ipc.send('call-window-method', 'focus')
$(window).focus()
# Extended: Show the current window.
show: ->
ipc.send('call-window-method', 'show')
# Extended: Hide the current window.
hide: ->
ipc.send('call-window-method', 'hide')
# Extended: Reload the current window.
reload: ->
ipc.send('call-window-method', 'restart')
# Extended: Returns a {Boolean} true when the current window is maximized.
isMaximixed: ->
@getCurrentWindow().isMaximized()
maximize: ->
ipc.send('call-window-method', 'maximize')
# Extended: Is the current window in full screen mode?
isFullScreen: ->
@getCurrentWindow().isFullScreen()
# 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")
# Extended: Toggle the full screen state of the current window.
toggleFullScreen: ->
@setFullScreen(!@isFullScreen())
# Schedule the window to be shown and focused on the next tick.
# Public: Get the dimensions of this window.
#
# This is done in a next tick to prevent a white flicker from occurring
# if called synchronously.
displayWindow: ({maximize}={}) ->
setImmediate =>
@show()
@focus()
@setFullScreen(true) if @workspace.fullScreen
@maximize() if maximize
# Get the dimensions of this window.
#
# Returns an {Object} with the following keys:
# * `x` The window's x-position {Number}.
# * `y` The window's y-position {Number}.
# * `width` The window's width {Number}.
# * `height` The window's height {Number}.
# Returns an object with x, y, width, and height keys.
getWindowDimensions: ->
browserWindow = @getCurrentWindow()
[x, y] = browserWindow.getPosition()
[width, height] = browserWindow.getSize()
maximized = browserWindow.isMaximized()
{x, y, width, height, maximized}
{x, y, width, height}
# Set the dimensions of the window.
# Public: Set the dimensions of the window.
#
# The window will be centered if either the x or y coordinate is not set
# in the dimensions parameter. If x or y are omitted the window will be
# centered. If height or width are omitted only the position will be changed.
#
# * `dimensions` An {Object} with the following keys:
# * `x` The new x coordinate.
# * `y` The new y coordinate.
# * `width` The new width.
# * `height` The new height.
# dimensions - An {Object} with the following keys:
# :x - The new x coordinate.
# :y - The new y coordinate.
# :width - The new width.
# :height - The new height.
setWindowDimensions: ({x, y, width, height}) ->
if width? and height?
@setSize(width, height)
@@ -443,131 +249,16 @@ class Atom extends Model
unless @isValidDimensions(dimensions)
dimensions = @getDefaultWindowDimensions()
@setWindowDimensions(dimensions)
dimensions
storeWindowDimensions: ->
dimensions = @getWindowDimensions()
@state.windowDimensions = dimensions if @isValidDimensions(dimensions)
# Call this method when establishing a real application window.
startEditorWindow: ->
{resourcePath, safeMode} = @getLoadSettings()
CommandInstaller = require './command-installer'
CommandInstaller.installAtomCommand resourcePath, false, (error) ->
console.warn error.message if error?
CommandInstaller.installApmCommand resourcePath, false, (error) ->
console.warn error.message if error?
dimensions = @restoreWindowDimensions()
@config.load()
@config.setDefaults('core', require('./workspace-view').configDefaults)
@config.setDefaults('editor', require('./editor-view').configDefaults)
@keymaps.loadBundledKeymaps()
@themes.loadBaseStylesheets()
@packages.loadPackages()
@deserializeEditorWindow()
@watchProjectPath()
@packages.activate()
@keymaps.loadUserKeymap()
@requireUserInitScript() unless safeMode
@menu.update()
maximize = dimensions?.maximized and process.platform isnt 'darwin'
@displayWindow({maximize})
unloadEditorWindow: ->
return if not @project and not @workspaceView
@state.syntax = @syntax.serialize()
@state.project = @project.serialize()
@state.workspace = @workspace.serialize()
@packages.deactivatePackages()
@state.packageStates = @packages.packageStates
@saveSync()
@windowState = null
removeEditorWindow: ->
return if not @project and not @workspaceView
@workspaceView?.remove()
@workspaceView = null
@project?.destroy()
@project = null
@windowEventHandler?.unsubscribe()
###
Section: Messaging the User
###
# Essential: Visually and audibly trigger a beep.
beep: ->
shell.beep() if @config.get('core.audioBeep')
@workspaceView.trigger 'beep'
@emitter.emit 'did-beep'
# Essential: A flexible way to open a dialog akin to an alert dialog.
# Public: Get the load settings for the current window.
#
# ## Examples
#
# ```coffee
# atom.confirm
# message: 'How you feeling?'
# detailedMessage: 'Be honest.'
# buttons:
# Good: -> window.alert('good to hear')
# Bad: -> window.alert('bummer')
# ```
#
# * `options` An {Object} with the following keys:
# * `message` The {String} message to display.
# * `detailedMessage` (optional) The {String} detailed message to display.
# * `buttons` (optional) Either an array of strings or an object where keys are
# button names and the values are callbacks to invoke when clicked.
#
# Returns the chosen button index {Number} if the buttons option was an array.
confirm: ({message, detailedMessage, buttons}={}) ->
buttons ?= {}
if _.isArray(buttons)
buttonLabels = buttons
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox @getCurrentWindow(),
type: 'info'
message: message
detail: detailedMessage
buttons: buttonLabels
if _.isArray(buttons)
chosen
else
callback = buttons[buttonLabels[chosen]]
callback?()
###
Section: Managing the Dev Tools
###
# Extended: Open the dev tools for the current window.
openDevTools: ->
ipc.send('call-window-method', 'openDevTools')
# Extended: Toggle the visibility of the dev tools for the current window.
toggleDevTools: ->
ipc.send('call-window-method', 'toggleDevTools')
# Extended: Execute code in dev tools.
executeJavaScriptInDevTools: (code) ->
ipc.send('call-window-method', 'executeJavaScriptInDevTools', code)
###
Section: Private
###
# Returns an object containing all the load setting key/value pairs.
getLoadSettings: ->
@constructor.getLoadSettings()
deserializeProject: ->
Project = require './project'
@@ -598,33 +289,111 @@ class Atom extends Model
@deserializeProject()
@deserializeWorkspaceView()
# Call this method when establishing a real application window.
startEditorWindow: ->
CommandInstaller = require './command-installer'
resourcePath = atom.getLoadSettings().resourcePath
CommandInstaller.installAtomCommand resourcePath, false, (error) ->
console.warn error.message if error?
CommandInstaller.installApmCommand resourcePath, false, (error) ->
console.warn error.message if error?
@restoreWindowDimensions()
@config.load()
@config.setDefaults('core', require('./workspace-view').configDefaults)
@config.setDefaults('editor', require('./editor-view').configDefaults)
@keymaps.loadBundledKeymaps()
@themes.loadBaseStylesheets()
@packages.loadPackages()
@deserializeEditorWindow()
@packages.activate()
@keymaps.loadUserKeymap()
@requireUserInitScript()
@menu.update()
$(window).on 'unload', =>
$(document.body).css('visibility', 'hidden')
@unloadEditorWindow()
false
@displayWindow()
unloadEditorWindow: ->
return if not @project and not @workspaceView
@state.syntax = @syntax.serialize()
@state.project = @project.serialize()
@state.workspace = @workspace.serialize()
@packages.deactivatePackages()
@state.packageStates = @packages.packageStates
@saveSync()
@workspaceView.remove()
@workspaceView = null
@project.destroy()
@windowEventHandler?.unsubscribe()
@keymaps.destroy()
@windowState = null
loadThemes: ->
@themes.load()
watchThemes: ->
@themes.onDidReloadAll =>
@themes.on 'reloaded', =>
# Only reload stylesheets from non-theme packages
for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
pack.reloadStylesheets?()
null
# Notify the browser project of the window's current project path
watchProjectPath: ->
onProjectPathChanged = =>
ipc.send('window-command', 'project-path-changed', @project.getPath())
@subscribe @project, 'path-changed', onProjectPathChanged
onProjectPathChanged()
# Public: Open a new Atom window using the given options.
#
# Calling this method without an options parameter will open a prompt to pick
# a file/folder to open in the new window.
#
# options - An {Object} with the following keys:
# :pathsToOpen - An {Array} of {String} paths to open.
open: (options) ->
ipc.send('open', options)
exit: (status) ->
app = remote.require('app')
app.emit('will-exit')
remote.process.exit(status)
# Public: Open a confirm dialog.
#
# ## Example
#
# ```coffee
# atom.confirm
# message: 'How you feeling?'
# detailedMessage: 'Be honest.'
# buttons:
# Good: -> window.alert('good to hear')
# Bad: -> window.alert('bummer')
# ```
#
# options - An {Object} with the following keys:
# :message - The {String} message to display.
# :detailedMessage - The {String} detailed message to display.
# :buttons - Either an array of strings or an object where keys are
# button names and the values are callbacks to invoke when
# clicked.
#
# Returns the chosen button index {Number} if the buttons option was an array.
confirm: ({message, detailedMessage, buttons}={}) ->
buttons ?= {}
if _.isArray(buttons)
buttonLabels = buttons
else
buttonLabels = Object.keys(buttons)
setDocumentEdited: (edited) ->
ipc.send('call-window-method', 'setDocumentEdited', edited)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox @getCurrentWindow(),
type: 'info'
message: message
detail: detailedMessage
buttons: buttonLabels
setRepresentedFilename: (filename) ->
ipc.send('call-window-method', 'setRepresentedFilename', filename)
if _.isArray(buttons)
chosen
else
callback = buttons[buttonLabels[chosen]]
callback?()
showSaveDialog: (callback) ->
callback(showSaveDialogSync())
@@ -635,6 +404,109 @@ class Atom extends Model
dialog = remote.require('dialog')
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
# Public: Open the dev tools for the current window.
openDevTools: ->
ipc.send('call-window-method', 'openDevTools')
# Public: Toggle the visibility of the dev tools for the current window.
toggleDevTools: ->
ipc.send('call-window-method', 'toggleDevTools')
# Public: Execute code in dev tools.
executeJavaScriptInDevTools: (code) ->
ipc.send('call-window-method', 'executeJavaScriptInDevTools', code)
# Public: Reload the current window.
reload: ->
ipc.send('call-window-method', 'restart')
# Public: Focus the current window.
focus: ->
ipc.send('call-window-method', 'focus')
$(window).focus()
# Public: Show the current window.
show: ->
ipc.send('call-window-method', 'show')
# Public: Hide the current window.
hide: ->
ipc.send('call-window-method', 'hide')
# Public: Set the size of current window.
#
# width - The {Number} of pixels.
# height - The {Number} of pixels.
setSize: (width, height) ->
@getCurrentWindow().setSize(width, height)
# Public: Set the position of current window.
#
# x - The {Number} of pixels.
# y - The {Number} of pixels.
setPosition: (x, y) ->
ipc.send('call-window-method', 'setPosition', x, y)
# Public: Move current window to the center of the screen.
center: ->
ipc.send('call-window-method', 'center')
# Schedule the window to be shown and focused on the next tick.
#
# This is done in a next tick to prevent a white flicker from occurring
# if called synchronously.
displayWindow: ->
setImmediate =>
@show()
@focus()
@setFullScreen(true) if @workspaceView.fullScreen
# Public: Close the current window.
close: ->
@getCurrentWindow().close()
exit: (status) ->
app = remote.require('app')
app.emit('will-exit')
remote.process.exit(status)
# Public: Is the current window in development mode?
inDevMode: ->
@getLoadSettings().devMode
# Public: Is the current window running specs?
inSpecMode: ->
@getLoadSettings().isSpec
# Public: Toggle the full screen state of the current window.
toggleFullScreen: ->
@setFullScreen(!@isFullScreen())
# Public: 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")
# Public: Is the current window in full screen mode?
isFullScreen: ->
@getCurrentWindow().isFullScreen()
# Public: Get the version of the Atom application.
#
# Returns the version text {String}.
getVersion: ->
@appVersion ?= @getLoadSettings().appVersion
# Public: Determine whether the current version is an official release.
isReleasedVersion: ->
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
# Public: Get the directory path to Atom's configuration area.
#
# Returns the absolute path to `~/.atom`.
getConfigDirPath: ->
@constructor.getConfigDirPath()
saveSync: ->
stateString = JSON.stringify(@state)
if statePath = @constructor.getStatePath(@mode)
@@ -642,12 +514,27 @@ class Atom extends Model
else
@getCurrentWindow().loadSettings.windowState = stateString
# Public: Get the time taken to completely load the current window.
#
# This time include things like loading and activating packages, creating
# DOM elements for the editor, and reading the config.
#
# Returns the number of milliseconds taken to load the window or null
# if the window hasn't finished loading yet.
getWindowLoadTime: ->
@loadTime
crashMainProcess: ->
remote.process.crash()
crashRenderProcess: ->
process.crash()
# Public: Visually and audibly trigger a beep.
beep: ->
shell.beep() if @config.get('core.audioBeep')
@workspaceView.trigger 'beep'
getUserInitScriptPath: ->
initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee'])
initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee')
@@ -659,13 +546,13 @@ class Atom extends Model
catch error
console.error "Failed to load `#{userInitScriptPath}`", error.stack, error
# Require the module with the given globals.
# Public: Require the module with the given globals.
#
# The globals will be set on the `window` object and removed after the
# require completes.
#
# * `id` The {String} module name or path.
# * `globals` An optinal {Object} to set as globals during require.
# id - The {String} module name or path.
# globals - An {Object} to set as globals during require (default: {})
requireWithGlobals: (id, globals={}) ->
existingGlobals = {}
for key, value of globals
@@ -679,14 +566,3 @@ class Atom extends Model
delete window[key]
else
window[key] = value
# Deprecated: Callers should be converted to use atom.deserializers
registerRepresentationClass: ->
deprecate("Callers should be converted to use atom.deserializers")
# Deprecated: Callers should be converted to use atom.deserializers
registerRepresentationClasses: ->
deprecate("Callers should be converted to use atom.deserializers")
setBodyPlatformClass: ->
document.body.classList.add("platform-#{process.platform}")
+1 -1
Ver Arquivo
@@ -116,7 +116,7 @@ class ApplicationMenu
item.metadata ?= {}
if item.command
item.accelerator = @acceleratorForCommand(item.command, keystrokesByCommand)
item.click = -> global.atomApplication.sendCommand(item.command)
item.click = => global.atomApplication.sendCommand(item.command)
item.metadata['windowSpecific'] = true unless /^application:/.test(item.command)
@translateTemplate(item.submenu, keystrokesByCommand) if item.submenu
template
+24 -71
Ver Arquivo
@@ -5,11 +5,13 @@ AutoUpdateManager = require './auto-update-manager'
BrowserWindow = require 'browser-window'
Menu = require 'menu'
app = require 'app'
dialog = require 'dialog'
fs = require 'fs'
ipc = require 'ipc'
path = require 'path'
os = require 'os'
net = require 'net'
shell = require 'shell'
url = require 'url'
{EventEmitter} = require 'events'
_ = require 'underscore-plus'
@@ -101,13 +103,6 @@ class AtomApplication
window.once 'window:loaded', =>
@autoUpdateManager.emitUpdateAvailableEvent(window)
unless window.isSpec
focusHandler = => @lastFocusedWindow = window
window.browserWindow.on 'focus', focusHandler
window.browserWindow.once 'closed', =>
@lastFocusedWindow = null if window is @lastFocusedWindow
window.browserWindow.removeListener 'focus', focusHandler
# Creates server to listen for additional atom application launches.
#
# You can run the atom command multiple times, but after the first launch
@@ -140,7 +135,7 @@ class AtomApplication
# Registers basic application commands, non-idempotent.
handleEvents: ->
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath, safeMode: @focusedWindow()?.safeMode)
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
@on 'application:run-benchmarks', -> @runBenchmarks()
@on 'application:quit', -> app.quit()
@on 'application:new-window', -> @openPath(windowDimensions: @focusedWindow()?.getDimensions())
@@ -154,8 +149,8 @@ class AtomApplication
atomWindow ?= @focusedWindow()
atomWindow?.browserWindow.inspectElement(x, y)
@on 'application:open-documentation', -> require('shell').openExternal('https://atom.io/docs/latest/?app')
@on 'application:open-terms-of-use', -> require('shell').openExternal('https://atom.io/terms')
@on 'application:open-documentation', -> shell.openExternal('https://atom.io/docs/latest/?app')
@on 'application:open-terms-of-use', -> shell.openExternal('https://atom.io/terms')
@on 'application:install-update', -> @autoUpdateManager.install()
@on 'application:check-for-update', => @autoUpdateManager.check()
@@ -204,15 +199,13 @@ class AtomApplication
# A request from the associated render process to open a new render process.
ipc.on 'open', (event, options) =>
window = @windowForEvent(event)
if options?
if options.pathsToOpen?.length > 0
options.window = window
@openPaths(options)
else
new AtomWindow(options)
else
@promptForPath({window})
@promptForPath()
ipc.on 'update-application-menu', (event, template, keystrokesByCommand) =>
@applicationMenu.update(template, keystrokesByCommand)
@@ -288,12 +281,8 @@ class AtomApplication
# Returns the {AtomWindow} for the given path.
windowForPath: (pathToOpen) ->
_.find @windows, (atomWindow) -> atomWindow.containsPath(pathToOpen)
# Returns the {AtomWindow} for the given ipc event.
windowForEvent: ({sender}) ->
window = BrowserWindow.fromWebContents(sender)
_.find @windows, ({browserWindow}) -> window is browserWindow
for atomWindow in @windows
return atomWindow if atomWindow.containsPath(pathToOpen)
# Public: Returns the currently focused {AtomWindow} or undefined if none.
focusedWindow: ->
@@ -307,10 +296,9 @@ class AtomApplication
# :newWindow - Boolean of whether this should be opened in a new window.
# :devMode - Boolean to control the opened window's dev mode.
# :safeMode - Boolean to control the opened window's safe mode.
# :window - {AtomWindow} to open file paths in.
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window}) ->
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode}) ->
for pathToOpen in pathsToOpen ? []
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window})
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode})
# Public: Opens a single path, in an existing window if possible.
#
@@ -321,33 +309,15 @@ class AtomApplication
# :devMode - Boolean to control the opened window's dev mode.
# :safeMode - Boolean to control the opened window's safe mode.
# :windowDimensions - Object with height and width keys.
# :window - {AtomWindow} to open file paths in.
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) ->
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions}={}) ->
{pathToOpen, initialLine, initialColumn} = @locationForPathToOpen(pathToOpen)
unless pidToKillWhenClosed or newWindow
pathToOpenStat = fs.statSyncNoException(pathToOpen)
# Default to using the specified window or the last focused window
currentWindow = window ? @lastFocusedWindow
if pathToOpenStat.isFile?()
# Open the file in the current window
existingWindow = currentWindow
else if pathToOpenStat.isDirectory?()
# Open the folder in the current window if it doesn't have a path
existingWindow = currentWindow unless currentWindow?.hasProjectPath()
# Don't reuse windows in dev mode
existingWindow ?= @windowForPath(pathToOpen) unless devMode
if existingWindow?
unless devMode
existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow
if existingWindow
openedWindow = existingWindow
openedWindow.openPath(pathToOpen, initialLine)
if openedWindow.isMinimized()
openedWindow.restore()
else
openedWindow.focus()
openedWindow.restore()
else
if devMode
try
@@ -361,7 +331,7 @@ class AtomApplication
if pidToKillWhenClosed?
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
openedWindow.browserWindow.once 'closed', =>
openedWindow.browserWindow.on 'closed', =>
@killProcessForWindow(openedWindow)
# Kill all processes associated with opened windows.
@@ -417,13 +387,11 @@ class AtomApplication
# Opens up a new {AtomWindow} to run specs within.
#
# options -
# :exitWhenDone - A Boolean that, if true, will close the window upon
# :exitWhenDone - A Boolean that if true, will close the window upon
# completion.
# :resourcePath - The path to include specs from.
# :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}) ->
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath
@@ -434,20 +402,16 @@ class AtomApplication
isSpec = true
devMode = true
safeMode ?= false
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile, safeMode})
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile})
runBenchmarks: ({exitWhenDone, specDirectory}={}) ->
runBenchmarks: ->
try
bootstrapScript = require.resolve(path.resolve(global.devResourcePath, 'benchmark', 'benchmark-bootstrap'))
catch error
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'benchmark', 'benchmark-bootstrap'))
specDirectory ?= path.dirname(bootstrapScript)
isSpec = true
devMode = true
new AtomWindow({bootstrapScript, @resourcePath, exitWhenDone, isSpec, specDirectory, devMode})
new AtomWindow({bootstrapScript, @resourcePath, isSpec})
locationForPathToOpen: (pathToOpen) ->
return {pathToOpen} unless pathToOpen
@@ -474,8 +438,7 @@ class AtomApplication
# should be in dev mode or not.
# :safeMode - A Boolean which controls whether any newly opened windows
# should be in safe mode or not.
# :window - An {AtomWindow} to use for opening a selected file path.
promptForPath: ({type, devMode, safeMode, window}={}) ->
promptForPath: ({type, devMode, safeMode}={}) ->
type ?= 'all'
properties =
switch type
@@ -483,15 +446,5 @@ class AtomApplication
when 'folder' then ['openDirectory']
when 'all' then ['openFile', 'openDirectory']
else throw new Error("#{type} is an invalid type for promptForPath")
# Show the open dialog as child window on Windows and Linux, and as
# independent dialog on OS X. This matches most native apps.
parentWindow =
if process.platform is 'darwin'
null
else
BrowserWindow.getFocusedWindow()
dialog = require 'dialog'
dialog.showOpenDialog parentWindow, title: 'Open', properties: properties.concat(['multiSelections', 'createDirectory']), (pathsToOpen) =>
@openPaths({pathsToOpen, devMode, safeMode, window})
dialog.showOpenDialog title: 'Open', properties: properties.concat(['multiSelections', 'createDirectory']), (pathsToOpen) =>
@openPaths({pathsToOpen, devMode, safeMode})
+2 -2
Ver Arquivo
@@ -1,5 +1,5 @@
app = require 'app'
fs = require 'fs'
fs = require 'fs-plus'
path = require 'path'
protocol = require 'protocol'
@@ -24,5 +24,5 @@ class AtomProtocolHandler
relativePath = path.normalize(request.url.substr(7))
for loadPath in @loadPaths
filePath = path.join(loadPath, relativePath)
break if fs.statSyncNoException(filePath).isFile?()
break if fs.isFileSync(filePath)
return new protocol.RequestFileJob(filePath)
+6 -23
Ver Arquivo
@@ -1,5 +1,7 @@
BrowserWindow = require 'browser-window'
ContextMenu = require './context-menu'
app = require 'app'
dialog = require 'dialog'
path = require 'path'
fs = require 'fs'
url = require 'url'
@@ -18,19 +20,14 @@ class AtomWindow
isSpec: null
constructor: (settings={}) ->
{@resourcePath, pathToOpen, initialLine, initialColumn, @isSpec, @exitWhenDone, @safeMode} = settings
{@resourcePath, pathToOpen, initialLine, initialColumn, @isSpec, @exitWhenDone} = settings
# Normalize to make sure drive letter case is consistent on Windows
@resourcePath = path.normalize(@resourcePath) if @resourcePath
@browserWindow = new BrowserWindow
show: false
title: 'Atom'
icon: @constructor.iconPath
'web-preferences':
'subpixel-font-scaling': false
global.atomApplication.addWindow(this)
@browserWindow = new BrowserWindow show: false, title: 'Atom', icon: @constructor.iconPath
@handleEvents()
loadSettings = _.extend({}, settings)
@@ -52,8 +49,6 @@ class AtomWindow
@emit 'window:loaded'
@loaded = true
@browserWindow.on 'project-path-changed', (@projectPath) =>
@browserWindow.loadUrl @getUrl(loadSettings)
@browserWindow.focusOnWebView() if @isSpec
@@ -71,18 +66,9 @@ class AtomWindow
slashes: true
query: {loadSettings: JSON.stringify(loadSettings)}
hasProjectPath: -> @projectPath?.length > 0
getInitialPath: ->
@browserWindow.loadSettings.initialPath
setupContextMenu: ->
ContextMenu = null
@browserWindow.on 'context-menu', (menuTemplate) =>
ContextMenu ?= require './context-menu'
new ContextMenu(menuTemplate, this)
containsPath: (pathToCheck) ->
initialPath = @getInitialPath()
if not initialPath
@@ -105,7 +91,6 @@ class AtomWindow
@browserWindow.on 'unresponsive', =>
return if @isSpec
dialog = require 'dialog'
chosen = dialog.showMessageBox @browserWindow,
type: 'warning'
buttons: ['Close', 'Keep Waiting']
@@ -116,7 +101,6 @@ class AtomWindow
@browserWindow.webContents.on 'crashed', =>
global.atomApplication.exit(100) if @exitWhenDone
dialog = require 'dialog'
chosen = dialog.showMessageBox @browserWindow,
type: 'warning'
buttons: ['Close Window', 'Reload', 'Keep It Open']
@@ -126,7 +110,8 @@ class AtomWindow
when 0 then @browserWindow.destroy()
when 1 then @browserWindow.restart()
@setupContextMenu()
@browserWindow.on 'context-menu', (menuTemplate) =>
new ContextMenu(menuTemplate, this)
if @isSpec
# Workaround for https://github.com/atom/atom-shell/issues/380
@@ -184,8 +169,6 @@ class AtomWindow
isFocused: -> @browserWindow.isFocused()
isMinimized: -> @browserWindow.isMinimized()
isWebViewFocused: -> @browserWindow.isWebViewFocused()
isSpecWindow: -> @isSpec
+17 -24
Ver Arquivo
@@ -1,47 +1,44 @@
autoUpdater = null
https = require 'https'
autoUpdater = require 'auto-updater'
dialog = require 'dialog'
_ = require 'underscore-plus'
{EventEmitter} = require 'events'
IdleState = 'idle'
CheckingState = 'checking'
DownladingState = 'downloading'
UpdateAvailableState = 'update-available'
NoUpdateAvailableState = 'no-update-available'
ErrorState = 'error'
IDLE_STATE='idle'
CHECKING_STATE='checking'
DOWNLOADING_STATE='downloading'
UPDATE_AVAILABLE_STATE='update-available'
NO_UPDATE_AVAILABLE_STATE='no-update-available'
ERROR_STATE='error'
module.exports =
class AutoUpdateManager
_.extend @prototype, EventEmitter.prototype
constructor: (@version) ->
@state = IdleState
@state = IDLE_STATE
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
process.nextTick => @setupAutoUpdater()
setupAutoUpdater: ->
autoUpdater = require 'auto-updater'
if process.platform is 'win32'
autoUpdater.checkForUpdates = => @checkForUpdatesShim()
autoUpdater.setFeedUrl @feedUrl
autoUpdater.on 'checking-for-update', =>
@setState(CheckingState)
@setState(CHECKING_STATE)
autoUpdater.on 'update-not-available', =>
@setState(NoUpdateAvailableState)
@setState(NO_UPDATE_AVAILABLE_STATE)
autoUpdater.on 'update-available', =>
@setState(DownladingState)
@setState(DOWNLOADING_STATE)
autoUpdater.on 'error', (event, message) =>
@setState(ErrorState)
@setState(ERROR_STATE)
console.error "Error Downloading Update: #{message}"
autoUpdater.on 'update-downloaded', (event, @releaseNotes, @releaseVersion) =>
@setState(UpdateAvailableState)
@setState(UPDATE_AVAILABLE_STATE)
@emitUpdateAvailableEvent(@getWindows()...)
# Only released versions should check for updates.
@@ -51,8 +48,6 @@ class AutoUpdateManager
# Windows doesn't have an auto-updater, so use this method to shim the events.
checkForUpdatesShim: ->
autoUpdater.emit 'checking-for-update'
https = require 'https'
request = https.get @feedUrl, (response) ->
if response.statusCode == 200
body = ""
@@ -72,14 +67,14 @@ class AutoUpdateManager
atomWindow.sendCommand('window:update-available', [@releaseVersion, @releaseNotes])
setState: (state) ->
return if @state is state
return unless @state != state
@state = state
@emit 'state-changed', @state
getState: ->
@state
check: ({hidePopups}={}) ->
check: ({hidePopups}={})->
unless hidePopups
autoUpdater.once 'update-not-available', @onUpdateNotAvailable
autoUpdater.once 'error', @onUpdateError
@@ -91,12 +86,10 @@ class AutoUpdateManager
onUpdateNotAvailable: =>
autoUpdater.removeListener 'error', @onUpdateError
dialog = require 'dialog'
dialog.showMessageBox type: 'info', buttons: ['OK'], message: 'No update available.', detail: "Version #{@version} is the latest version."
onUpdateError: (event, message) =>
autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable
dialog = require 'dialog'
dialog.showMessageBox type: 'warning', buttons: ['OK'], message: 'There was an error checking for updates.', detail: message
getWindows: ->
+8 -20
Ver Arquivo
@@ -3,9 +3,11 @@ global.shellStartTime = Date.now()
crashReporter = require 'crash-reporter'
app = require 'app'
fs = require 'fs'
module = require 'module'
path = require 'path'
optimist = require 'optimist'
nslog = require 'nslog'
dialog = require 'dialog'
console.log = nslog
@@ -31,14 +33,14 @@ start = ->
app.on 'will-finish-launching', ->
setupCrashReporter()
app.on 'ready', ->
app.on 'finish-launching', ->
app.removeListener 'open-file', addPathToOpen
app.removeListener 'open-url', addUrlToOpen
args.pathsToOpen = args.pathsToOpen.map (pathToOpen) ->
path.resolve(args.executedFrom ? process.cwd(), pathToOpen.toString())
setupCoffeeScript()
require('coffee-script').register()
if args.devMode
require(path.join(args.resourcePath, 'src', 'coffee-cache')).register()
AtomApplication = require path.join(args.resourcePath, 'src', 'browser', 'atom-application')
@@ -55,29 +57,13 @@ global.devResourcePath = path.normalize(global.devResourcePath) if global.devRes
setupCrashReporter = ->
crashReporter.start(productName: 'Atom', companyName: 'GitHub')
setupCoffeeScript = ->
CoffeeScript = null
require.extensions['.coffee'] = (module, filePath) ->
CoffeeScript ?= require('coffee-script')
coffee = fs.readFileSync(filePath, 'utf8')
js = CoffeeScript.compile(coffee, filename: filePath)
module._compile(js, filePath)
parseCommandLine = ->
version = app.getVersion()
options = optimist(process.argv[1..])
options.usage """
Atom Editor v#{version}
Usage: atom [options] [path ...]
One or more paths to files or folders to open may be specified.
File paths will open in the current window.
Folder paths will open in an existing window if that folder has already been
opened or a new window if it hasn't.
Usage: atom [options] [file ...]
"""
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the browser process in the foreground.')
@@ -116,7 +102,9 @@ parseCommandLine = ->
else if devMode
resourcePath = global.devResourcePath
unless fs.statSyncNoException(resourcePath)
try
fs.statSync resourcePath
catch
resourcePath = path.dirname(path.dirname(__dirname))
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile}
+22 -23
Ver Arquivo
@@ -1,40 +1,39 @@
BufferedProcess = require './buffered-process'
path = require 'path'
# Extended: Like {BufferedProcess}, but accepts a Node script as the command
# Public: Like {BufferedProcess}, but accepts a Node script as the command
# to run.
#
# This is necessary on Windows since it doesn't support shebang `#!` lines.
#
# ## Examples
# ## Requiring in packages
#
# ```coffee
# {BufferedNodeProcess} = require 'atom'
# {BufferedNodeProcess} = require 'atom'
# ```
module.exports =
class BufferedNodeProcess extends BufferedProcess
# Public: Runs the given Node script by spawning a new child process.
#
# * `options` An {Object} with the following keys:
# * `command` The {String} path to the JavaScript script to execute.
# * `args` The {Array} of arguments to pass to the script (optional).
# * `options` The options {Object} to pass to Node's `ChildProcess.spawn`
# method (optional).
# * `stdout` The callback {Function} that receives a single argument which
# contains the standard output from the command. The callback is
# called as data is received but it's buffered to ensure only
# complete lines are passed until the source stream closes. After
# the source stream has closed all remaining data is sent in a
# final call (optional).
# * `stderr` The callback {Function} that receives a single argument which
# contains the standard error output from the command. The
# callback is called as data is received but it's buffered to
# ensure only complete lines are passed until the source stream
# closes. After the source stream has closed all remaining data
# is sent in a final call (optional).
# * `exit` The callback {Function} which receives a single argument
# containing the exit status (optional).
# options - An {Object} with the following keys:
# :command - The {String} path to the JavaScript script to execute.
# :args - The {Array} of arguments to pass to the script (optional).
# :options - The options {Object} to pass to Node's `ChildProcess.spawn`
# method (optional).
# :stdout - The callback {Function} that receives a single argument which
# contains the standard output from the command. The callback is
# called as data is received but it's buffered to ensure only
# complete lines are passed until the source stream closes. After
# the source stream has closed all remaining data is sent in a
# final call (optional).
# :stderr - The callback {Function} that receives a single argument which
# contains the standard error output from the command. The
# callback is called as data is received but it's buffered to
# ensure only complete lines are passed until the source stream
# closes. After the source stream has closed all remaining data
# is sent in a final call (optional).
# :exit - The callback {Function} which receives a single argument
# containing the exit status (optional).
constructor: ({command, args, options, stdout, stderr, exit}) ->
node =
if process.platform is 'darwin'
+31 -73
Ver Arquivo
@@ -1,10 +1,10 @@
_ = require 'underscore-plus'
ChildProcess = require 'child_process'
# Extended: A wrapper which provides standard error/output line buffering for
# Public: A wrapper which provides standard error/output line buffering for
# Node's ChildProcess.
#
# ## Examples
# ## Requiring in packages
#
# ```coffee
# {BufferedProcess} = require 'atom'
@@ -19,45 +19,43 @@ module.exports =
class BufferedProcess
# Public: Runs the given command by spawning a new child process.
#
# * `options` An {Object} with the following keys:
# * `command` The {String} command to execute.
# * `args` The {Array} of arguments to pass to the command (optional).
# * `options` The options {Object} to pass to Node's `ChildProcess.spawn`
# method (optional).
# * `stdout` The callback {Function} that receives a single argument which
# contains the standard output from the command. The callback is
# called as data is received but it's buffered to ensure only
# complete lines are passed until the source stream closes. After
# the source stream has closed all remaining data is sent in a
# final call (optional).
# * `stderr` The callback {Function} that receives a single argument which
# contains the standard error output from the command. The
# callback is called as data is received but it's buffered to
# ensure only complete lines are passed until the source stream
# closes. After the source stream has closed all remaining data
# is sent in a final call (optional).
# * `exit` The callback {Function} which receives a single argument
# containing the exit status (optional).
# options - An {Object} with the following keys:
# :command - The {String} command to execute.
# :args - The {Array} of arguments to pass to the command (optional).
# :options - The options {Object} to pass to Node's `ChildProcess.spawn`
# method (optional).
# :stdout - The callback {Function} that receives a single argument which
# contains the standard output from the command. The callback is
# called as data is received but it's buffered to ensure only
# complete lines are passed until the source stream closes. After
# the source stream has closed all remaining data is sent in a
# final call (optional).
# :stderr - The callback {Function} that receives a single argument which
# contains the standard error output from the command. The
# callback is called as data is received but it's buffered to
# ensure only complete lines are passed until the source stream
# closes. After the source stream has closed all remaining data
# is sent in a final call (optional).
# :exit - The callback {Function} which receives a single argument
# containing the exit status (optional).
constructor: ({command, args, options, stdout, stderr, exit}={}) ->
options ?= {}
# Quick hack. Killing @process will only kill cmd.exe, and not the child
# process and will just orphan it. Does not escape ^ (cmd's escape symbol).
# Related to joyent/node#2318
if process.platform is "win32"
# Quote all arguments and escapes inner quotes
if args?
cmdArgs = args.filter (arg) -> arg?
cmdArgs = cmdArgs.map (arg) ->
cmdArgs = args.map (arg) ->
if command in ['explorer.exe', 'explorer'] and /^\/[a-zA-Z]+,.*$/.test(arg)
# Don't wrap /root,C:\folder style arguments to explorer calls in
# quotes since they will not be interpreted correctly if they are
arg
else
"\"#{arg.toString().replace(/"/g, '\\"')}\""
"\"#{arg.replace(/"/g, '\\"')}\""
else
cmdArgs = []
if /\s/.test(command)
cmdArgs.unshift("\"#{command}\"")
else
cmdArgs.unshift(command)
cmdArgs.unshift("\"#{command}\"")
cmdArgs = ['/s', '/c', "\"#{cmdArgs.join(' ')}\""]
cmdOptions = _.clone(options)
cmdOptions.windowsVerbatimArguments = true
@@ -96,9 +94,9 @@ class BufferedProcess
# Helper method to pass data line by line.
#
# * `stream` The Stream to read from.
# * `onLines` The callback to call with each line of data.
# * `onDone` The callback to call when the stream has closed.
# stream - The Stream to read from.
# onLines - The callback to call with each line of data.
# onDone - The callback to call when the stream has closed.
bufferStream: (stream, onLines, onDone) ->
stream.setEncoding('utf8')
buffered = ''
@@ -116,48 +114,8 @@ class BufferedProcess
onLines(buffered) if buffered.length > 0
onDone()
# Kill all child processes of the spawned cmd.exe process on Windows.
#
# This is required since killing the cmd.exe does not terminate child
# processes.
killOnWindows: ->
parentPid = @process.pid
cmd = 'wmic'
args = [
'process'
'where'
"(ParentProcessId=#{parentPid})"
'get'
'processid'
]
wmicProcess = ChildProcess.spawn(cmd, args)
wmicProcess.on 'error', -> # ignore errors
output = ''
wmicProcess.stdout.on 'data', (data) -> output += data
wmicProcess.stdout.on 'close', =>
pidsToKill = output.split(/\s+/)
.filter (pid) -> /^\d+$/.test(pid)
.map (pid) -> parseInt(pid)
.filter (pid) -> pid isnt parentPid and 0 < pid < Infinity
for pid in pidsToKill
try
process.kill(pid)
@killProcess()
killProcess: ->
@process?.kill()
@process = null
# Public: Terminate the process.
kill: ->
return if @killed
@killed = true
if process.platform is 'win32'
@killOnWindows()
else
@killProcess()
undefined
@process.kill()
@process = null
+6 -14
Ver Arquivo
@@ -1,17 +1,9 @@
clipboard = require 'clipboard'
crypto = require 'crypto'
# Extended: Represents the clipboard used for copying and pasting in Atom.
# Public: Represents the clipboard used for copying and pasting in Atom.
#
# An instance of this class is always available as the `atom.clipboard` global.
#
# ## Examples
#
# ```coffee
# atom.clipboard.write('hello')
#
# console.log(atom.clipboard.read()) # 'hello'
# ```
module.exports =
class Clipboard
metadata: null
@@ -19,7 +11,7 @@ class Clipboard
# Creates an `md5` hash of some text.
#
# * `text` A {String} to hash.
# text - A {String} to hash.
#
# Returns a hashed {String}.
md5: (text) ->
@@ -30,8 +22,8 @@ class Clipboard
# The metadata associated with the text is available by calling
# {::readWithMetadata}.
#
# * `text` The {String} to store.
# * `metadata` The additional info to associate with the text.
# text - The {String} to store.
# metadata - The additional info to associate with the text.
write: (text, metadata) ->
@signatureForMetadata = @md5(text)
@metadata = metadata
@@ -47,8 +39,8 @@ class Clipboard
# associated metadata.
#
# Returns an {Object} with the following keys:
# * `text` The {String} clipboard text.
# * `metadata` The metadata stored by an earlier call to {::write}.
# :text - The {String} clipboard text.
# :metadata - The metadata stored by an earlier call to {::write}.
readWithMetadata: ->
text = @read()
if @signatureForMetadata is @md5(text)
+1 -1
Ver Arquivo
@@ -41,7 +41,7 @@ module.exports =
callback()
return
symlinkCommand commandPath, destinationPath, (error) ->
symlinkCommand commandPath, destinationPath, (error) =>
if askForPrivilege and error?.code is 'EACCES'
try
error = null
-176
Ver Arquivo
@@ -1,176 +0,0 @@
{Disposable, CompositeDisposable} = require 'event-kit'
{specificity} = require 'clear-cut'
_ = require 'underscore-plus'
{$} = require './space-pen-extensions'
SequenceCount = 0
SpecificityCache = {}
module.exports =
# Experimental: Associates listener functions with commands in a
# context-sensitive way using CSS selectors. You can access a global instance of
# this class via `atom.commands`, and commands registered there will be
# presented in the command palette.
#
# The global command registry facilitates a style of event handling known as
# *event delegation* that was popularized by jQuery. Atom commands are expressed
# as custom DOM events that can be invoked on the currently focused element via
# a key binding or manually via the command palette. Rather than binding
# listeners for command events directly to DOM nodes, you instead register
# command event listeners globally on `atom.commands` and constrain them to
# specific kinds of elements with CSS selectors.
#
# As the event bubbles upward through the DOM, all registered event listeners
# with matching selectors are invoked in order of specificity. In the event of a
# specificity tie, the most recently registered listener is invoked first. This
# mirrors the "cascade" semantics of CSS. Event listeners are invoked in the
# context of the current DOM node, meaning `this` always points at
# `event.currentTarget`. As is normally the case with DOM events,
# `stopPropagation` and `stopImmediatePropagation` can be used to terminate the
# bubbling process and prevent invocation of additional listeners.
#
# ## Example
#
# Here is a command that inserts the current date in an editor:
#
# ```coffee
# atom.commands.add '.editor',
# 'user:insert-date': (event) ->
# editor = $(this).view().getModel()
# # soon the above above line will be:
# # editor = @getModel()
# editor.insertText(new Date().toLocaleString())
# ```
class CommandRegistry
constructor: (@rootNode) ->
@listenersByCommandName = {}
setRootNode: (newRootNode) ->
oldRootNode = @rootNode
@rootNode = newRootNode
for commandName of @listenersByCommandName
oldRootNode?.removeEventListener(commandName, @dispatchCommand, true)
newRootNode?.addEventListener(commandName, @dispatchCommand, true)
# Public: Add one or more command listeners associated with a selector.
#
# ## Arguments: Registering One Command
#
# * `selector` A {String} containing a CSS selector matching elements on which
# you want to handle the commands. The `,` combinator is not currently
# supported.
# * `commandName` A {String} containing the name of a command you want to
# handle such as `user:insert-date`.
# * `callback` A {Function} to call when the given command is invoked on an
# element matching the selector. It will be called with `this` referencing
# the matching DOM node.
# * `event` A standard DOM event instance. Call `stopPropagation` or
# `stopImmediatePropagation` to terminate bubbling early.
#
# ## Arguments: Registering Multiple Commands
#
# * `selector` A {String} containing a CSS selector matching elements on which
# you want to handle the commands. The `,` combinator is not currently
# supported.
# * `commands` An {Object} mapping command names like `user:insert-date` to
# listener {Function}s.
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# added command handler(s).
add: (selector, commandName, callback) ->
if typeof commandName is 'object'
commands = commandName
disposable = new CompositeDisposable
for commandName, callback of commands
disposable.add @add(selector, commandName, callback)
return disposable
unless @listenersByCommandName[commandName]?
@rootNode?.addEventListener(commandName, @dispatchCommand, true)
@listenersByCommandName[commandName] = []
listener = new CommandListener(selector, callback)
listenersForCommand = @listenersByCommandName[commandName]
listenersForCommand.push(listener)
new Disposable =>
listenersForCommand.splice(listenersForCommand.indexOf(listener), 1)
if listenersForCommand.length is 0
delete @listenersByCommandName[commandName]
@rootNode.removeEventListener(commandName, @dispatchCommand, true)
dispatchCommand: (event) =>
propagationStopped = false
immediatePropagationStopped = false
currentTarget = event.target
syntheticEvent = Object.create event,
eventPhase: value: Event.BUBBLING_PHASE
currentTarget: get: -> currentTarget
stopPropagation: value: ->
propagationStopped = true
stopImmediatePropagation: value: ->
propagationStopped = true
immediatePropagationStopped = true
loop
matchingListeners =
@listenersByCommandName[event.type]
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
.sort (a, b) -> a.compare(b)
for listener in matchingListeners
break if immediatePropagationStopped
listener.callback.call(currentTarget, syntheticEvent)
break if propagationStopped
break if currentTarget is @rootNode
currentTarget = currentTarget.parentNode
# Public: Find all registered commands matching a query.
#
# * `params` An {Object} containing one or more of the following keys:
# * `target` A DOM node that is the hypothetical target of a given command.
#
# Returns an {Array} of {Object}s containing the following keys:
# * `name` The name of the command. For example, `user:insert-date`.
# * `displayName` The display name of the command. For example,
# `User: Insert Date`.
# * `jQuery` Present if the command was registered with the legacy
# `$::command` method.
findCommands: ({target}) ->
commands = []
target = @rootNode unless @rootNode.contains(target)
currentTarget = target
loop
for commandName, listeners of @listenersByCommandName
for listener in listeners
if currentTarget.webkitMatchesSelector(listener.selector)
commands.push
name: commandName
displayName: _.humanizeEventName(commandName)
break if currentTarget is @rootNode
currentTarget = currentTarget.parentNode
for name, displayName of $(target).events() when displayName
commands.push({name, displayName, jQuery: true})
for name, displayName of $(window).events() when displayName
commands.push({name, displayName, jQuery: true})
commands
clear: ->
@listenersByCommandName = {}
class CommandListener
constructor: (@selector, @callback) ->
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))
@sequenceNumber = SequenceCount++
compare: (other) ->
other.specificity - @specificity or
other.sequenceNumber - @sequenceNumber
+47 -55
Ver Arquivo
@@ -6,7 +6,7 @@ path = require 'path'
async = require 'async'
pathWatcher = require 'pathwatcher'
# Essential: Used to access all of Atom's configuration details.
# Public: Used to access all of Atom's configuration details.
#
# An instance of this class is always available as the `atom.config` global.
#
@@ -15,9 +15,9 @@ pathWatcher = require 'pathwatcher'
# * Create your own root keypath using your package's name.
# * Don't depend on (or write to) configuration keys outside of your keypath.
#
# ## Examples
# ## Example
#
# ```coffee
# ```coffeescript
# atom.config.set('my-package.key', 'value')
# atom.config.observe 'my-package.key', ->
# console.log 'My configuration changed:', atom.config.get('my-package.key')
@@ -39,7 +39,7 @@ class Config
fs.makeTreeSync(@configDirPath)
queue = async.queue ({sourcePath, destinationPath}, callback) ->
queue = async.queue ({sourcePath, destinationPath}, callback) =>
fs.copy(sourcePath, destinationPath, callback)
queue.drain = done
@@ -88,48 +88,37 @@ class Config
_.extend hash, defaults
@emit 'updated'
# Extended: Get the {String} path to the config file being used.
# Public: Get the {String} path to the config file being used.
getUserConfigPath: ->
@configFilePath
# Extended: Returns a new {Object} containing all of settings and defaults.
# Public: Returns a new {Object} containing all of settings and defaults.
getSettings: ->
_.deepExtend(@settings, @defaultSettings)
# Essential: Retrieves the setting for the given key.
# Public: Retrieves the setting for the given key.
#
# * `keyPath` The {String} name of the key to retrieve.
# keyPath - The {String} name of the key to retrieve.
#
# Returns the value from Atom's default settings, the user's configuration
# file, or `null` if the key doesn't exist in either.
get: (keyPath) ->
value = _.valueForKeyPath(@settings, keyPath)
defaultValue = _.valueForKeyPath(@defaultSettings, keyPath)
value = _.valueForKeyPath(@settings, keyPath) ? _.valueForKeyPath(@defaultSettings, keyPath)
_.deepClone(value)
if value?
value = _.deepClone(value)
valueIsObject = _.isObject(value) and not _.isArray(value)
defaultValueIsObject = _.isObject(defaultValue) and not _.isArray(defaultValue)
if valueIsObject and defaultValueIsObject
_.defaults(value, defaultValue)
else
value = _.deepClone(defaultValue)
value
# Extended: Retrieves the setting for the given key as an integer.
# Public: Retrieves the setting for the given key as an integer.
#
# * `keyPath` The {String} name of the key to retrieve
# keyPath - The {String} name of the key to retrieve
#
# Returns the value from Atom's default settings, the user's configuration
# file, or `NaN` if the key doesn't exist in either.
getInt: (keyPath) ->
parseInt(@get(keyPath))
# Extended: Retrieves the setting for the given key as a positive integer.
# Public: Retrieves the setting for the given key as a positive integer.
#
# * `keyPath` The {String} name of the key to retrieve
# * `defaultValue` The integer {Number} to fall back to if the value isn't
# keyPath - The {String} name of the key to retrieve
# defaultValue - The integer {Number} to fall back to if the value isn't
# positive, defaults to 0.
#
# Returns the value from Atom's default settings, the user's configuration
@@ -137,12 +126,12 @@ class Config
getPositiveInt: (keyPath, defaultValue=0) ->
Math.max(@getInt(keyPath), 0) or defaultValue
# Essential: Sets the value for a configuration setting.
# Public: Sets the value for a configuration setting.
#
# This value is stored in Atom's internal configuration file.
#
# * `keyPath` The {String} name of the key.
# * `value` The value of the setting.
# keyPath - The {String} name of the key.
# value - The value of the setting.
#
# Returns the `value`.
set: (keyPath, value) ->
@@ -153,47 +142,47 @@ class Config
@update()
value
# Extended: Toggle the value at the key path.
# Public: Toggle the value at the key path.
#
# The new value will be `true` if the value is currently falsy and will be
# `false` if the value is currently truthy.
#
# * `keyPath` The {String} name of the key.
# keyPath - The {String} name of the key.
#
# Returns the new value.
toggle: (keyPath) ->
@set(keyPath, !@get(keyPath))
# Extended: Restore the key path to its default value.
# Public: Restore the key path to its default value.
#
# * `keyPath` The {String} name of the key.
# keyPath - The {String} name of the key.
#
# Returns the new value.
restoreDefault: (keyPath) ->
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
# Extended: Get the default value of the key path.
# Public: Get the default value of the key path.
#
# * `keyPath` The {String} name of the key.
# keyPath - The {String} name of the key.
#
# Returns the default value.
getDefault: (keyPath) ->
defaultValue = _.valueForKeyPath(@defaultSettings, keyPath)
_.deepClone(defaultValue)
# Extended: Is the key path value its default value?
# Public: Is the key path value its default value?
#
# * `keyPath` The {String} name of the key.
# keyPath - The {String} name of the key.
#
# Returns a {Boolean}, `true` if the current value is the default, `false`
# otherwise.
isDefault: (keyPath) ->
not _.valueForKeyPath(@settings, keyPath)?
# Extended: Push the value to the array at the key path.
# Public: Push the value to the array at the key path.
#
# * `keyPath` The {String} key path.
# * `value` The value to push to the array.
# keyPath - The {String} key path.
# value - The value to push to the array.
#
# Returns the new array length {Number} of the setting.
pushAtKeyPath: (keyPath, value) ->
@@ -202,10 +191,10 @@ class Config
@set(keyPath, arrayValue)
result
# Extended: Add the value to the beginning of the array at the key path.
# Public: Add the value to the beginning of the array at the key path.
#
# * `keyPath` The {String} key path.
# * `value` The value to shift onto the array.
# keyPath - The {String} key path.
# value - The value to shift onto the array.
#
# Returns the new array length {Number} of the setting.
unshiftAtKeyPath: (keyPath, value) ->
@@ -216,8 +205,8 @@ class Config
# Public: Remove the value from the array at the key path.
#
# * `keyPath` The {String} key path.
# * `value` The value to remove from the array.
# keyPath - The {String} key path.
# value - The value to remove from the array.
#
# Returns the new array value of the setting.
removeAtKeyPath: (keyPath, value) ->
@@ -226,17 +215,20 @@ class Config
@set(keyPath, arrayValue)
result
# Essential: Add a listener for changes to a given key path.
# Public: Establishes an event listener for a given key.
#
# * `keyPath` The {String} name of the key to observe
# * `options` An optional {Object} containing the `callNow` key.
# * `callback` The {Function} to call when the value of the key changes.
# The first argument will be the new value of the key and the
#   second argument will be an {Object} with a `previous` property
# that is the prior value of the key.
# `callback` is fired whenever the value of the key is changed and will
# be fired immediately unless the `callNow` option is `false`.
#
# keyPath - The {String} name of the key to observe
# options - An optional {Object} containing the `callNow` key.
# callback - The {Function} to call when the value of the key changes.
# The first argument will be the new value of the key and the
#   second argument will be an {Object} with a `previous` property
# that is the prior value of the key.
#
# Returns an {Object} with the following keys:
# * `off` A {Function} that unobserves the `keyPath` when called.
# :off - A {Function} that unobserves the `keyPath` when called.
observe: (keyPath, options={}, callback) ->
if _.isFunction(options)
callback = options
@@ -256,9 +248,9 @@ class Config
callback(value) if options.callNow ? true
subscription
# Unobserve all callbacks on a given key.
# Public: Unobserve all callbacks on a given key.
#
# * `keyPath` The {String} name of the key to unobserve.
# keyPath - The {String} name of the key to unobserve.
unobserve: (keyPath) ->
@off("updated.#{keyPath.replace(/\./, '-')}")
+17 -31
Ver Arquivo
@@ -1,18 +1,15 @@
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
remote = require 'remote'
path = require 'path'
CSON = require 'season'
fs = require 'fs-plus'
# Extended: Provides a registry for commands that you'd like to appear in the
# Public: Provides a registry for commands that you'd like to appear in the
# context menu.
#
# An instance of this class is always available as the `atom.contextMenu`
# global.
module.exports =
class ContextMenuManager
constructor: ({@resourcePath, @devMode}) ->
constructor: (@devMode=false) ->
@definitions = {}
@devModeDefinitions = {}
@activeElement = null
@@ -24,22 +21,16 @@ class ContextMenuManager
@commandOptions = x: e.pageX, y: e.pageY
]
atom.keymaps.onDidLoadBundledKeymaps => @loadPlatformItems()
loadPlatformItems: ->
menusDirPath = path.join(@resourcePath, 'menus')
platformMenuPath = fs.resolve(menusDirPath, process.platform, ['cson', 'json'])
map = CSON.readFileSync(platformMenuPath)
atom.contextMenu.add(platformMenuPath, map['context-menu'])
# Public: Creates menu definitions from the object specified by the menu
# JSON API.
# cson API.
#
# * `name` The path of the file that contains the menu definitions.
# * `object` The 'context-menu' object specified in the menu JSON API.
# * `options` An optional {Object} with the following keys:
# * `devMode` Determines whether the entries should only be shown when
# the window is in dev mode.
# name - The path of the file that contains the menu definitions.
# object - The 'context-menu' object specified in the menu cson API.
# options - An {Object} with the following keys:
# :devMode - Determines whether the entries should only be shown when
# the window is in dev mode.
#
# Returns nothing.
add: (name, object, {devMode}={}) ->
for selector, items of object
for label, commandOrSubmenu of items
@@ -52,8 +43,6 @@ class ContextMenuManager
menuItem = @buildMenuItem(label, commandOrSubmenu)
@addBySelector(selector, menuItem, {devMode})
undefined
buildMenuItem: (label, command) ->
if command is '-'
{type: 'separator'}
@@ -63,12 +52,12 @@ class ContextMenuManager
# Registers a command to be displayed when the relevant item is right
# clicked.
#
# * `selector` The css selector for the active element which should include
# the given command in its context menu.
# * `definition` The object containing keys which match the menu template API.
# * `options` An optional {Object} with the following keys:
# * `devMode` Indicates whether this command should only appear while the
# editor is in dev mode.
# selector - The css selector for the active element which should include
# the given command in its context menu.
# definition - The object containing keys which match the menu template API.
# options - An {Object} with the following keys:
# :devMode - Indicates whether this command should only appear while the
# editor is in dev mode.
addBySelector: (selector, definition, {devMode}={}) ->
definitions = if devMode then @devModeDefinitions else @definitions
if not _.findWhere(definitions[selector], definition) or _.isEqual(definition, {type: 'separator'})
@@ -90,7 +79,7 @@ class ContextMenuManager
# active element are listed first. The further down the list you go, the higher
# up the ancestor hierarchy they match.
#
# * `element` The DOM element to generate the menu template for.
# element - The DOM element to generate the menu template for.
menuTemplateForMostSpecificElement: (element, {devMode}={}) ->
menuTemplate = @definitionsForElement(element, {devMode})
if element.parentElement
@@ -120,12 +109,9 @@ class ContextMenuManager
delete template.executeAtBuild
# Public: Request a context menu to be displayed.
#
# * `event` A DOM event.
showForEvent: (event) ->
@activeElement = event.target
menuTemplate = @combinedMenuTemplateForElement(event.target)
return unless menuTemplate?.length > 0
@executeBuildHandlers(event, menuTemplate)
remote.getCurrentWindow().emit('context-menu', menuTemplate)
undefined
+113
Ver Arquivo
@@ -0,0 +1,113 @@
{View} = require './space-pen-extensions'
_ = require 'underscore-plus'
module.exports =
class CursorView extends View
@content: ->
@div class: 'cursor idle', => @raw '&nbsp;'
@blinkPeriod: 800
@blinkCursors: ->
element.classList.toggle('blink-off') for [element] in @cursorViews
@startBlinking: (cursorView) ->
@cursorViews ?= []
@cursorViews.push(cursorView)
if @cursorViews.length is 1
@blinkInterval = setInterval(@blinkCursors.bind(this), @blinkPeriod / 2)
@stopBlinking: (cursorView) ->
cursorView[0].classList.remove('blink-off')
_.remove(@cursorViews, cursorView)
clearInterval(@blinkInterval) if @cursorViews.length is 0
blinking: false
visible: true
needsUpdate: true
needsRemoval: false
shouldPauseBlinking: false
initialize: (@cursor, @editorView) ->
@subscribe @cursor, 'moved', =>
@needsUpdate = true
@shouldPauseBlinking = true
@subscribe @cursor, 'visibility-changed', =>
@needsUpdate = true
@subscribe @cursor, 'autoscrolled', =>
@editorView.requestDisplayUpdate()
@subscribe @cursor, 'destroyed', =>
@needsRemoval = true
beforeRemove: ->
@editorView.removeCursorView(this)
@stopBlinking()
updateDisplay: ->
screenPosition = @getScreenPosition()
pixelPosition = @getPixelPosition()
unless _.isEqual(@lastPixelPosition, pixelPosition)
@lastPixelPosition = pixelPosition
@css(pixelPosition)
@trigger 'cursor:moved'
if @shouldPauseBlinking
@resetBlinking()
else if !@startBlinkingTimeout
@startBlinking()
@setVisible(@cursor.isVisible() and not @editorView.getEditor().isFoldedAtScreenRow(screenPosition.row))
# Override for speed. The base function checks the computedStyle
isHidden: ->
this[0].style.display is 'none' or not @isOnDom()
needsAutoscroll: ->
@cursor.needsAutoscroll
clearAutoscroll: ->
@cursor.clearAutoscroll()
getPixelPosition: ->
@editorView.pixelPositionForScreenPosition(@getScreenPosition())
setVisible: (visible) ->
unless @visible is visible
@visible = visible
hiddenCursor = 'hidden-cursor'
if visible
@removeClass hiddenCursor
else
@addClass hiddenCursor
stopBlinking: ->
@constructor.stopBlinking(this) if @blinking
@blinking = false
startBlinking: ->
@constructor.startBlinking(this) unless @blinking
@blinking = true
resetBlinking: ->
@stopBlinking()
@startBlinking()
getBufferPosition: ->
@cursor.getBufferPosition()
getScreenPosition: ->
@cursor.getScreenPosition()
removeIdleClassTemporarily: ->
@removeClass 'idle'
window.clearTimeout(@idleTimeout) if @idleTimeout
@idleTimeout = window.setTimeout (=> @addClass 'idle'), 200
resetCursorAnimation: ->
window.clearTimeout(@idleTimeout) if @idleTimeout
@removeClass 'idle'
_.defer => @addClass 'idle'
+203 -336
Ver Arquivo
@@ -1,10 +1,8 @@
{Point, Range} = require 'text-buffer'
{Model} = require 'theorist'
{Emitter} = require 'event-kit'
_ = require 'underscore-plus'
Grim = require 'grim'
# Extended: The `Cursor` class represents the little blinking line identifying
# Public: The `Cursor` class represents the little blinking line identifying
# where text can be inserted.
#
# Cursors belong to {Editor}s and have some metadata attached in the form
@@ -19,11 +17,9 @@ class Cursor extends Model
# Instantiated by an {Editor}
constructor: ({@editor, @marker, id}) ->
@emitter = new Emitter
@assignId(id)
@updateVisibility()
@marker.onDidChange (e) =>
@marker.on 'changed', (e) =>
@updateVisibility()
{oldHeadScreenPosition, newHeadScreenPosition} = e
{oldHeadBufferPosition, newHeadBufferPosition} = e
@@ -42,80 +38,36 @@ class Cursor extends Model
newBufferPosition: newHeadBufferPosition
newScreenPosition: newHeadScreenPosition
textChanged: textChanged
cursor: this
@emit 'moved', movedEvent
@emitter.emit 'did-change-position'
@editor.cursorMoved(movedEvent)
@marker.onDidDestroy =>
@marker.on 'destroyed', =>
@destroyed = true
@editor.removeCursor(this)
@emit 'destroyed'
@emitter.emit 'did-destroy'
@emitter.dispose()
@needsAutoscroll = true
destroy: ->
@marker.destroy()
###
Section: Event Subscription
###
changePosition: (options, fn) ->
@clearSelection()
@needsAutoscroll = options.autoscroll ? @isLastCursor()
fn()
if @needsAutoscroll
@emit 'autoscrolled' # Support legacy editor
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view
# Public: Calls your `callback` when the cursor has been moved.
#
# * `callback` {Function}
# * `event` {Object}
# * `oldBufferPosition` {Point}
# * `oldScreenPosition` {Point}
# * `newBufferPosition` {Point}
# * `newScreenPosition` {Point}
# * `textChanged` {Boolean}
# * `Cursor` {Cursor} that triggered the event
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangePosition: (callback) ->
@emitter.on 'did-change-position', callback
# Public: Calls your `callback` when the cursor is destroyed
#
# * `callback` {Function}
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidDestroy: (callback) ->
@emitter.on 'did-destroy', callback
# Public: Calls your `callback` when the cursor's visibility has changed
#
# * `callback` {Function}
# * `visibility` {Boolean}
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeVisibility: (callback) ->
@emitter.on 'did-change-visibility', callback
on: (eventName) ->
switch eventName
when 'moved'
Grim.deprecate("Use Cursor::onDidChangePosition instead")
when 'destroyed'
Grim.deprecate("Use Cursor::onDidDestroy instead")
when 'destroyed'
Grim.deprecate("Use Cursor::onDidDestroy instead")
else
Grim.deprecate("::on is no longer supported. Use the event subscription methods instead")
super
###
Section: Managing Cursor Position
###
getPixelRect: ->
@editor.pixelRectForScreenRange(@getScreenRange())
# Public: Moves a cursor to a given screen position.
#
# * `screenPosition` {Array} of two numbers: the screen row, and the screen column.
# * `options` (optional) {Object} with the following keys:
# * `autoscroll` A Boolean which, if `true`, scrolls the {Editor} to wherever
# the cursor moves to.
# screenPosition - An {Array} of two numbers: the screen row, and the screen
# column.
# options - An {Object} with the following keys:
# :autoscroll - A Boolean which, if `true`, scrolls the {Editor} to wherever
# the cursor moves to.
setScreenPosition: (screenPosition, options={}) ->
@changePosition options, =>
@marker.setHeadScreenPosition(screenPosition, options)
@@ -124,12 +76,17 @@ class Cursor extends Model
getScreenPosition: ->
@marker.getHeadScreenPosition()
getScreenRange: ->
{row, column} = @getScreenPosition()
new Range(new Point(row, column), new Point(row, column + 1))
# Public: Moves a cursor to a given buffer position.
#
# * `bufferPosition` {Array} of two numbers: the buffer row, and the buffer column.
# * `options` (optional) {Object} with the following keys:
# * `autoscroll` A Boolean which, if `true`, scrolls the {Editor} to wherever
# the cursor moves to.
# bufferPosition - An {Array} of two numbers: the buffer row, and the buffer
# column.
# options - An {Object} with the following keys:
# :autoscroll - A Boolean which, if `true`, scrolls the {Editor} to wherever
# the cursor moves to.
setBufferPosition: (bufferPosition, options={}) ->
@changePosition options, =>
@marker.setHeadBufferPosition(bufferPosition, options)
@@ -138,38 +95,47 @@ class Cursor extends Model
getBufferPosition: ->
@marker.getHeadBufferPosition()
# Public: Returns the cursor's current screen row.
getScreenRow: ->
@getScreenPosition().row
autoscroll: ->
@editor.scrollToScreenRange(@getScreenRange())
# Public: Returns the cursor's current screen column.
getScreenColumn: ->
@getScreenPosition().column
# Public: If the marker range is empty, the cursor is marked as being visible.
updateVisibility: ->
@setVisible(@marker.getBufferRange().isEmpty())
# Public: Retrieves the cursor's current buffer row.
getBufferRow: ->
@getBufferPosition().row
# Public: Sets whether the cursor is visible.
setVisible: (visible) ->
if @visible != visible
@visible = visible
@needsAutoscroll ?= true if @visible and @isLastCursor()
@emit 'visibility-changed', @visible
# Public: Returns the cursor's current buffer column.
getBufferColumn: ->
@getBufferPosition().column
# Public: Returns the visibility of the cursor.
isVisible: -> @visible
# Public: Returns the cursor's current buffer row of text excluding its line
# ending.
getCurrentBufferLine: ->
@editor.lineTextForBufferRow(@getBufferRow())
# Public: Get the RegExp used by the cursor to determine what a "word" is.
#
# options: An optional {Object} with the following keys:
# :includeNonWordCharacters - A {Boolean} indicating whether to include
# non-word characters in the regex.
# (default: true)
#
# Returns a {RegExp}.
wordRegExp: ({includeNonWordCharacters}={})->
includeNonWordCharacters ?= true
nonWordCharacters = atom.config.get('editor.nonWordCharacters')
segments = ["^[\t ]*$"]
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
if includeNonWordCharacters
segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
new RegExp(segments.join("|"), "g")
# Public: Returns whether the cursor is at the start of a line.
isAtBeginningOfLine: ->
@getBufferPosition().column == 0
# Public: Returns whether the cursor is on the line return character.
isAtEndOfLine: ->
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
###
Section: Cursor Position Details
###
# Public: Identifies if this cursor is the last in the {Editor}.
#
# "Last" is defined as the most recently added cursor.
#
# Returns a {Boolean}.
isLastCursor: ->
this == @editor.getCursor()
# Public: Identifies if the cursor is surrounded by whitespace.
#
@@ -207,50 +173,37 @@ class Cursor extends Model
range = [[row, column], [row, Infinity]]
@editor.getTextInBufferRange(range).search(@wordRegExp()) == 0
# Public: Returns the indentation level of the current line.
getIndentLevel: ->
if @editor.getSoftTabs()
@getBufferColumn() / @editor.getTabLength()
else
@getBufferColumn()
# Public: Prevents this cursor from causing scrolling.
clearAutoscroll: ->
@needsAutoscroll = null
# Public: Retrieves the grammar's token scopes for the line.
#
# Returns an {Array} of {String}s.
getScopes: ->
@editor.scopesForBufferPosition(@getBufferPosition())
# Public: Deselects the current selection.
clearSelection: ->
@selection?.clear()
# Public: Returns true if this cursor has no non-whitespace characters before
# its current position.
hasPrecedingCharactersOnLine: ->
bufferPosition = @getBufferPosition()
line = @editor.lineTextForBufferRow(bufferPosition.row)
firstCharacterColumn = line.search(/\S/)
# Public: Returns the cursor's current screen row.
getScreenRow: ->
@getScreenPosition().row
if firstCharacterColumn is -1
false
else
bufferPosition.column > firstCharacterColumn
# Public: Returns the cursor's current screen column.
getScreenColumn: ->
@getScreenPosition().column
# Public: Identifies if this cursor is the last in the {Editor}.
#
# "Last" is defined as the most recently added cursor.
#
# Returns a {Boolean}.
isLastCursor: ->
this == @editor.getLastCursor()
# Public: Retrieves the cursor's current buffer row.
getBufferRow: ->
@getBufferPosition().row
###
Section: Moving the Cursor
###
# Public: Returns the cursor's current buffer column.
getBufferColumn: ->
@getBufferPosition().column
# Public: Returns the cursor's current buffer row of text excluding its line
# ending.
getCurrentBufferLine: ->
@editor.lineForBufferRow(@getBufferRow())
# Public: Moves the cursor up one screen row.
#
# * `rowCount` (optional) {Number} number of rows to move (default: 1)
# * `options` (optional) {Object} with the following keys:
# * `moveToEndOfSelection` if true, move to the left of the selection if a
# selection exists.
moveUp: (rowCount=1, {moveToEndOfSelection}={}) ->
moveUp: (rowCount = 1, {moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
{ row, column } = range.start
@@ -262,12 +215,7 @@ class Cursor extends Model
@goalColumn = column
# Public: Moves the cursor down one screen row.
#
# * `rowCount` (optional) {Number} number of rows to move (default: 1)
# * `options` (optional) {Object} with the following keys:
# * `moveToEndOfSelection` if true, move to the left of the selection if a
# selection exists.
moveDown: (rowCount=1, {moveToEndOfSelection}={}) ->
moveDown: (rowCount = 1, {moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
{ row, column } = range.end
@@ -280,51 +228,30 @@ class Cursor extends Model
# Public: Moves the cursor left one screen column.
#
# * `columnCount` (optional) {Number} number of columns to move (default: 1)
# * `options` (optional) {Object} with the following keys:
# * `moveToEndOfSelection` if true, move to the left of the selection if a
# selection exists.
moveLeft: (columnCount=1, {moveToEndOfSelection}={}) ->
# options - An {Object} with the following keys:
# :moveToEndOfSelection - if true, move to the left of the selection if a
# selection exists.
moveLeft: ({moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
@setScreenPosition(range.start)
else
{row, column} = @getScreenPosition()
while columnCount > column and row > 0
columnCount -= column
column = @editor.lineTextForScreenRow(--row).length
columnCount-- # subtract 1 for the row move
column = column - columnCount
[row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity]
@setScreenPosition({row, column})
# Public: Moves the cursor right one screen column.
#
# * `columnCount` (optional) {Number} number of columns to move (default: 1)
# * `options` (optional) {Object} with the following keys:
# * `moveToEndOfSelection` if true, move to the right of the selection if a
# selection exists.
moveRight: (columnCount=1, {moveToEndOfSelection}={}) ->
# options - An {Object} with the following keys:
# :moveToEndOfSelection - if true, move to the right of the selection if a
# selection exists.
moveRight: ({moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
@setScreenPosition(range.end)
else
{ row, column } = @getScreenPosition()
maxLines = @editor.getScreenLineCount()
rowLength = @editor.lineTextForScreenRow(row).length
columnsRemainingInLine = rowLength - column
while columnCount > columnsRemainingInLine and row < maxLines - 1
columnCount -= columnsRemainingInLine
columnCount-- # subtract 1 for the row move
column = 0
rowLength = @editor.lineTextForScreenRow(++row).length
columnsRemainingInLine = rowLength
column = column + columnCount
@setScreenPosition({row, column}, skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
@setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
# Public: Moves the cursor to the top of the buffer.
moveToTop: ->
@@ -345,20 +272,23 @@ class Cursor extends Model
# Public: Moves the cursor to the beginning of the first character in the
# line.
moveToFirstCharacterOfLine: ->
screenRow = @getScreenRow()
lineBufferRange = @editor.bufferRangeForScreenRange([[screenRow, 0], [screenRow, Infinity]])
{row, column} = @getScreenPosition()
screenline = @editor.lineForScreenRow(row)
firstCharacterColumn = null
@editor.scanInBufferRange /\S/, lineBufferRange, ({range, stop}) ->
firstCharacterColumn = range.start.column
stop()
goalColumn = screenline.text.search(/\S/)
goalColumn = 0 if goalColumn == column or goalColumn == -1
@setScreenPosition([row, goalColumn])
if firstCharacterColumn? and firstCharacterColumn isnt @getBufferColumn()
targetBufferColumn = firstCharacterColumn
else
targetBufferColumn = lineBufferRange.start.column
# Public: Moves the cursor to the beginning of the buffer line, skipping all
# whitespace.
skipLeadingWhitespace: ->
position = @getBufferPosition()
scanRange = @getCurrentLineBufferRange()
endOfLeadingWhitespace = null
@editor.scanInBufferRange /^[ \t]*/, scanRange, ({range}) =>
endOfLeadingWhitespace = range.end
@setBufferPosition([lineBufferRange.start.row, targetBufferColumn])
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
# Public: Moves the cursor to the end of the line.
moveToEndOfScreenLine: ->
@@ -392,41 +322,16 @@ class Cursor extends Model
if position = @getMoveNextWordBoundaryBufferPosition()
@setBufferPosition(position)
# Public: Moves the cursor to the beginning of the buffer line, skipping all
# whitespace.
skipLeadingWhitespace: ->
position = @getBufferPosition()
scanRange = @getCurrentLineBufferRange()
endOfLeadingWhitespace = null
@editor.scanInBufferRange /^[ \t]*/, scanRange, ({range}) ->
endOfLeadingWhitespace = range.end
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
# Public: Moves the cursor to the beginning of the next paragraph
moveToBeginningOfNextParagraph: ->
if position = @getBeginningOfNextParagraphBufferPosition()
@setBufferPosition(position)
# Public: Moves the cursor to the beginning of the previous paragraph
moveToBeginningOfPreviousParagraph: ->
if position = @getBeginningOfPreviousParagraphBufferPosition()
@setBufferPosition(position)
###
Section: Local Positions and Ranges
###
# Public: Retrieves the buffer position of where the current word starts.
#
# * `options` (optional) An {Object} with the following keys:
# * `wordRegex` A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp}).
# * `includeNonWordCharacters` A {Boolean} indicating whether to include
# non-word characters in the default word regex.
# Has no effect if wordRegex is set.
# * `allowPrevious` A {Boolean} indicating whether the beginning of the
# previous word can be returned.
# options - An {Object} with the following keys:
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp}).
# :includeNonWordCharacters - A {Boolean} indicating whether to include
# non-word characters in the default word regex.
# Has no effect if wordRegex is set.
# :allowPrevious - A {Boolean} indicating whether the beginning of the
# previous word can be returned.
#
# Returns a {Range}.
getBeginningOfCurrentWordBufferPosition: (options = {}) ->
@@ -436,7 +341,7 @@ class Cursor extends Model
scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
beginningOfWordPosition = null
@editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
@editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) =>
if range.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
beginningOfWordPosition = range.start
if not beginningOfWordPosition?.isEqual(currentBufferPosition)
@@ -457,7 +362,7 @@ class Cursor extends Model
scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
beginningOfWordPosition = null
@editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
@editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) =>
if range.start.row < currentBufferPosition.row and currentBufferPosition.column > 0
# force it to stop at the beginning of each line
beginningOfWordPosition = new Point(currentBufferPosition.row, 0)
@@ -478,7 +383,7 @@ class Cursor extends Model
scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
endOfWordPosition = null
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) =>
if range.start.row > currentBufferPosition.row
# force it to stop at the beginning of each line
endOfWordPosition = new Point(range.start.row, 0)
@@ -494,12 +399,12 @@ class Cursor extends Model
# Public: Retrieves the buffer position of where the current word ends.
#
# * `options` (optional) {Object} with the following keys:
# * `wordRegex` A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp})
# * `includeNonWordCharacters` A Boolean indicating whether to include
# non-word characters in the default word regex. Has no effect if
# wordRegex is set.
# options - An {Object} with the following keys:
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp})
# :includeNonWordCharacters - A Boolean indicating whether to include
# non-word characters in the default word regex.
# Has no effect if wordRegex is set.
#
# Returns a {Range}.
getEndOfCurrentWordBufferPosition: (options = {}) ->
@@ -508,7 +413,7 @@ class Cursor extends Model
scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
endOfWordPosition = null
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) =>
if range.start.isLessThanOrEqual(currentBufferPosition) or allowNext
endOfWordPosition = range.end
if not endOfWordPosition?.isEqual(currentBufferPosition)
@@ -518,18 +423,18 @@ class Cursor extends Model
# Public: Retrieves the buffer position of where the next word starts.
#
# * `options` (optional) {Object}
# * `wordRegex` A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp}).
# options -
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp}).
#
# Returns a {Range}
# Returns a {Range}.
getBeginningOfNextWordBufferPosition: (options = {}) ->
currentBufferPosition = @getBufferPosition()
start = if @isInsideWord() then @getEndOfCurrentWordBufferPosition() else currentBufferPosition
scanRange = [start, @editor.getEofBufferPosition()]
beginningOfNextWordPosition = null
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) ->
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) =>
beginningOfNextWordPosition = range.start
stop()
@@ -537,9 +442,9 @@ class Cursor extends Model
# Public: Returns the buffer Range occupied by the word located under the cursor.
#
# * `options` (optional) {Object}
# * `wordRegex` A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp}).
# options -
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {::wordRegExp}).
getCurrentWordBufferRange: (options={}) ->
startOptions = _.extend(_.clone(options), allowPrevious: false)
endOptions = _.extend(_.clone(options), allowNext: false)
@@ -547,12 +452,49 @@ class Cursor extends Model
# Public: Returns the buffer Range for the current line.
#
# * `options` (optional) {Object}
# * `includeNewline` A {Boolean} which controls whether the Range should
# include the newline.
# options -
# :includeNewline: - A {Boolean} which controls whether the Range should
# include the newline.
getCurrentLineBufferRange: (options) ->
@editor.bufferRangeForBufferRow(@getBufferRow(), options)
# Public: Moves the cursor to the beginning of the next paragraph
moveToBeginningOfNextParagraph: ->
if position = @getBeginningOfNextParagraphBufferPosition()
@setBufferPosition(position)
# Public: Moves the cursor to the beginning of the previous paragraph
moveToBeginningOfPreviousParagraph: ->
if position = @getBeginningOfPreviousParagraphBufferPosition()
@setBufferPosition(position)
getBeginningOfNextParagraphBufferPosition: (editor) ->
start = @getBufferPosition()
eof = @editor.getEofBufferPosition()
scanRange = [start, eof]
{row, column} = eof
position = new Point(row, column - 1)
@editor.scanInBufferRange /^\n*$/g, scanRange, ({range, stop}) =>
if !range.start.isEqual(start)
position = range.start
stop()
@editor.screenPositionForBufferPosition(position)
getBeginningOfPreviousParagraphBufferPosition: (editor) ->
start = @editor.getCursorBufferPosition()
{row, column} = start
scanRange = [[row-1, column], [0,0]]
position = new Point(0, 0)
zero = new Point(0,0)
@editor.backwardsScanInBufferRange /^\n*$/g, scanRange, ({range, stop}) =>
if !range.start.isEqual(zero)
position = range.start
stop()
@editor.screenPositionForBufferPosition(position)
# Public: Retrieves the range for the current paragraph.
#
# A paragraph is defined as a block of text surrounded by empty lines.
@@ -565,110 +507,35 @@ class Cursor extends Model
getCurrentWordPrefix: ->
@editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
###
Section: Visibility
###
# Public: Returns whether the cursor is at the start of a line.
isAtBeginningOfLine: ->
@getBufferPosition().column == 0
# Public: If the marker range is empty, the cursor is marked as being visible.
updateVisibility: ->
@setVisible(@marker.getBufferRange().isEmpty())
# Public: Returns the indentation level of the current line.
getIndentLevel: ->
if @editor.getSoftTabs()
@getBufferColumn() / @editor.getTabLength()
else
@getBufferColumn()
# Public: Sets whether the cursor is visible.
setVisible: (visible) ->
if @visible != visible
@visible = visible
@needsAutoscroll ?= true if @visible and @isLastCursor()
@emit 'visibility-changed', @visible
@emitter.emit 'did-change-visibility', @visible
# Public: Returns whether the cursor is on the line return character.
isAtEndOfLine: ->
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
# Public: Returns the visibility of the cursor.
isVisible: -> @visible
###
Section: Comparing to another cursor
###
# Public: Compare this cursor's buffer position to another cursor's buffer position.
# Public: Retrieves the grammar's token scopes for the line.
#
# See {Point::compare} for more details.
#
# * `otherCursor`{Cursor} to compare against
compare: (otherCursor) ->
@getBufferPosition().compare(otherCursor.getBufferPosition())
# Returns an {Array} of {String}s.
getScopes: ->
@editor.scopesForBufferPosition(@getBufferPosition())
###
Section: Utilities
###
# Public: Returns true if this cursor has no non-whitespace characters before
# its current position.
hasPrecedingCharactersOnLine: ->
bufferPosition = @getBufferPosition()
line = @editor.lineForBufferRow(bufferPosition.row)
firstCharacterColumn = line.search(/\S/)
# Public: Prevents this cursor from causing scrolling.
clearAutoscroll: ->
@needsAutoscroll = null
# Public: Deselects the current selection.
clearSelection: ->
@selection?.clear()
# Public: Get the RegExp used by the cursor to determine what a "word" is.
#
# * `options` (optional) {Object} with the following keys:
# * `includeNonWordCharacters` A {Boolean} indicating whether to include
# non-word characters in the regex. (default: true)
#
# Returns a {RegExp}.
wordRegExp: ({includeNonWordCharacters}={}) ->
includeNonWordCharacters ?= true
nonWordCharacters = atom.config.get('editor.nonWordCharacters')
segments = ["^[\t ]*$"]
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
if includeNonWordCharacters
segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
new RegExp(segments.join("|"), "g")
###
Section: Private
###
changePosition: (options, fn) ->
@clearSelection()
@needsAutoscroll = options.autoscroll ? @isLastCursor()
fn()
if @needsAutoscroll
@emit 'autoscrolled' # Support legacy editor
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view
getPixelRect: ->
@editor.pixelRectForScreenRange(@getScreenRange())
getScreenRange: ->
{row, column} = @getScreenPosition()
new Range(new Point(row, column), new Point(row, column + 1))
autoscroll: (options) ->
@editor.scrollToScreenRange(@getScreenRange(), options)
getBeginningOfNextParagraphBufferPosition: (editor) ->
start = @getBufferPosition()
eof = @editor.getEofBufferPosition()
scanRange = [start, eof]
{row, column} = eof
position = new Point(row, column - 1)
@editor.scanInBufferRange /^\n*$/g, scanRange, ({range, stop}) ->
if !range.start.isEqual(start)
position = range.start
stop()
@editor.screenPositionForBufferPosition(position)
getBeginningOfPreviousParagraphBufferPosition: (editor) ->
start = @editor.getCursorBufferPosition()
{row, column} = start
scanRange = [[row-1, column], [0,0]]
position = new Point(0, 0)
zero = new Point(0,0)
@editor.backwardsScanInBufferRange /^\n*$/g, scanRange, ({range, stop}) ->
if !range.start.isEqual(zero)
position = range.start
stop()
@editor.screenPositionForBufferPosition(position)
if firstCharacterColumn is -1
false
else
bufferPosition.column > firstCharacterColumn
+45 -122
Ver Arquivo
@@ -1,12 +1,10 @@
_ = require 'underscore-plus'
EmitterMixin = require('emissary').Emitter
{Emitter} = require 'event-kit'
Grim = require 'grim'
{Subscriber, Emitter} = require 'emissary'
idCounter = 0
nextId = -> idCounter++
# Essential: Represents a decoration that follows a {Marker}. A decoration is
# Public: Represents a decoration that follows a {Marker}. A decoration is
# basically a visual representation of a marker. It allows you to add CSS
# classes to line numbers in the gutter, lines, and add selection-line regions
# around marked ranges of text.
@@ -22,163 +20,88 @@ nextId = -> idCounter++
#
# Best practice for destorying the decoration is by destroying the {Marker}.
#
# ```coffee
# ```
# marker.destroy()
# ```
#
# You should only use {Decoration::destroy} when you still need or do not own
# the marker.
#
# ### IDs
# Each {Decoration} has a unique ID available via `decoration.id`.
#
# ### Events
# A couple of events are emitted:
#
# * `destroyed`: When the {Decoration} is destroyed
# * `updated`: When the {Decoration} is updated via {Decoration::update}.
# Event object has properties `oldParams` and `newParams`
#
module.exports =
class Decoration
EmitterMixin.includeInto(this)
Emitter.includeInto(this)
# Private: Check if the `decorationProperties.type` matches `type`
#
# * `decorationProperties` {Object} eg. `{type: 'gutter', class: 'my-new-class'}`
# * `type` {String} type like `'gutter'`, `'line'`, etc. `type` can also
# be an {Array} of {String}s, where it will return true if the decoration's
# type matches any in the array.
#
# Returns {Boolean}
@isType: (decorationProperties, type) ->
if _.isArray(decorationProperties.type)
type in decorationProperties.type
@isType: (decorationParams, type) ->
if _.isArray(decorationParams.type)
type in decorationParams.type
else
type is decorationProperties.type
type is decorationParams.type
###
Section: Construction and Destruction
###
constructor: (@marker, @displayBuffer, @properties) ->
@emitter = new Emitter
constructor: (@marker, @displayBuffer, @params) ->
@id = nextId()
@properties.id = @id
@params.id = @id
@flashQueue = null
@destroyed = false
@isDestroyed = false
@markerDestroyDisposable = @marker.onDidDestroy => @destroy()
# Essential: Destroy this marker.
# Public: Destroy this marker.
#
# If you own the marker, you should use {Marker::destroy} which will destroy
# this decoration.
destroy: ->
return if @destroyed
@markerDestroyDisposable.dispose()
@markerDestroyDisposable = null
@destroyed = true
return if @isDestroyed
@isDestroyed = true
@displayBuffer.removeDecoration(this)
@emit 'destroyed'
@emitter.emit 'did-destroy'
@emitter.dispose()
###
Section: Event Subscription
###
# Essential: When the {Decoration} is updated via {Decoration::update}.
# Public: Update the marker with new params. Allows you to change the decoration's class.
#
# * `callback` {Function}
# * `event` {Object}
# * `oldProperties` {Object} the old parameters the decoration used to have
# * `newProperties` {Object} the new parameters the decoration now has
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeProperties: (callback) ->
@emitter.on 'did-change-properties', callback
# ```
# decoration.update({type: 'gutter', class: 'my-new-class'})
# ```
update: (newParams) ->
return if @isDestroyed
oldParams = @params
@params = newParams
@params.id = @id
@displayBuffer.decorationUpdated(this)
@emit 'updated', {oldParams, newParams}
# Essential: Invoke the given callback when the {Decoration} is destroyed
#
# * `callback` {Function}
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidDestroy: (callback) ->
@emitter.on 'did-destroy', callback
###
Section: Decoration Details
###
# Essential: An id unique across all {Decoration} objects
getId: -> @id
# Essential: Returns the marker associated with this {Decoration}
# Public: Returns the marker associated with this {Decoration}
getMarker: -> @marker
# Public: Returns the {Decoration}'s params.
getParams: -> @params
# Public: Check if this decoration is of type `type`
#
# * `type` {String} type like `'gutter'`, `'line'`, etc. `type` can also
# be an {Array} of {String}s, where it will return true if the decoration's
# type matches any in the array.
# type - A {String} type like `'gutter'`
#
# Returns {Boolean}
# Returns a {Boolean}
isType: (type) ->
Decoration.isType(@properties, type)
###
Section: Properties
###
# Essential: Returns the {Decoration}'s properties.
getProperties: ->
@properties
getParams: ->
Grim.deprecate 'Use Decoration::getProperties instead'
@getProperties()
# Essential: Update the marker with new Properties. Allows you to change the decoration's class.
#
# ## Examples
#
# ```coffee
# decoration.update({type: 'gutter', class: 'my-new-class'})
# ```
#
# * `newProperties` {Object} eg. `{type: 'gutter', class: 'my-new-class'}`
setProperties: (newProperties) ->
return if @destroyed
oldProperties = @properties
@properties = newProperties
@properties.id = @id
@emit 'updated', {oldParams: oldProperties, newParams: newProperties}
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
update: (newProperties) ->
Grim.deprecate 'Use Decoration::setProperties instead'
@setProperties(newProperties)
###
Section: Private methods
###
Decoration.isType(@params, type)
matchesPattern: (decorationPattern) ->
return false unless decorationPattern?
for key, value of decorationPattern
return false if @properties[key] != value
return false if @params[key] != value
true
onDidFlash: (callback) ->
@emitter.on 'did-flash', callback
flash: (klass, duration=500) ->
flashObject = {class: klass, duration}
@flashQueue ?= []
@flashQueue.push(flashObject)
@emit 'flash'
@emitter.emit 'did-flash'
consumeNextFlash: ->
return @flashQueue.shift() if @flashQueue?.length > 0
null
on: (eventName) ->
switch eventName
when 'updated'
Grim.deprecate 'Use Decoration::onDidChangeProperties instead'
when 'destroyed'
Grim.deprecate 'Use Decoration::onDidDestroy instead'
when 'flash'
Grim.deprecate 'Use Decoration::onDidFlash instead'
else
Grim.deprecate 'Decoration::on is deprecated. Use event subscription methods instead.'
EmitterMixin::on.apply(this, arguments)
+8 -8
Ver Arquivo
@@ -1,9 +1,9 @@
# Extended: Manages the deserializers used for serialized state
# Public: Manages the deserializers used for serialized state
#
# An instance of this class is always available as the `atom.deserializers`
# global.
#
# ## Examples
# ### Registering a deserializer
#
# ```coffee
# class MyPackageView extends View
@@ -24,21 +24,21 @@ class DeserializerManager
# Public: Register the given class(es) as deserializers.
#
# * `classes` One or more classes to register.
# classes - One or more classes to register.
add: (classes...) ->
@deserializers[klass.name] = klass for klass in classes
# Public: Remove the given class(es) as deserializers.
#
# * `classes` One or more classes to remove.
# classes - One or more classes to remove.
remove: (classes...) ->
delete @deserializers[name] for {name} in classes
# Public: Deserialize the state and params.
#
# * `state` The state {Object} to deserialize.
# * `params` The params {Object} to pass as the second arguments to the
# deserialize method of the deserializer.
# state - The state {Object} to deserialize.
# params - The params {Object} to pass as the second arguments to the
# deserialize method of the deserializer.
deserialize: (state, params) ->
return unless state?
@@ -51,7 +51,7 @@ class DeserializerManager
# Get the deserializer for the state.
#
# * `state` The state {Object} being deserialized.
# state - The state {Object} being deserialized.
get: (state) ->
return unless state?
+231
Ver Arquivo
@@ -0,0 +1,231 @@
{Range} = require 'text-buffer'
_ = require 'underscore-plus'
{Emitter, Subscriber} = require 'emissary'
module.exports =
class DisplayBufferMarker
Emitter.includeInto(this)
Subscriber.includeInto(this)
bufferMarkerSubscription: null
oldHeadBufferPosition: null
oldHeadScreenPosition: null
oldTailBufferPosition: null
oldTailScreenPosition: null
wasValid: true
constructor: ({@bufferMarker, @displayBuffer}) ->
@id = @bufferMarker.id
@oldHeadBufferPosition = @getHeadBufferPosition()
@oldHeadScreenPosition = @getHeadScreenPosition()
@oldTailBufferPosition = @getTailBufferPosition()
@oldTailScreenPosition = @getTailScreenPosition()
@wasValid = @isValid()
@subscribe @bufferMarker, 'destroyed', => @destroyed()
@subscribe @bufferMarker, 'changed', (event) => @notifyObservers(event)
copy: (attributes) ->
@displayBuffer.getMarker(@bufferMarker.copy(attributes).id)
# Gets the screen range of the display marker.
#
# Returns a {Range}.
getScreenRange: ->
@displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true)
# Modifies the screen range of the display marker.
#
# screenRange - The new {Range} to use
# options - A hash of options matching those found in {Marker::setRange}
setScreenRange: (screenRange, options) ->
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options)
# Gets the buffer range of the display marker.
#
# Returns a {Range}.
getBufferRange: ->
@bufferMarker.getRange()
# Modifies the buffer range of the display marker.
#
# screenRange - The new {Range} to use
# options - A hash of options matching those found in {Marker::setRange}
setBufferRange: (bufferRange, options) ->
@bufferMarker.setRange(bufferRange, options)
getPixelRange: ->
@displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false)
# Retrieves the screen position of the marker's head.
#
# Returns a {Point}.
getHeadScreenPosition: ->
@displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
# Sets the screen position of the marker's head.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition}
setHeadScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
# Retrieves the buffer position of the marker's head.
#
# Returns a {Point}.
getHeadBufferPosition: ->
@bufferMarker.getHeadPosition()
# Sets the buffer position of the marker's head.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition}
setHeadBufferPosition: (bufferPosition) ->
@bufferMarker.setHeadPosition(bufferPosition)
# Retrieves the screen position of the marker's tail.
#
# Returns a {Point}.
getTailScreenPosition: ->
@displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
# Sets the screen position of the marker's tail.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition}
setTailScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
# Retrieves the buffer position of the marker's tail.
#
# Returns a {Point}.
getTailBufferPosition: ->
@bufferMarker.getTailPosition()
# Sets the buffer position of the marker's tail.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition}
setTailBufferPosition: (bufferPosition) ->
@bufferMarker.setTailPosition(bufferPosition)
# Retrieves the screen position of the marker's start. This will always be
# less than or equal to the result of {DisplayBufferMarker::getEndScreenPosition}.
#
# Returns a {Point}.
getStartScreenPosition: ->
@displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true)
# Retrieves the buffer position of the marker's start. This will always be
# less than or equal to the result of {DisplayBufferMarker::getEndBufferPosition}.
#
# Returns a {Point}.
getStartBufferPosition: ->
@bufferMarker.getStartPosition()
# Retrieves the screen position of the marker's end. This will always be
# greater than or equal to the result of {DisplayBufferMarker::getStartScreenPosition}.
#
# Returns a {Point}.
getEndScreenPosition: ->
@displayBuffer.screenPositionForBufferPosition(@getEndBufferPosition(), wrapAtSoftNewlines: true)
# Retrieves the buffer position of the marker's end. This will always be
# greater than or equal to the result of {DisplayBufferMarker::getStartBufferPosition}.
#
# Returns a {Point}.
getEndBufferPosition: ->
@bufferMarker.getEndPosition()
# Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
plantTail: ->
@bufferMarker.plantTail()
# Removes the tail from the marker.
clearTail: ->
@bufferMarker.clearTail()
hasTail: ->
@bufferMarker.hasTail()
# Returns whether the head precedes the tail in the buffer
isReversed: ->
@bufferMarker.isReversed()
# Returns a {Boolean} indicating whether the marker is valid. Markers can be
# invalidated when a region surrounding them in the buffer is changed.
isValid: ->
@bufferMarker.isValid()
# Returns a {Boolean} indicating whether the marker has been destroyed. A marker
# can be invalid without being destroyed, in which case undoing the invalidating
# operation would restore the marker. Once a marker is destroyed by calling
# {Marker::destroy}, no undo/redo operation can ever bring it back.
isDestroyed: ->
@bufferMarker.isDestroyed()
getAttributes: ->
@bufferMarker.getProperties()
setAttributes: (attributes) ->
@bufferMarker.setProperties(attributes)
matchesAttributes: (attributes) ->
attributes = @displayBuffer.translateToBufferMarkerParams(attributes)
@bufferMarker.matchesAttributes(attributes)
# Destroys the marker
destroy: ->
@bufferMarker.destroy()
@unsubscribe()
isEqual: (other) ->
return false unless other instanceof @constructor
@bufferMarker.isEqual(other.bufferMarker)
compare: (other) ->
@bufferMarker.compare(other.bufferMarker)
# Returns a {String} representation of the marker
inspect: ->
"DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange()})"
destroyed: ->
delete @displayBuffer.markers[@id]
@emit 'destroyed'
notifyObservers: ({textChanged}) ->
textChanged ?= false
newHeadBufferPosition = @getHeadBufferPosition()
newHeadScreenPosition = @getHeadScreenPosition()
newTailBufferPosition = @getTailBufferPosition()
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)
@emit 'changed', {
@oldHeadScreenPosition, newHeadScreenPosition,
@oldTailScreenPosition, newTailScreenPosition,
@oldHeadBufferPosition, newHeadBufferPosition,
@oldTailBufferPosition, newTailBufferPosition,
textChanged,
isValid
}
@oldHeadBufferPosition = newHeadBufferPosition
@oldHeadScreenPosition = newHeadScreenPosition
@oldTailBufferPosition = newTailBufferPosition
@oldTailScreenPosition = newTailScreenPosition
@wasValid = isValid
+99 -150
Ver Arquivo
@@ -1,17 +1,15 @@
_ = require 'underscore-plus'
EmitterMixin = require('emissary').Emitter
{Emitter} = require 'emissary'
guid = require 'guid'
Serializable = require 'serializable'
{Model} = require 'theorist'
{Emitter} = require 'event-kit'
{Point, Range} = require 'text-buffer'
TokenizedBuffer = require './tokenized-buffer'
RowMap = require './row-map'
Fold = require './fold'
Token = require './token'
Decoration = require './decoration'
Marker = require './marker'
Grim = require 'grim'
DisplayBufferMarker = require './display-buffer-marker'
class BufferToScreenConversionError extends Error
constructor: (@message, @metadata) ->
@@ -24,7 +22,7 @@ class DisplayBuffer extends Model
@properties
manageScrollPosition: false
softWrapped: null
softWrap: null
editorWidthInChars: null
lineHeightInPixels: null
defaultCharWidth: null
@@ -33,55 +31,57 @@ class DisplayBuffer extends Model
scrollTop: 0
scrollLeft: 0
scrollWidth: 0
verticalScrollbarWidth: 15
horizontalScrollbarHeight: 15
verticalScrollMargin: 2
horizontalScrollMargin: 6
horizontalScrollbarHeight: 15
verticalScrollbarWidth: 15
scopedCharacterWidthsChangeCount: 0
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, @invisibles}={}) ->
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer}={}) ->
super
@emitter = new Emitter
@softWrapped ?= atom.config.get('editor.softWrap') ? false
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, @invisibles})
@softWrap ?= atom.config.get('editor.softWrap') ? false
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer})
@buffer = @tokenizedBuffer.buffer
@charWidthsByScope = {}
@markers = {}
@foldsByMarkerId = {}
@decorationsById = {}
@decorationsByMarkerId = {}
@decorationMarkerChangedSubscriptions = {}
@decorationMarkerDestroyedSubscriptions = {}
@updateAllScreenLines()
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
@subscribe @tokenizedBuffer.onDidChange @handleTokenizedBufferChange
@subscribe @buffer.onDidUpdateMarkers @handleBufferMarkersUpdated
@subscribe @buffer.onDidCreateMarker @handleBufferMarkerCreated
@subscribe @tokenizedBuffer, 'grammar-changed', (grammar) => @emit 'grammar-changed', grammar
@subscribe @tokenizedBuffer, 'tokenized', => @emit 'tokenized'
@subscribe @tokenizedBuffer, 'changed', @handleTokenizedBufferChange
@subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated
@subscribe @buffer, 'marker-created', @handleBufferMarkerCreated
@subscribe @$softWrap, (softWrap) =>
@emit 'soft-wrap-changed', softWrap
@updateWrappedScreenLines()
@subscribe atom.config.observe 'editor.preferredLineLength', callNow: false, =>
@updateWrappedScreenLines() if @isSoftWrapped() and atom.config.get('editor.softWrapAtPreferredLineLength')
@updateWrappedScreenLines() if @softWrap and atom.config.get('editor.softWrapAtPreferredLineLength')
@subscribe atom.config.observe 'editor.softWrapAtPreferredLineLength', callNow: false, =>
@updateWrappedScreenLines() if @isSoftWrapped()
@updateAllScreenLines()
@updateWrappedScreenLines() if @softWrap
serializeParams: ->
id: @id
softWrapped: @isSoftWrapped()
softWrap: @softWrap
editorWidthInChars: @editorWidthInChars
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())
@@ -95,71 +95,12 @@ class DisplayBuffer extends Model
@rowMap = new RowMap
@updateScreenLines(0, @buffer.getLineCount(), null, suppressChangeEvent: true)
onDidChangeSoftWrapped: (callback) ->
@emitter.on 'did-change-soft-wrapped', callback
onDidChangeGrammar: (callback) ->
@tokenizedBuffer.onDidChangeGrammar(callback)
onDidTokenize: (callback) ->
@tokenizedBuffer.onDidTokenize(callback)
onDidChange: (callback) ->
@emitter.on 'did-change', callback
onDidChangeCharacterWidths: (callback) ->
@emitter.on 'did-change-character-widths', callback
observeDecorations: (callback) ->
callback(decoration) for decoration in @getDecorations()
@onDidAddDecoration(callback)
onDidAddDecoration: (callback) ->
@emitter.on 'did-add-decoration', callback
onDidRemoveDecoration: (callback) ->
@emitter.on 'did-remove-decoration', callback
onDidCreateMarker: (callback) ->
@emitter.on 'did-create-marker', callback
onDidUpdateMarkers: (callback) ->
@emitter.on 'did-update-markers', callback
on: (eventName) ->
switch eventName
when 'changed'
Grim.deprecate("Use DisplayBuffer::onDidChange instead")
when 'grammar-changed'
Grim.deprecate("Use DisplayBuffer::onDidChangeGrammar instead")
when 'soft-wrap-changed'
Grim.deprecate("Use DisplayBuffer::onDidChangeSoftWrap instead")
when 'character-widths-changed'
Grim.deprecate("Use DisplayBuffer::onDidChangeCharacterWidths instead")
when 'decoration-added'
Grim.deprecate("Use DisplayBuffer::onDidAddDecoration instead")
when 'decoration-removed'
Grim.deprecate("Use DisplayBuffer::onDidRemoveDecoration instead")
when 'decoration-changed'
Grim.deprecate("Use decoration.getMarker().onDidChange() instead")
when 'decoration-updated'
Grim.deprecate("Use Decoration::onDidChangeProperties instead")
when 'marker-created'
Grim.deprecate("Use Decoration::onDidCreateMarker instead")
when 'markers-updated'
Grim.deprecate("Use Decoration::onDidUpdateMarkers instead")
else
Grim.deprecate("DisplayBuffer::on is deprecated. Use event subscription methods instead.")
EmitterMixin::on.apply(this, arguments)
emitDidChange: (eventProperties, refreshMarkers=true) ->
emitChanged: (eventProperties, refreshMarkers=true) ->
if refreshMarkers
@pauseMarkerChangeEvents()
@pauseMarkerObservers()
@refreshMarkerScreenPositions()
@emit 'changed', eventProperties
@emitter.emit 'did-change', eventProperties
@resumeMarkerChangeEvents()
@resumeMarkerObservers()
updateWrappedScreenLines: ->
start = 0
@@ -167,7 +108,7 @@ class DisplayBuffer extends Model
@updateAllScreenLines()
screenDelta = @getLastRow() - end
bufferDelta = 0
@emitDidChange({ start, end, screenDelta, bufferDelta })
@emitChanged({ start, end, screenDelta, bufferDelta })
# Sets the visibility of the tokenized buffer.
#
@@ -211,7 +152,7 @@ class DisplayBuffer extends Model
horizontallyScrollable: (reentrant) ->
return false unless @width?
return false if @isSoftWrapped()
return false if @getSoftWrap()
if reentrant
@getScrollWidth() > @getWidth()
else
@@ -236,7 +177,7 @@ class DisplayBuffer extends Model
setWidth: (newWidth) ->
oldWidth = @width
@width = newWidth
@updateWrappedScreenLines() if newWidth isnt oldWidth and @isSoftWrapped()
@updateWrappedScreenLines() if newWidth isnt oldWidth and @softWrap
@setScrollTop(@getScrollTop()) # Ensure scrollTop is still valid in case horizontal scrollbar disappeared
@width
@@ -309,7 +250,6 @@ class DisplayBuffer extends Model
characterWidthsChanged: ->
@computeScrollWidth()
@emit 'character-widths-changed', @scopedCharacterWidthsChangeCount
@emitter.emit 'did-change-character-widths', @scopedCharacterWidthsChangeCount
clearScopedCharWidths: ->
@charWidthsByScope = {}
@@ -400,18 +340,11 @@ class DisplayBuffer extends Model
setTabLength: (tabLength) ->
@tokenizedBuffer.setTabLength(tabLength)
setInvisibles: (@invisibles) ->
@tokenizedBuffer.setInvisibles(@invisibles)
# Deprecated: Use the softWrap property directly
setSoftWrap: (@softWrap) -> @softWrap
setSoftWrapped: (softWrapped) ->
if softWrapped isnt @softWrapped
@softWrapped = softWrapped
@updateWrappedScreenLines()
@emit 'soft-wrap-changed', @softWrapped
@emitter.emit 'did-change-soft-wrapped', @softWrapped
@softWrapped
isSoftWrapped: -> @softWrapped
# Deprecated: Use the softWrap property directly
getSoftWrap: -> @softWrap
# Set the number of characters that fit horizontally in the editor.
#
@@ -420,7 +353,7 @@ class DisplayBuffer extends Model
if editorWidthInChars > 0
previousWidthInChars = @editorWidthInChars
@editorWidthInChars = editorWidthInChars
if editorWidthInChars isnt previousWidthInChars and @isSoftWrapped()
if editorWidthInChars isnt previousWidthInChars and @softWrap
@updateWrappedScreenLines()
# Returns the editor width in characters for soft wrap.
@@ -440,25 +373,25 @@ class DisplayBuffer extends Model
# Gets the screen line for the given screen row.
#
# * `screenRow` - A {Number} indicating the screen row.
# screenRow - A {Number} indicating the screen row.
#
# Returns {TokenizedLine}
tokenizedLineForScreenRow: (screenRow) ->
@screenLines[screenRow]
# Returns a {ScreenLine}.
lineForRow: (row) ->
@screenLines[row]
# Gets the screen lines for the given screen row range.
#
# startRow - A {Number} indicating the beginning screen row.
# endRow - A {Number} indicating the ending screen row.
#
# Returns an {Array} of {TokenizedLine}s.
tokenizedLinesForScreenRows: (startRow, endRow) ->
# Returns an {Array} of {ScreenLine}s.
linesForRows: (startRow, endRow) ->
@screenLines[startRow..endRow]
# Gets all the screen lines.
#
# Returns an {Array} of {TokenizedLine}s.
getTokenizedLines: ->
# Returns an {Array} of {ScreenLines}s.
getLines: ->
new Array(@screenLines...)
indentLevelForLine: (line) ->
@@ -618,7 +551,7 @@ class DisplayBuffer extends Model
top = targetRow * @lineHeightInPixels
left = 0
column = 0
for token in @tokenizedLineForScreenRow(targetRow).tokens
for token in @lineForRow(targetRow).tokens
charWidths = @getScopedCharWidths(token.scopes)
for char in token.value
return {top, left} if column is targetColumn
@@ -636,7 +569,7 @@ class DisplayBuffer extends Model
left = 0
column = 0
for token in @tokenizedLineForScreenRow(row).tokens
for token in @lineForRow(row).tokens
charWidths = @getScopedCharWidths(token.scopes)
for char in token.value
charWidth = charWidths[char] ? defaultCharWidth
@@ -690,7 +623,7 @@ class DisplayBuffer extends Model
unless screenLine?
throw new BufferToScreenConversionError "No screen line exists when converting buffer row to screen row",
softWrapEnabled: @isSoftWrapped()
softWrapEnabled: @getSoftWrap()
foldCount: @findFoldMarkers().length
lastBufferRow: @buffer.getLastRow()
lastScreenRow: @getLastRow()
@@ -806,7 +739,7 @@ class DisplayBuffer extends Model
# Returns a {Number} representing the `line` position where the wrap would take place.
# Returns `null` if a wrap wouldn't occur.
findWrapColumn: (line, softWrapColumn=@getSoftWrapColumn()) ->
return unless @isSoftWrapped()
return unless @softWrap
return unless line.length > softWrapColumn
if /\s/.test(line[softWrapColumn])
@@ -829,12 +762,6 @@ class DisplayBuffer extends Model
decorationForId: (id) ->
@decorationsById[id]
getDecorations: ->
allDecorations = []
for markerId, decorations of @decorationsByMarkerId
allDecorations = allDecorations.concat(decorations) if decorations?
allDecorations
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
decorationsByMarkerId = {}
for marker in @findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
@@ -844,13 +771,24 @@ class DisplayBuffer extends Model
decorateMarker: (marker, decorationParams) ->
marker = @getMarker(marker.id)
@decorationMarkerDestroyedSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', =>
@removeAllDecorationsForMarker(marker)
@decorationMarkerChangedSubscriptions[marker.id] ?= @subscribe marker, 'changed', (event) =>
decorations = @decorationsByMarkerId[marker.id]
# Why check existence? Markers may get destroyed or decorations removed
# in the change handler. Bookmarks does this.
if decorations?
for decoration in decorations
@emit 'decoration-changed', marker, decoration, event
decoration = new Decoration(marker, this, decorationParams)
@subscribe decoration.onDidDestroy => @removeDecoration(decoration)
@decorationsByMarkerId[marker.id] ?= []
@decorationsByMarkerId[marker.id].push(decoration)
@decorationsById[decoration.id] = decoration
@emit 'decoration-added', decoration
@emitter.emit 'did-add-decoration', decoration
@emit 'decoration-added', marker, decoration
decoration
removeDecoration: (decoration) ->
@@ -861,25 +799,41 @@ class DisplayBuffer extends Model
if index > -1
decorations.splice(index, 1)
delete @decorationsById[decoration.id]
@emit 'decoration-removed', decoration
@emitter.emit 'did-remove-decoration', decoration
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
@emit 'decoration-removed', marker, decoration
@removedAllMarkerDecorations(marker) if decorations.length is 0
# Retrieves a {Marker} based on its id.
removeAllDecorationsForMarker: (marker) ->
decorations = @decorationsByMarkerId[marker.id].slice()
for decoration in decorations
@emit 'decoration-removed', marker, decoration
@removedAllMarkerDecorations(marker)
removedAllMarkerDecorations: (marker) ->
@decorationMarkerChangedSubscriptions[marker.id].off()
@decorationMarkerDestroyedSubscriptions[marker.id].off()
delete @decorationsByMarkerId[marker.id]
delete @decorationMarkerChangedSubscriptions[marker.id]
delete @decorationMarkerDestroyedSubscriptions[marker.id]
decorationUpdated: (decoration) ->
@emit 'decoration-updated', decoration
# Retrieves a {DisplayBufferMarker} based on its id.
#
# id - A {Number} representing a marker id
#
# Returns the {Marker} (if it exists).
# Returns the {DisplayBufferMarker} (if it exists).
getMarker: (id) ->
unless marker = @markers[id]
if bufferMarker = @buffer.getMarker(id)
marker = new Marker({bufferMarker, displayBuffer: this})
marker = new DisplayBufferMarker({bufferMarker, displayBuffer: this})
@markers[id] = marker
marker
# Retrieves the active markers in the buffer.
#
# Returns an {Array} of existing {Marker}s.
# Returns an {Array} of existing {DisplayBufferMarker}s.
getMarkers: ->
@buffer.getMarkers().map ({id}) => @getMarker(id)
@@ -934,7 +888,7 @@ class DisplayBuffer extends Model
#
# Refer to {DisplayBuffer::findMarkers} for details.
#
# Returns a {Marker} or null
# Returns a {DisplayBufferMarker} or null
findMarker: (params) ->
@findMarkers(params)[0]
@@ -955,7 +909,7 @@ class DisplayBuffer extends Model
# :containedInBufferRange - A {Range} or range-compatible {Array}. Only
# returns markers contained within this range.
#
# Returns an {Array} of {Marker}s
# Returns an {Array} of {DisplayBufferMarker}s
findMarkers: (params) ->
params = @translateToBufferMarkerParams(params)
@buffer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id)
@@ -1007,13 +961,12 @@ class DisplayBuffer extends Model
getFoldMarkerAttributes: (attributes={}) ->
_.extend(attributes, class: 'fold', displayBufferId: @id)
pauseMarkerChangeEvents: ->
marker.pauseChangeEvents() for marker in @getMarkers()
pauseMarkerObservers: ->
marker.pauseEvents() for marker in @getMarkers()
resumeMarkerChangeEvents: ->
marker.resumeChangeEvents() for marker in @getMarkers()
resumeMarkerObservers: ->
marker.resumeEvents() for marker in @getMarkers()
@emit 'markers-updated'
@emitter.emit 'did-update-markers'
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
@@ -1024,9 +977,9 @@ class DisplayBuffer extends Model
@tokenizedBuffer.destroy()
@unsubscribe()
logLines: (start=0, end=@getLastRow()) ->
logLines: (start=0, end=@getLastRow())->
for row in [start..end]
line = @tokenizedLineForScreenRow(row).text
line = @lineForRow(row).text
console.log row, @bufferRowForScreenRow(row), line, line.length
handleTokenizedBufferChange: (tokenizedBufferChange) =>
@@ -1055,10 +1008,10 @@ class DisplayBuffer extends Model
bufferDelta: bufferDelta
if options.delayChangeEvent
@pauseMarkerChangeEvents()
@pauseMarkerObservers()
@pendingChangeEvent = changeEvent
else
@emitDidChange(changeEvent, options.refreshMarkers)
@emitChanged(changeEvent, options.refreshMarkers)
buildScreenLines: (startBufferRow, endBufferRow) ->
screenLines = []
@@ -1067,7 +1020,7 @@ class DisplayBuffer extends Model
bufferRow = startBufferRow
while bufferRow < endBufferRow
tokenizedLine = @tokenizedBuffer.tokenizedLineForRow(bufferRow)
tokenizedLine = @tokenizedBuffer.lineForScreenRow(bufferRow)
if fold = @largestFoldStartingAtBufferRow(bufferRow)
foldLine = tokenizedLine.copy()
@@ -1130,21 +1083,17 @@ class DisplayBuffer extends Model
computeScrollWidth: ->
@scrollWidth = @pixelPositionForScreenPosition([@longestScreenRow, @maxLineLength]).left
@scrollWidth += 1 unless @isSoftWrapped()
@scrollWidth += 1 unless @getSoftWrap()
@setScrollLeft(Math.min(@getScrollLeft(), @getMaxScrollLeft()))
handleBufferMarkersUpdated: =>
if event = @pendingChangeEvent
@pendingChangeEvent = null
@emitDidChange(event, false)
@emitChanged(event, false)
handleBufferMarkerCreated: (textBufferMarker) =>
@createFoldForMarker(textBufferMarker) if textBufferMarker.matchesParams(@getFoldMarkerAttributes())
if marker = @getMarker(textBufferMarker.id)
# The marker might have been removed in some other handler called before
# this one. Only emit when the marker still exists.
@emit 'marker-created', marker
@emitter.emit 'did-create-marker', marker
handleBufferMarkerCreated: (marker) =>
@createFoldForMarker(marker) if marker.matchesAttributes(@getFoldMarkerAttributes())
@emit 'marker-created', @getMarker(marker.id)
createFoldForMarker: (marker) ->
@decorateMarker(marker, type: 'gutter', class: 'folded')
+219 -298
Ver Arquivo
@@ -1,10 +1,8 @@
_ = require 'underscore-plus'
React = require 'react-atom-fork'
{div, span} = require 'reactionary-atom-fork'
{debounce, defaults, isEqualForProperties} = require 'underscore-plus'
scrollbarStyle = require 'scrollbar-style'
{Range, Point} = require 'text-buffer'
grim = require 'grim'
GutterComponent = require './gutter-component'
InputComponent = require './input-component'
@@ -31,27 +29,30 @@ EditorComponent = React.createClass
updateRequested: false
updatesPaused: false
updateRequestedWhilePaused: false
cursorMoved: false
cursorsMoved: false
selectionChanged: false
selectionAdded: false
scrollingVertically: false
refreshingScrollbars: false
measuringScrollbars: true
mouseWheelScreenRow: null
mouseWheelScreenRowClearDelay: 150
scrollSensitivity: 0.4
heightAndWidthMeasurementRequested: false
measureLineHeightAndDefaultCharWidthWhenShown: false
remeasureCharacterWidthsWhenShown: false
inputEnabled: true
scopedCharacterWidthsChangeCount: null
domPollingInterval: 100
domPollingIntervalId: null
domPollingPaused: false
measureScrollbarsWhenShown: true
measureLineHeightAndDefaultCharWidthWhenShown: true
remeasureCharacterWidthsWhenShown: false
render: ->
{focused, showIndentGuide, showLineNumbers, visible} = @state
{focused, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state
{editor, mini, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
maxLineNumberDigits = editor.getLineCount().toString().length
hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty()
invisibles = if showInvisibles and not mini then @state.invisibles else {}
hasSelection = editor.getSelection()? and !editor.getSelection().isEmpty()
style = {}
if @performedInitialMeasurement
@@ -59,13 +60,10 @@ EditorComponent = React.createClass
[renderedStartRow, renderedEndRow] = renderedRowRange
cursorPixelRects = @getCursorPixelRects(renderedRowRange)
tokenizedLines = editor.tokenizedLinesForScreenRows(renderedStartRow, renderedEndRow - 1)
decorations = editor.decorationsForScreenRowRange(renderedStartRow, renderedEndRow)
highlightDecorations = @getHighlightDecorations(decorations)
lineDecorations = @getLineDecorations(decorations)
placeholderText = @props.placeholderText if @props.placeholderText? and editor.isEmpty()
visible = @isVisible()
scrollHeight = editor.getScrollHeight()
scrollWidth = editor.getScrollWidth()
@@ -109,12 +107,12 @@ EditorComponent = React.createClass
LinesComponent {
ref: 'lines',
editor, lineHeightInPixels, defaultCharWidth, tokenizedLines, lineDecorations, highlightDecorations,
editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations,
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow,
visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration,
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
@visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration,
placeholderText, @performedInitialMeasurement, @backgroundColor, cursorPixelRects,
cursorBlinkPeriod, cursorBlinkResumeDelay, mini
cursorBlinkPeriod, cursorBlinkResumeDelay
}
ScrollbarComponent
@@ -124,11 +122,10 @@ EditorComponent = React.createClass
onScroll: @onHorizontalScroll
scrollLeft: scrollLeft
scrollWidth: scrollWidth
visible: horizontallyScrollable
visible: horizontallyScrollable and not @refreshingScrollbars and not @measuringScrollbars
scrollableInOppositeDirection: verticallyScrollable
verticalScrollbarWidth: verticalScrollbarWidth
horizontalScrollbarHeight: horizontalScrollbarHeight
useHardwareAcceleration: @useHardwareAcceleration
ScrollbarComponent
ref: 'verticalScrollbar'
@@ -137,16 +134,15 @@ EditorComponent = React.createClass
onScroll: @onVerticalScroll
scrollTop: scrollTop
scrollHeight: scrollHeight
visible: verticallyScrollable
visible: verticallyScrollable and not @refreshingScrollbars and not @measuringScrollbars
scrollableInOppositeDirection: horizontallyScrollable
verticalScrollbarWidth: verticalScrollbarWidth
horizontalScrollbarHeight: horizontalScrollbarHeight
useHardwareAcceleration: @useHardwareAcceleration
# Also used to measure the height/width of scrollbars after the initial render
ScrollbarCornerComponent
ref: 'scrollbarCorner'
visible: horizontallyScrollable and verticallyScrollable
visible: not @refreshingScrollbars and (@measuringScrollbars or horizontallyScrollable and verticallyScrollable)
measuringScrollbars: @measuringScrollbars
height: horizontalScrollbarHeight
width: verticalScrollbarWidth
@@ -174,60 +170,59 @@ EditorComponent = React.createClass
componentDidMount: ->
{editor} = @props
@domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval)
@observeEditor()
@listenForDOMEvents()
@listenForCommands()
@subscribe atom.themes.onDidAddStylesheet @onStylesheetsChanged
@subscribe atom.themes.onDidUpdateStylesheet @onStylesheetsChanged
@subscribe atom.themes.onDidRemoveStylesheet @onStylesheetsChanged
unless atom.themes.isInitialLoadComplete()
@subscribe atom.themes.onDidReloadAll @onStylesheetsChanged
@subscribe atom.themes, 'stylesheet-added stylesheet-removed stylesheet-updated', @onStylesheetsChanged
@subscribe scrollbarStyle.changes, @refreshScrollbars
@domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval)
@updateParentViewFocusedClassIfNeeded({})
@updateParentViewMiniClassIfNeeded({})
@checkForVisibilityChange()
if @visible = @isVisible()
@performInitialMeasurement()
@forceUpdate()
componentWillUnmount: ->
{editor, parentView} = @props
parentView.trigger 'editor:will-be-removed', [parentView]
@props.parentView.trigger 'editor:will-be-removed', [@props.parentView]
@unsubscribe()
window.removeEventListener 'resize', @requestHeightAndWidthMeasurement
clearInterval(@domPollingIntervalId)
@domPollingIntervalId = null
componentWillReceiveProps: (newProps) ->
@props.editor.setMini(newProps.mini)
componentWillUpdate: ->
wasVisible = @visible
@visible = @isVisible()
@performInitialMeasurement() if @visible and not wasVisible
componentDidUpdate: (prevProps, prevState) ->
cursorMoved = @cursorMoved
cursorsMoved = @cursorsMoved
selectionChanged = @selectionChanged
@pendingChanges.length = 0
@cursorMoved = false
@cursorsMoved = false
@selectionChanged = false
@refreshingScrollbars = false
if @props.editor.isAlive()
@updateParentViewFocusedClassIfNeeded(prevState)
@updateParentViewMiniClassIfNeeded(prevState)
@props.parentView.trigger 'cursor:moved' if cursorMoved
@props.parentView.trigger 'cursor:moved' if cursorsMoved
@props.parentView.trigger 'selection:changed' if selectionChanged
@props.parentView.trigger 'editor:display-updated'
becameVisible: ->
if @performedInitialMeasurement
@measureScrollbars() if @measuringScrollbars
performInitialMeasurement: ->
@updatesPaused = true
@measureHeightAndWidth()
@sampleFontStyling()
@sampleBackgroundColors()
@measureHeightAndWidth()
@measureScrollbars() if @measureScrollbarsWhenShown
@measureScrollbars()
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
@props.editor.setVisible(true)
@performedInitialMeasurement = true
@updatesPaused = false
@forceUpdate() if @updateRequestedWhilePaused
@performedInitialMeasurement = true
requestUpdate: ->
return unless @isMounted()
@@ -267,9 +262,9 @@ EditorComponent = React.createClass
getHiddenInputPosition: ->
{editor} = @props
{focused} = @state
return {top: 0, left: 0} unless @isMounted() and focused and editor.getLastCursor()?
return {top: 0, left: 0} unless @isMounted() and focused and editor.getCursor()?
{top, left, height, width} = editor.getLastCursor().getPixelRect()
{top, left, height, width} = editor.getCursor().getPixelRect()
width = 2 if width is 0 # Prevent autoscroll at the end of longest line
top -= editor.getScrollTop()
left -= editor.getScrollLeft()
@@ -313,7 +308,7 @@ EditorComponent = React.createClass
if marker.isValid()
for decoration in decorations
if decoration.isType('gutter') or decoration.isType('line')
decorationParams = decoration.getProperties()
decorationParams = decoration.getParams()
screenRange ?= marker.getScreenRange()
headScreenRow ?= marker.getHeadScreenPosition().row
startRow = screenRange.start.row
@@ -340,7 +335,7 @@ EditorComponent = React.createClass
if marker.isValid() and not screenRange.isEmpty()
for decoration in decorations
if decoration.isType('highlight')
decorationParams = decoration.getProperties()
decorationParams = decoration.getParams()
filteredDecorations[markerId] ?=
id: markerId
startPixelPosition: editor.pixelPositionForScreenPosition(screenRange.start)
@@ -352,16 +347,17 @@ EditorComponent = React.createClass
observeEditor: ->
{editor} = @props
@subscribe editor.onDidChange(@onScreenLinesChanged)
@subscribe editor.observeCursors(@onCursorAdded)
@subscribe editor.observeSelections(@onSelectionAdded)
@subscribe editor.observeDecorations(@onDecorationAdded)
@subscribe editor.onDidRemoveDecoration(@onDecorationRemoved)
@subscribe editor.onDidChangeCharacterWidths(@onCharacterWidthsChanged)
@subscribe editor, 'screen-lines-changed', @onScreenLinesChanged
@subscribe editor, 'cursors-moved', @onCursorsMoved
@subscribe editor, 'selection-removed selection-screen-range-changed', @onSelectionChanged
@subscribe editor, 'selection-added', @onSelectionAdded
@subscribe editor, 'decoration-added', @onDecorationChanged
@subscribe editor, 'decoration-removed', @onDecorationChanged
@subscribe editor, 'decoration-changed', @onDecorationChanged
@subscribe editor, 'decoration-updated', @onDecorationChanged
@subscribe editor, 'character-widths-changed', @onCharacterWidthsChanged
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
@subscribe editor.$scrollLeft.changes, @requestUpdate
@subscribe editor.$verticalScrollbarWidth.changes, @requestUpdate
@subscribe editor.$horizontalScrollbarHeight.changes, @requestUpdate
@subscribe editor.$height.changes, @requestUpdate
@subscribe editor.$width.changes, @requestUpdate
@subscribe editor.$defaultCharWidth.changes, @requestUpdate
@@ -409,135 +405,125 @@ EditorComponent = React.createClass
{parentView, editor, mini} = @props
@addCommandListeners
'core:move-left': -> editor.moveLeft()
'core:move-right': -> editor.moveRight()
'core:select-left': -> editor.selectLeft()
'core:select-right': -> editor.selectRight()
'core:select-all': -> editor.selectAll()
'core:backspace': -> editor.backspace()
'core:delete': -> editor.delete()
'core:undo': -> editor.undo()
'core:redo': -> editor.redo()
'core:cut': -> editor.cutSelectedText()
'core:copy': -> editor.copySelectedText()
'core:paste': -> editor.pasteText()
'editor:move-to-previous-word': -> editor.moveToPreviousWord()
'editor:select-word': -> editor.selectWordsContainingCursors()
'core:move-left': => editor.moveCursorLeft()
'core:move-right': => editor.moveCursorRight()
'core:select-left': => editor.selectLeft()
'core:select-right': => editor.selectRight()
'core:select-all': => editor.selectAll()
'core:backspace': => editor.backspace()
'core:delete': => editor.delete()
'core:undo': => editor.undo()
'core:redo': => editor.redo()
'core:cut': => editor.cutSelectedText()
'core:copy': => editor.copySelectedText()
'core:paste': => editor.pasteText()
'editor:move-to-previous-word': => editor.moveCursorToPreviousWord()
'editor:select-word': => editor.selectWord()
'editor:consolidate-selections': @consolidateSelections
'editor:delete-to-beginning-of-word': -> editor.deleteToBeginningOfWord()
'editor:delete-to-beginning-of-line': -> editor.deleteToBeginningOfLine()
'editor:delete-to-end-of-line': -> editor.deleteToEndOfLine()
'editor:delete-to-end-of-word': -> editor.deleteToEndOfWord()
'editor:delete-line': -> editor.deleteLine()
'editor:cut-to-end-of-line': -> editor.cutToEndOfLine()
'editor:move-to-beginning-of-next-paragraph': -> editor.moveToBeginningOfNextParagraph()
'editor:move-to-beginning-of-previous-paragraph': -> editor.moveToBeginningOfPreviousParagraph()
'editor:move-to-beginning-of-screen-line': -> editor.moveToBeginningOfScreenLine()
'editor:move-to-beginning-of-line': -> editor.moveToBeginningOfLine()
'editor:move-to-end-of-screen-line': -> editor.moveToEndOfScreenLine()
'editor:move-to-end-of-line': -> editor.moveToEndOfLine()
'editor:move-to-first-character-of-line': -> editor.moveToFirstCharacterOfLine()
'editor:move-to-beginning-of-word': -> editor.moveToBeginningOfWord()
'editor:move-to-end-of-word': -> editor.moveToEndOfWord()
'editor:move-to-beginning-of-next-word': -> editor.moveToBeginningOfNextWord()
'editor:move-to-previous-word-boundary': -> editor.moveToPreviousWordBoundary()
'editor:move-to-next-word-boundary': -> editor.moveToNextWordBoundary()
'editor:select-to-beginning-of-next-paragraph': -> editor.selectToBeginningOfNextParagraph()
'editor:select-to-beginning-of-previous-paragraph': -> editor.selectToBeginningOfPreviousParagraph()
'editor:select-to-end-of-line': -> editor.selectToEndOfLine()
'editor:select-to-beginning-of-line': -> editor.selectToBeginningOfLine()
'editor:select-to-end-of-word': -> editor.selectToEndOfWord()
'editor:select-to-beginning-of-word': -> editor.selectToBeginningOfWord()
'editor:select-to-beginning-of-next-word': -> editor.selectToBeginningOfNextWord()
'editor:select-to-next-word-boundary': -> editor.selectToNextWordBoundary()
'editor:select-to-previous-word-boundary': -> editor.selectToPreviousWordBoundary()
'editor:select-to-first-character-of-line': -> editor.selectToFirstCharacterOfLine()
'editor:select-line': -> editor.selectLinesContainingCursors()
'editor:transpose': -> editor.transpose()
'editor:upper-case': -> editor.upperCase()
'editor:lower-case': -> editor.lowerCase()
'editor:delete-to-beginning-of-word': => editor.deleteToBeginningOfWord()
'editor:delete-to-beginning-of-line': => editor.deleteToBeginningOfLine()
'editor:delete-to-end-of-line': => editor.deleteToEndOfLine()
'editor:delete-to-end-of-word': => editor.deleteToEndOfWord()
'editor:delete-line': => editor.deleteLine()
'editor:cut-to-end-of-line': => editor.cutToEndOfLine()
'editor:move-to-beginning-of-next-paragraph': => editor.moveCursorToBeginningOfNextParagraph()
'editor:move-to-beginning-of-previous-paragraph': => editor.moveCursorToBeginningOfPreviousParagraph()
'editor:move-to-beginning-of-screen-line': => editor.moveCursorToBeginningOfScreenLine()
'editor:move-to-beginning-of-line': => editor.moveCursorToBeginningOfLine()
'editor:move-to-end-of-screen-line': => editor.moveCursorToEndOfScreenLine()
'editor:move-to-end-of-line': => editor.moveCursorToEndOfLine()
'editor:move-to-first-character-of-line': => editor.moveCursorToFirstCharacterOfLine()
'editor:move-to-beginning-of-word': => editor.moveCursorToBeginningOfWord()
'editor:move-to-end-of-word': => editor.moveCursorToEndOfWord()
'editor:move-to-beginning-of-next-word': => editor.moveCursorToBeginningOfNextWord()
'editor:move-to-previous-word-boundary': => editor.moveCursorToPreviousWordBoundary()
'editor:move-to-next-word-boundary': => editor.moveCursorToNextWordBoundary()
'editor:select-to-beginning-of-next-paragraph': => editor.selectToBeginningOfNextParagraph()
'editor:select-to-beginning-of-previous-paragraph': => editor.selectToBeginningOfPreviousParagraph()
'editor:select-to-end-of-line': => editor.selectToEndOfLine()
'editor:select-to-beginning-of-line': => editor.selectToBeginningOfLine()
'editor:select-to-end-of-word': => editor.selectToEndOfWord()
'editor:select-to-beginning-of-word': => editor.selectToBeginningOfWord()
'editor:select-to-beginning-of-next-word': => editor.selectToBeginningOfNextWord()
'editor:select-to-next-word-boundary': => editor.selectToNextWordBoundary()
'editor:select-to-previous-word-boundary': => editor.selectToPreviousWordBoundary()
'editor:select-to-first-character-of-line': => editor.selectToFirstCharacterOfLine()
'editor:select-line': => editor.selectLine()
'editor:transpose': => editor.transpose()
'editor:upper-case': => editor.upperCase()
'editor:lower-case': => editor.lowerCase()
unless mini
@addCommandListeners
'core:move-up': -> editor.moveUp()
'core:move-down': -> editor.moveDown()
'core:move-to-top': -> editor.moveToTop()
'core:move-to-bottom': -> editor.moveToBottom()
'core:page-up': -> editor.pageUp()
'core:page-down': -> editor.pageDown()
'core:select-up': -> editor.selectUp()
'core:select-down': -> editor.selectDown()
'core:select-to-top': -> editor.selectToTop()
'core:select-to-bottom': -> editor.selectToBottom()
'core:select-page-up': -> editor.selectPageUp()
'core:select-page-down': -> editor.selectPageDown()
'editor:indent': -> editor.indent()
'editor:auto-indent': -> editor.autoIndentSelectedRows()
'editor:indent-selected-rows': -> editor.indentSelectedRows()
'editor:outdent-selected-rows': -> editor.outdentSelectedRows()
'editor:newline': -> editor.insertNewline()
'editor:newline-below': -> editor.insertNewlineBelow()
'editor:newline-above': -> editor.insertNewlineAbove()
'editor:add-selection-below': -> editor.addSelectionBelow()
'editor:add-selection-above': -> editor.addSelectionAbove()
'editor:split-selections-into-lines': -> editor.splitSelectionsIntoLines()
'editor:toggle-soft-tabs': -> editor.toggleSoftTabs()
'editor:toggle-soft-wrap': -> editor.toggleSoftWrapped()
'editor:fold-all': -> editor.foldAll()
'editor:unfold-all': -> editor.unfoldAll()
'editor:fold-current-row': -> editor.foldCurrentRow()
'editor:unfold-current-row': -> editor.unfoldCurrentRow()
'editor:fold-selection': -> editor.foldSelectedLines()
'editor:fold-at-indent-level-1': -> editor.foldAllAtIndentLevel(0)
'editor:fold-at-indent-level-2': -> editor.foldAllAtIndentLevel(1)
'editor:fold-at-indent-level-3': -> editor.foldAllAtIndentLevel(2)
'editor:fold-at-indent-level-4': -> editor.foldAllAtIndentLevel(3)
'editor:fold-at-indent-level-5': -> editor.foldAllAtIndentLevel(4)
'editor:fold-at-indent-level-6': -> editor.foldAllAtIndentLevel(5)
'editor:fold-at-indent-level-7': -> editor.foldAllAtIndentLevel(6)
'editor:fold-at-indent-level-8': -> editor.foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': -> editor.foldAllAtIndentLevel(8)
'editor:toggle-line-comments': -> editor.toggleLineCommentsInSelection()
'editor:log-cursor-scope': -> editor.logCursorScope()
'editor:checkout-head-revision': -> atom.project.getRepo()?.checkoutHeadForEditor(editor)
'editor:copy-path': -> editor.copyPathToClipboard()
'editor:move-line-up': -> editor.moveLineUp()
'editor:move-line-down': -> editor.moveLineDown()
'editor:duplicate-lines': -> editor.duplicateLines()
'editor:join-lines': -> editor.joinLines()
'editor:toggle-indent-guide': -> atom.config.toggle('editor.showIndentGuide')
'editor:toggle-line-numbers': -> atom.config.toggle('editor.showLineNumbers')
'editor:scroll-to-cursor': -> editor.scrollToCursorPosition()
'core:move-up': => editor.moveCursorUp()
'core:move-down': => editor.moveCursorDown()
'core:move-to-top': => editor.moveCursorToTop()
'core:move-to-bottom': => editor.moveCursorToBottom()
'core:page-up': => editor.pageUp()
'core:page-down': => editor.pageDown()
'core:select-up': => editor.selectUp()
'core:select-down': => editor.selectDown()
'core:select-to-top': => editor.selectToTop()
'core:select-to-bottom': => editor.selectToBottom()
'core:select-page-up': => editor.selectPageUp()
'core:select-page-down': => editor.selectPageDown()
'editor:indent': => editor.indent()
'editor:auto-indent': => editor.autoIndentSelectedRows()
'editor:indent-selected-rows': => editor.indentSelectedRows()
'editor:outdent-selected-rows': => editor.outdentSelectedRows()
'editor:newline': => editor.insertNewline()
'editor:newline-below': => editor.insertNewlineBelow()
'editor:newline-above': => editor.insertNewlineAbove()
'editor:add-selection-below': => editor.addSelectionBelow()
'editor:add-selection-above': => editor.addSelectionAbove()
'editor:split-selections-into-lines': => editor.splitSelectionsIntoLines()
'editor:toggle-soft-tabs': => editor.toggleSoftTabs()
'editor:toggle-soft-wrap': => editor.toggleSoftWrap()
'editor:fold-all': => editor.foldAll()
'editor:unfold-all': => editor.unfoldAll()
'editor:fold-current-row': => editor.foldCurrentRow()
'editor:unfold-current-row': => editor.unfoldCurrentRow()
'editor:fold-selection': => editor.foldSelectedLines()
'editor:fold-at-indent-level-1': => editor.foldAllAtIndentLevel(0)
'editor:fold-at-indent-level-2': => editor.foldAllAtIndentLevel(1)
'editor:fold-at-indent-level-3': => editor.foldAllAtIndentLevel(2)
'editor:fold-at-indent-level-4': => editor.foldAllAtIndentLevel(3)
'editor:fold-at-indent-level-5': => editor.foldAllAtIndentLevel(4)
'editor:fold-at-indent-level-6': => editor.foldAllAtIndentLevel(5)
'editor:fold-at-indent-level-7': => editor.foldAllAtIndentLevel(6)
'editor:fold-at-indent-level-8': => editor.foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': => editor.foldAllAtIndentLevel(8)
'editor:toggle-line-comments': => editor.toggleLineCommentsInSelection()
'editor:log-cursor-scope': => editor.logCursorScope()
'editor:checkout-head-revision': => editor.checkoutHead()
'editor:copy-path': => editor.copyPathToClipboard()
'editor:move-line-up': => editor.moveLineUp()
'editor:move-line-down': => editor.moveLineDown()
'editor:duplicate-lines': => editor.duplicateLines()
'editor:join-lines': => editor.joinLines()
'editor:toggle-indent-guide': => atom.config.toggle('editor.showIndentGuide')
'editor:toggle-line-numbers': => atom.config.toggle('editor.showLineNumbers')
'editor:scroll-to-cursor': => editor.scrollToCursorPosition()
'benchmark:scroll': @runScrollBenchmark
addCommandListeners: (listenersByCommandName) ->
{parentView} = @props
addListener = (command, listener) ->
parentView.command command, (event) ->
event.stopPropagation()
listener(event)
addListener(command, listener) for command, listener of listenersByCommandName
return
for command, listener of listenersByCommandName
parentView.command command, listener
observeConfig: ->
@subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide
@subscribe atom.config.observe 'editor.invisibles', @setInvisibles
@subscribe atom.config.observe 'editor.showInvisibles', @setShowInvisibles
@subscribe atom.config.observe 'editor.showLineNumbers', @setShowLineNumbers
@subscribe atom.config.observe 'editor.scrollSensitivity', @setScrollSensitivity
@subscribe atom.config.observe 'editor.useHardwareAcceleration', @setUseHardwareAcceleration
onFocus: ->
@refs.input.focus() if @isMounted()
@refs.input.focus()
onTextInput: (event) ->
event.stopPropagation()
# If we prevent the insertion of a space character, then the browser
# interprets the spacebar keypress as a page-down command.
event.preventDefault() unless event.data is ' '
return unless @isInputEnabled()
{editor} = @props
@@ -550,8 +536,12 @@ EditorComponent = React.createClass
selectedLength = inputNode.selectionEnd - inputNode.selectionStart
editor.selectLeft() if selectedLength is 1
inputNode.value = event.data if editor.insertText(event.data)
editor.insertText(event.data)
inputNode.value = event.data
# If we prevent the insertion of a space character, then the browser
# interprets the spacebar keypress as a page-down command.
event.preventDefault() unless event.data is ' '
onInputFocused: ->
@setState(focused: true)
@@ -612,17 +602,9 @@ EditorComponent = React.createClass
onMouseDown: (event) ->
return unless event.button is 0 # only handle the left mouse button
return if event.target?.classList.contains('horizontal-scrollbar')
{editor} = @props
{detail, shiftKey, metaKey, ctrlKey} = event
# CTRL+click brings up the context menu on OSX, so don't handle those either
return if ctrlKey and process.platform is 'darwin'
# Prevent focusout event on hidden input if editor is already focused
event.preventDefault() if @state.focused
screenPosition = @screenPositionForMouseEvent(event)
if event.target?.classList.contains('fold-marker')
@@ -649,12 +631,8 @@ EditorComponent = React.createClass
onGutterMouseDown: (event) ->
return unless event.button is 0 # only handle the left mouse button
{shiftKey, metaKey, ctrlKey} = event
if shiftKey
if event.shiftKey
@onGutterShiftClick(event)
else if metaKey or (ctrlKey and process.platform isnt 'darwin')
@onGutterMetaClick(event)
else
@onGutterClick(event)
@@ -662,43 +640,19 @@ EditorComponent = React.createClass
{editor} = @props
clickedRow = @screenPositionForMouseEvent(event).row
editor.setSelectedScreenRange([[clickedRow, 0], [clickedRow + 1, 0]], preserveFolds: true)
editor.setCursorScreenPosition([clickedRow, 0])
@handleDragUntilMouseUp event, (screenPosition) ->
dragRow = screenPosition.row
if dragRow < clickedRow # dragging up
editor.setSelectedScreenRange([[dragRow, 0], [clickedRow + 1, 0]], preserveFolds: true)
editor.setSelectedScreenRange([[dragRow, 0], [clickedRow + 1, 0]])
else
editor.setSelectedScreenRange([[clickedRow, 0], [dragRow + 1, 0]], preserveFolds: true)
onGutterMetaClick: (event) ->
{editor} = @props
clickedRow = @screenPositionForMouseEvent(event).row
bufferRange = editor.bufferRangeForScreenRange([[clickedRow, 0], [clickedRow + 1, 0]])
rowSelection = editor.addSelectionForBufferRange(bufferRange, preserveFolds: true)
@handleDragUntilMouseUp event, (screenPosition) ->
dragRow = screenPosition.row
if dragRow < clickedRow # dragging up
rowSelection.setScreenRange([[dragRow, 0], [clickedRow + 1, 0]], preserveFolds: true)
else
rowSelection.setScreenRange([[clickedRow, 0], [dragRow + 1, 0]], preserveFolds: true)
# After updating the selected screen range, merge overlapping selections
editor.mergeIntersectingSelections(preserveFolds: true)
# The merge process will possibly destroy the current selection because
# it will be merged into another one. Therefore, we need to obtain a
# reference to the new selection that contains the originally selected row
rowSelection = _.find editor.getSelections(), (selection) ->
selection.intersectsBufferRange(bufferRange)
editor.setSelectedScreenRange([[clickedRow, 0], [dragRow + 1, 0]])
onGutterShiftClick: (event) ->
{editor} = @props
clickedRow = @screenPositionForMouseEvent(event).row
tailPosition = editor.getLastSelection().getTailScreenPosition()
tailPosition = editor.getSelection().getTailScreenPosition()
if clickedRow < tailPosition.row
editor.selectToScreenPosition([clickedRow, 0])
@@ -708,15 +662,14 @@ EditorComponent = React.createClass
@handleDragUntilMouseUp event, (screenPosition) ->
dragRow = screenPosition.row
if dragRow < tailPosition.row # dragging up
editor.setSelectedScreenRange([[dragRow, 0], tailPosition], preserveFolds: true)
editor.setSelectedScreenRange([[dragRow, 0], tailPosition])
else
editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]], preserveFolds: true)
editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]])
onStylesheetsChanged: (stylesheet) ->
return unless @performedInitialMeasurement
return unless atom.themes.isInitialLoadComplete()
@refreshScrollbars() if not stylesheet? or @containsScrollbarSelector(stylesheet)
@refreshScrollbars() if @containsScrollbarSelector(stylesheet)
@sampleFontStyling()
@sampleBackgroundColors()
@remeasureCharacterWidths()
@@ -726,24 +679,19 @@ EditorComponent = React.createClass
@pendingChanges.push(change)
@requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events
onSelectionAdded: (selection) ->
{editor} = @props
@subscribe selection.onDidChangeRange => @onSelectionChanged(selection)
@subscribe selection.onDidDestroy =>
@onSelectionChanged(selection)
@unsubscribe(selection)
if editor.selectionIntersectsVisibleRowRange(selection)
@selectionChanged = true
@requestUpdate()
onSelectionChanged: (selection) ->
{editor} = @props
if editor.selectionIntersectsVisibleRowRange(selection)
@selectionChanged = true
@requestUpdate()
onSelectionAdded: (selection) ->
{editor} = @props
if editor.selectionIntersectsVisibleRowRange(selection)
@selectionChanged = true
@selectionAdded = true
@requestUpdate()
onScrollTopChanged: ->
@scrollingVertically = true
@requestUpdate()
@@ -759,24 +707,13 @@ EditorComponent = React.createClass
onStoppedScrollingAfterDelay: null # created lazily
onCursorAdded: (cursor) ->
@subscribe cursor.onDidChangePosition @onCursorMoved
onCursorMoved: ->
@cursorMoved = true
@requestUpdate()
onDecorationAdded: (decoration) ->
@subscribe decoration.onDidChangeProperties(@onDecorationChanged)
@subscribe decoration.getMarker().onDidChange(@onDecorationChanged)
onCursorsMoved: ->
@cursorsMoved = true
@requestUpdate()
onDecorationChanged: ->
@requestUpdate()
onDecorationRemoved: ->
@requestUpdate()
onCharacterWidthsChanged: (@scopedCharacterWidthsChangeCount) ->
@requestUpdate()
@@ -829,20 +766,15 @@ EditorComponent = React.createClass
pollDOM: ->
return if @domPollingPaused or @updateRequested or not @isMounted()
unless @checkForVisibilityChange()
@sampleBackgroundColors()
@measureHeightAndWidth()
@sampleFontStyling()
checkForVisibilityChange: ->
if @isVisible()
if @wasVisible
false
wasVisible = @visible
if @visible = @isVisible()
if wasVisible
@measureHeightAndWidth()
@sampleFontStyling()
@sampleBackgroundColors()
else
@becameVisible()
@wasVisible = true
else
@wasVisible = false
@performInitialMeasurement()
@forceUpdate()
requestHeightAndWidthMeasurement: ->
return if @heightAndWidthMeasurementRequested
@@ -868,7 +800,7 @@ EditorComponent = React.createClass
if position is 'absolute' or height
if @autoHeight
@autoHeight = false
@forceUpdate() unless @updatesPaused
@forceUpdate()
clientHeight = scrollViewNode.clientHeight
editor.setHeight(clientHeight) if clientHeight > 0
@@ -910,36 +842,30 @@ EditorComponent = React.createClass
@requestUpdate() unless suppressUpdate
measureLineHeightAndDefaultCharWidth: ->
if @isVisible()
if @visible
@measureLineHeightAndDefaultCharWidthWhenShown = false
@refs.lines.measureLineHeightAndDefaultCharWidth()
else
@measureLineHeightAndDefaultCharWidthWhenShown = true
remeasureCharacterWidths: ->
if @isVisible()
if @visible
@remeasureCharacterWidthsWhenShown = false
@refs.lines.remeasureCharacterWidths()
else
@remeasureCharacterWidthsWhenShown = true
measureScrollbars: ->
@measureScrollbarsWhenShown = false
return unless @visible
@measuringScrollbars = false
{editor} = @props
cornerNode = @refs.scrollbarCorner.getDOMNode()
originalDisplayValue = cornerNode.style.display
cornerNode.style.display = 'block'
width = (cornerNode.offsetWidth - cornerNode.clientWidth) or 15
height = (cornerNode.offsetHeight - cornerNode.clientHeight) or 15
scrollbarCornerNode = @refs.scrollbarCorner.getDOMNode()
width = (scrollbarCornerNode.offsetWidth - scrollbarCornerNode.clientWidth) or 15
height = (scrollbarCornerNode.offsetHeight - scrollbarCornerNode.clientHeight) or 15
editor.setVerticalScrollbarWidth(width)
editor.setHorizontalScrollbarHeight(height)
cornerNode.style.display = originalDisplayValue
containsScrollbarSelector: (stylesheet) ->
for rule in stylesheet.cssRules
if rule.selectorText?.indexOf('scrollbar') > -1
@@ -947,39 +873,24 @@ EditorComponent = React.createClass
false
refreshScrollbars: ->
if @isVisible()
@measureScrollbarsWhenShown = false
else
@measureScrollbarsWhenShown = true
return
# Believe it or not, proper handling of changes to scrollbar styles requires
# three DOM updates.
{verticalScrollbar, horizontalScrollbar, scrollbarCorner} = @refs
# Scrollbar style changes won't apply to scrollbars that are already
# visible, so first we need to hide scrollbars so we can redisplay them and
# force Chromium to apply updates.
@refreshingScrollbars = true
@forceUpdate()
verticalNode = verticalScrollbar.getDOMNode()
horizontalNode = horizontalScrollbar.getDOMNode()
cornerNode = scrollbarCorner.getDOMNode()
# Next, we display only the scrollbar corner so we can measure the new
# scrollbar dimensions. The ::measuringScrollbars property will be set back
# to false after the scrollbars are measured.
@measuringScrollbars = true
@forceUpdate()
originalVerticalDisplayValue = verticalNode.style.display
originalHorizontalDisplayValue = horizontalNode.style.display
originalCornerDisplayValue = cornerNode.style.display
# First, hide all scrollbars in case they are visible so they take on new
# styles when they are shown again.
verticalNode.style.display = 'none'
horizontalNode.style.display = 'none'
cornerNode.style.display = 'none'
# Force a reflow
cornerNode.offsetWidth
# Now measure the new scrollbar dimensions
@measureScrollbars()
# Now restore the display value for all scrollbars, since they were
# previously hidden
verticalNode.style.display = originalVerticalDisplayValue
horizontalNode.style.display = originalHorizontalDisplayValue
cornerNode.style.display = originalCornerDisplayValue
# Finally, we restore the scrollbars based on the newly-measured dimensions
# if the editor's content and dimensions require them to be visible.
@forceUpdate()
clearMouseWheelScreenRow: ->
if @mouseWheelScreenRow?
@@ -1023,14 +934,24 @@ EditorComponent = React.createClass
setShowIndentGuide: (showIndentGuide) ->
@setState({showIndentGuide})
# Deprecated
# Public: Defines which characters are invisible.
#
# invisibles - An {Object} defining the invisible characters:
# :eol - The end of line invisible {String} (default: `\u00ac`).
# :space - The space invisible {String} (default: `\u00b7`).
# :tab - The tab invisible {String} (default: `\u00bb`).
# :cr - The carriage return invisible {String} (default: `\u00a4`).
setInvisibles: (invisibles={}) ->
grim.deprecate "Use config.set('editor.invisibles', invisibles) instead"
atom.config.set('editor.invisibles', invisibles)
defaults invisibles,
eol: '\u00ac'
space: '\u00b7'
tab: '\u00bb'
cr: '\u00a4'
@setState({invisibles})
# Deprecated
setShowInvisibles: (showInvisibles) ->
atom.config.set('editor.showInvisibles', showInvisibles)
@setState({showInvisibles})
setShowLineNumbers: (showLineNumbers) ->
@setState({showLineNumbers})
+1527 -304
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+1486 -2203
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+2 -2
Ver Arquivo
@@ -14,8 +14,8 @@ class Fold
@id = @marker.id
@displayBuffer.foldsByMarkerId[@marker.id] = this
@updateDisplayBuffer()
@marker.onDidDestroy => @destroyed()
@marker.onDidChange ({isValid}) => @destroy() unless isValid
@marker.on 'destroyed', => @destroyed()
@marker.on 'changed', ({isValid}) => @destroy() unless isValid
# Returns whether this fold is contained within another fold
isInsideLargerFold: ->
+202 -302
Ver Arquivo
@@ -1,16 +1,13 @@
{basename, join} = require 'path'
{join} = require 'path'
_ = require 'underscore-plus'
{Subscriber} = require 'emissary'
EmitterMixin = require('emissary').Emitter
{Emitter} = require 'event-kit'
{Emitter, Subscriber} = require 'emissary'
fs = require 'fs-plus'
GitUtils = require 'git-utils'
{deprecate} = require 'grim'
Task = require './task'
# Extended: Represents the underlying git operations performed by Atom.
# Public: Represents the underlying git operations performed by Atom.
#
# This class shouldn't be instantiated directly but instead by accessing the
# `atom.project` global and calling `getRepo()`. Note that this will only be
@@ -28,25 +25,38 @@ Task = require './task'
# repo.getShortHead('vendor/path/to/a/submodule') # 'dead1234'
# ```
#
# ## Examples
# ## Example
#
# ### Logging the URL of the origin remote
#
# ```coffee
# ```coffeescript
# git = atom.project.getRepo()
# console.log git.getOriginUrl()
# ```
#
# ### Requiring in packages
# ## Requiring in packages
#
# ```coffee
# {GitRepository} = require 'atom'
# {Git} = require 'atom'
# ```
module.exports =
class GitRepository
EmitterMixin.includeInto(this)
class Git
Emitter.includeInto(this)
Subscriber.includeInto(this)
# Public: Creates a new Git instance.
#
# path - The path to the Git repository to open.
# options - An object with the following keys (default: {}):
# :refreshOnWindowFocus - `true` to refresh the index and statuses when the
# window is focused.
#
# Returns a Git instance or null if the repository could not be opened.
@open: (path, options) ->
return null unless path
try
new Git(path, options)
catch
null
@exists: (path) ->
if git = @open(path)
git.destroy()
@@ -54,27 +64,7 @@ class GitRepository
else
false
###
Section: Construction and Destruction
###
# Public: Creates a new GitRepository instance.
#
# * `path` The {String} path to the Git repository to open.
# * `options` An optinal {Object} with the following keys:
# * `refreshOnWindowFocus` A {Boolean}, `true` to refresh the index and
# statuses when the window is focused.
#
# Returns a {GitRepository} instance or `null` if the repository could not be opened.
@open: (path, options) ->
return null unless path
try
new GitRepository(path, options)
catch
null
constructor: (path, options={}) ->
@emitter = new Emitter
@repo = GitUtils.open(path)
unless @repo?
throw new Error("No Git repository found searching path: #{path}")
@@ -96,10 +86,15 @@ class GitRepository
if @project?
@subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer)
# Public: Destroy this {GitRepository} object.
#
# This destroys any tasks and subscriptions and releases the underlying
# libgit2 repository handle.
# Subscribes to buffer events.
subscribeToBuffer: (buffer) ->
@subscribe buffer, 'saved reloaded path-changed', =>
if path = buffer.getPath()
@getPathStatus(path)
@subscribe buffer, 'destroyed', => @unsubscribe(buffer)
# Public: Destroy this `Git` object. This destroys any tasks and subscriptions
# and releases the underlying libgit2 repository handle.
destroy: ->
if @statusTask?
@statusTask.terminate()
@@ -111,48 +106,16 @@ class GitRepository
@unsubscribe()
###
Section: Event Subscription
###
# Returns the corresponding {Repository}
getRepo: (path) ->
if @repo?
@repo.submoduleForPath(path) ? @repo
else
throw new Error("Repository has been destroyed")
# Public: Invoke the given callback when a specific file's status has
# changed. When a file is updated, reloaded, etc, and the status changes, this
# will be fired.
#
# * `callback` {Function}
# * `event` {Object}
# * `path` {String} the old parameters the decoration used to have
# * `pathStatus` {Number} representing the status. This value can be passed to
# {::isStatusModified} or {::isStatusNew} to get more information.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatus: (callback) ->
@emitter.on 'did-change-status', callback
# Public: Invoke the given callback when a multiple files' statuses have
# changed. For example, on window focus, the status of all the paths in the
# repo is checked. If any of them have changed, this will be fired. Call
# {::getPathStatus(path)} to get the status for your path of choice.
#
# * `callback` {Function}
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatuses: (callback) ->
@emitter.on 'did-change-statuses', callback
on: (eventName) ->
switch eventName
when 'status-changed'
deprecate 'Use GitRepository::onDidChangeStatus instead'
when 'statuses-changed'
deprecate 'Use GitRepository::onDidChangeStatuses instead'
else
deprecate 'GitRepository::on is deprecated. Use event subscription methods instead.'
EmitterMixin::on.apply(this, arguments)
###
Section: Repository Details
###
# Reread the index to update any values that have changed since the
# last time the index was read.
refreshIndex: -> @getRepo().refreshIndex()
# Public: Returns the {String} path of the repository.
getPath: ->
@@ -161,136 +124,9 @@ class GitRepository
# Public: Returns the {String} working directory path of the repository.
getWorkingDirectory: -> @getRepo().getWorkingDirectory()
# Public: Returns true if at the root, false if in a subfolder of the
# repository.
isProjectAtRoot: ->
@projectAtRoot ?= @project?.relativize(@getWorkingDirectory()) is ''
# Public: Makes a path relative to the repository's working directory.
relativize: (path) -> @getRepo().relativize(path)
# Public: Returns true if the given branch exists.
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
# Public: Retrieves a shortened version of the HEAD reference value.
#
# This removes the leading segments of `refs/heads`, `refs/tags`, or
# `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7
# characters.
#
# * `path` An optional {String} path in the repository to get this information
# for, only needed if the repository contains submodules.
#
# Returns a {String}.
getShortHead: (path) -> @getRepo(path).getShortHead()
# Public: Is the given path a submodule in the repository?
#
# * `path` The {String} path to check.
#
# Returns a {Boolean}.
isSubmodule: (path) ->
return false unless path
repo = @getRepo(path)
if repo.isSubmodule(repo.relativize(path))
true
else
# Check if the path is a working directory in a repo that isn't the root.
repo isnt @getRepo() and repo.relativize(join(path, 'dir')) is 'dir'
# Public: Returns the number of commits behind the current branch is from the
# its upstream remote branch.
#
# * `reference` The {String} branch reference name.
# * `path` The {String} path in the repository to get this information for,
# only needed if the repository contains submodules.
getAheadBehindCount: (reference, path) ->
@getRepo(path).getAheadBehindCount(reference)
# Public: Get the cached ahead/behind commit counts for the current branch's
# upstream branch.
#
# * `path` An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
#
# Returns an {Object} with the following keys:
# * `ahead` The {Number} of commits ahead.
# * `behind` The {Number} of commits behind.
getCachedUpstreamAheadBehindCount: (path) ->
@getRepo(path).upstream ? @upstream
# Public: Returns the git configuration value specified by the key.
#
# * `path` An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key)
# Public: Returns the origin url of the repository.
#
# * `path` (optional) {String} path in the repository to get this information
# for, only needed if the repository has submodules.
getOriginUrl: (path) -> @getConfigValue('remote.origin.url', path)
# Public: Returns the upstream branch for the current HEAD, or null if there
# is no upstream branch for the current HEAD.
#
# * `path` An optional {String} path in the repo to get this information for,
# only needed if the repository contains submodules.
#
# Returns a {String} branch name such as `refs/remotes/origin/master`.
getUpstreamBranch: (path) -> @getRepo(path).getUpstreamBranch()
# Public: Gets all the local and remote references.
#
# * `path` An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
#
# Returns an {Object} with the following keys:
# * `heads` An {Array} of head reference names.
# * `remotes` An {Array} of remote reference names.
# * `tags` An {Array} of tag reference names.
getReferences: (path) -> @getRepo(path).getReferences()
# Public: Returns the current {String} SHA for the given reference.
#
# * `reference` The {String} reference to get the target of.
# * `path` An optional {String} path in the repo to get the reference target
# for. Only needed if the repository contains submodules.
getReferenceTarget: (reference, path) ->
@getRepo(path).getReferenceTarget(reference)
###
Section: Reading Status
###
# Public: Returns true if the given path is modified.
isPathModified: (path) -> @isStatusModified(@getPathStatus(path))
# Public: Returns true if the given path is new.
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
# Public: Is the given path ignored?
#
# Returns a {Boolean}.
isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path))
# Public: Get the status of a directory in the repository's working directory.
#
# * `path` The {String} path to check.
#
# Returns a {Number} representing the status. This value can be passed to
# {::isStatusModified} or {::isStatusNew} to get more information.
getDirectoryStatus: (directoryPath) ->
directoryPath = "#{@relativize(directoryPath)}/"
directoryStatus = 0
for path, status of @statuses
directoryStatus |= status if path.indexOf(directoryPath) is 0
directoryStatus
# Public: Get the status of a single path in the repository.
#
# `path` A {String} repository-relative path.
# path - A {String} repository-relative path.
#
# Returns a {Number} representing the status. This value can be passed to
# {::isStatusModified} or {::isStatusNew} to get more information.
@@ -306,75 +142,56 @@ class GitRepository
delete @statuses[relativePath]
if currentPathStatus isnt pathStatus
@emit 'status-changed', path, pathStatus
@emitter.emit 'did-change-status', {path, pathStatus}
pathStatus
# Public: Get the cached status for the given path.
# Public: Is the given path ignored?
#
# * `path` A {String} path in the repository, relative or absolute.
#
# Returns a status {Number} or null if the path is not in the cache.
getCachedPathStatus: (path) ->
@statuses[@relativize(path)]
# Returns a {Boolean}.
isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path))
# Public: Returns true if the given status indicates modification.
isStatusModified: (status) -> @getRepo().isStatusModified(status)
# Public: Returns true if the given path is modified.
isPathModified: (path) -> @isStatusModified(@getPathStatus(path))
# Public: Returns true if the given status indicates a new path.
isStatusNew: (status) -> @getRepo().isStatusNew(status)
###
Section: Retrieving Diffs
###
# Public: Returns true if the given path is new.
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
# Public: Retrieves the number of lines added and removed to a path.
#
# This compares the working directory contents of the path to the `HEAD`
# version.
#
# * `path` The {String} path to check.
#
# Returns an {Object} with the following keys:
# * `added` The {Number} of added lines.
# * `deleted` The {Number} of deleted lines.
getDiffStats: (path) ->
repo = @getRepo(path)
repo.getDiffStats(repo.relativize(path))
# Public: Returns true if at the root, false if in a subfolder of the
# repository.
isProjectAtRoot: ->
@projectAtRoot ?= @project?.relativize(@getWorkingDirectory()) is ''
# Public: Retrieves the line diffs comparing the `HEAD` version of the given
# path and the given text.
#
# * `path` The {String} path relative to the repository.
# * `text` The {String} to compare against the `HEAD` contents
#
# Returns an {Array} of hunk {Object}s with the following keys:
# * `oldStart` The line {Number} of the old hunk.
# * `newStart` The line {Number} of the new hunk.
# * `oldLines` The {Number} of lines in the old hunk.
# * `newLines` The {Number} of lines in the new hunk
getLineDiffs: (path, text) ->
# Ignore eol of line differences on windows so that files checked in as
# LF don't report every line modified when the text contains CRLF endings.
options = ignoreEolWhitespace: process.platform is 'win32'
repo = @getRepo(path)
repo.getLineDiffs(repo.relativize(path), text, options)
# Public: Makes a path relative to the repository's working directory.
relativize: (path) -> @getRepo().relativize(path)
###
Section: Checking Out
###
# Public: Retrieves a shortened version of the HEAD reference value.
#
# This removes the leading segments of `refs/heads`, `refs/tags`, or
# `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7
# characters.
#
# path - An optional {String} path in the repository to get this information
# for, only needed if the repository contains submodules.
#
# Returns a {String}.
getShortHead: (path) -> @getRepo(path).getShortHead()
# Public: Restore the contents of a path in the working directory and index
# to the version at `HEAD`.
#
# This is essentially the same as running:
#
# ```sh
# git reset HEAD -- <path>
# git checkout HEAD -- <path>
# ```
# git reset HEAD -- <path>
# git checkout HEAD -- <path>
# ```
#
# * `path` The {String} path to checkout.
# path - The {String} path to checkout.
#
# Returns a {Boolean} that's true if the method was successful.
checkoutHead: (path) ->
@@ -385,60 +202,145 @@ class GitRepository
# Public: Checks out a branch in your repository.
#
# * `reference` The {String} reference to checkout.
# * `create` A {Boolean} value which, if true creates the new reference if
# it doesn't exist.
# reference - The String reference to checkout
# create - A Boolean value which, if true creates the new reference if it
# doesn't exist.
#
# Returns a Boolean that's true if the method was successful.
checkoutReference: (reference, create) ->
@getRepo().checkoutReference(reference, create)
###
Section: Private
###
# Public: Retrieves the number of lines added and removed to a path.
#
# This compares the working directory contents of the path to the `HEAD`
# version.
#
# path - The {String} path to check.
#
# Returns an {Object} with the following keys:
# :added - The {Number} of added lines.
# :deleted - The {Number} of deleted lines.
getDiffStats: (path) ->
repo = @getRepo(path)
repo.getDiffStats(repo.relativize(path))
# Subscribes to buffer events.
subscribeToBuffer: (buffer) ->
getBufferPathStatus = =>
if path = buffer.getPath()
@getPathStatus(path)
# Public: Is the given path a submodule in the repository?
#
# path - The {String} path to check.
#
# Returns a {Boolean}.
isSubmodule: (path) ->
return false unless path
@subscribe buffer.onDidSave(getBufferPathStatus)
@subscribe buffer.onDidReload(getBufferPathStatus)
@subscribe buffer.onDidChangePath(getBufferPathStatus)
@subscribe buffer.onDidDestroy => @unsubscribe(buffer)
# Subscribes to editor view event.
checkoutHeadForEditor: (editor) ->
filePath = editor.getPath()
return unless filePath
fileName = basename(filePath)
checkoutHead = =>
editor.buffer.reload() if editor.buffer.isModified()
@checkoutHead(filePath)
if atom.config.get('editor.confirmCheckoutHeadRevision')
atom.confirm
message: 'Confirm Checkout HEAD Revision'
detailedMessage: "Are you sure you want to discard all changes to \"#{fileName}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null
repo = @getRepo(path)
if repo.isSubmodule(repo.relativize(path))
true
else
checkoutHead()
# Check if the path is a working directory in a repo that isn't the root.
repo isnt @getRepo() and repo.relativize(join(path, 'dir')) is 'dir'
# Returns the corresponding {Repository}
getRepo: (path) ->
if @repo?
@repo.submoduleForPath(path) ? @repo
else
throw new Error("Repository has been destroyed")
# Public: Get the status of a directory in the repository's working directory.
#
# path - The {String} path to check.
#
# Returns a {Number} representing the status. This value can be passed to
# {::isStatusModified} or {::isStatusNew} to get more information.
getDirectoryStatus: (directoryPath) ->
directoryPath = "#{@relativize(directoryPath)}/"
directoryStatus = 0
for path, status of @statuses
directoryStatus |= status if path.indexOf(directoryPath) is 0
directoryStatus
# Reread the index to update any values that have changed since the
# last time the index was read.
refreshIndex: -> @getRepo().refreshIndex()
# Public: Retrieves the line diffs comparing the `HEAD` version of the given
# path and the given text.
#
# path - The {String} path relative to the repository.
# text - The {String} to compare against the `HEAD` contents
#
# Returns an {Array} of hunk {Object}s with the following keys:
# :oldStart - The line {Number} of the old hunk.
# :newStart - The line {Number} of the new hunk.
# :oldLines - The {Number} of lines in the old hunk.
# :newLines - The {Number} of lines in the new hunk
getLineDiffs: (path, text) ->
# Ignore eol of line differences on windows so that files checked in as
# LF don't report every line modified when the text contains CRLF endings.
options = ignoreEolWhitespace: process.platform is 'win32'
repo = @getRepo(path)
repo.getLineDiffs(repo.relativize(path), text, options)
# Public: Returns the git configuration value specified by the key.
#
# path - An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key)
# Public: Returns the origin url of the repository.
#
# path - An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
getOriginUrl: (path) -> @getConfigValue('remote.origin.url', path)
# Public: Returns the upstream branch for the current HEAD, or null if there
# is no upstream branch for the current HEAD.
#
# path - An optional {String} path in the repo to get this information for,
# only needed if the repository contains submodules.
#
# Returns a {String} branch name such as `refs/remotes/origin/master`.
getUpstreamBranch: (path) -> @getRepo(path).getUpstreamBranch()
# Public: Returns the current SHA for the given reference.
#
# reference - The {String} reference to get the target of.
# path - An optional {String} path in the repo to get the reference target
# for. Only needed if the repository contains submodules.
getReferenceTarget: (reference, path) ->
@getRepo(path).getReferenceTarget(reference)
# Public: Gets all the local and remote references.
#
# path - An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
#
# Returns an {Object} with the following keys:
# :heads - An {Array} of head reference names.
# :remotes - An {Array} of remote reference names.
# :tags - An {Array} of tag reference names.
getReferences: (path) -> @getRepo(path).getReferences()
# Public: Returns the number of commits behind the current branch is from the
# its upstream remote branch.
#
# reference - The {String} branch reference name.
# path - The {String} path in the repository to get this information for,
# only needed if the repository contains submodules.
getAheadBehindCount: (reference, path) ->
@getRepo(path).getAheadBehindCount(reference)
# Public: Get the cached ahead/behind commit counts for the current branch's
# upstream branch.
#
# path - An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
#
# Returns an {Object} with the following keys:
# :ahead - The {Number} of commits ahead.
# :behind - The {Number} of commits behind.
getCachedUpstreamAheadBehindCount: (path) ->
@getRepo(path).upstream ? @upstream
# Public: Get the cached status for the given path.
#
# path - A {String} path in the repository, relative or absolute.
#
# Returns a status {Number} or null if the path is not in the cache.
getCachedPathStatus: (path) ->
@statuses[@relativize(path)]
# Public: Returns true if the given branch exists.
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
# Refreshes the current git status in an outside process and asynchronously
# updates the relevant properties.
@@ -460,6 +362,4 @@ class GitRepository
for submodulePath, submoduleRepo of @getRepo().submodules
submoduleRepo.upstream = submodules[submodulePath]?.upstream ? {ahead: 0, behind: 0}
unless statusesUnchanged
@emit 'statuses-changed'
@emitter.emit 'did-change-statuses'
@emit 'statuses-changed' unless statusesUnchanged
+1 -9
Ver Arquivo
@@ -21,7 +21,7 @@ GutterComponent = React.createClass
if gutterBackgroundColor isnt 'rbga(0, 0, 0, 0)'
backgroundColor = gutterBackgroundColor
div className: 'gutter', onClick: @onClick, onMouseDown: @onMouseDown,
div className: 'gutter', onClick: @onClick, onMouseDown: onMouseDown,
div className: 'line-numbers', ref: 'lineNumbers', style:
height: Math.max(scrollHeight, scrollViewHeight)
WebkitTransform: @getTransform()
@@ -215,14 +215,6 @@ GutterComponent = React.createClass
lineNumberNodeForScreenRow: (screenRow) ->
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
onMouseDown: (event) ->
{editor} = @props
{target} = event
lineNumber = target.parentNode
unless target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
@props.onMouseDown(event)
onClick: (event) ->
{editor} = @props
{target} = event
+275
Ver Arquivo
@@ -0,0 +1,275 @@
{View, $, $$, $$$} = require './space-pen-extensions'
{Range} = require 'text-buffer'
_ = require 'underscore-plus'
# Represents the portion of the {EditorView} containing row numbers.
#
# The gutter also indicates if rows are folded.
module.exports =
class GutterView extends View
@content: ->
@div class: 'gutter', =>
@div outlet: 'lineNumbers', class: 'line-numbers'
firstScreenRow: null
lastScreenRow: null
initialize: ->
@elementBuilder = document.createElement('div')
afterAttach: (onDom) ->
return if @attached or not onDom
@attached = true
highlightLines = => @highlightLines()
@getEditorView().on 'cursor:moved', highlightLines
@getEditorView().on 'selection:changed', highlightLines
@on 'mousedown', (e) => @handleMouseEvents(e)
beforeRemove: ->
$(document).off(".gutter-#{@getEditorView().id}")
handleMouseEvents: (e) ->
editorView = @getEditorView()
editor = @getEditor()
startRow = editorView.screenPositionFromMouseEvent(e).row
if e.shiftKey
editor.selectToScreenPosition([startRow + 1, 0])
return
else
editor.getSelection().setScreenRange([[startRow, 0], [startRow, 0]])
moveHandler = (e) =>
start = startRow
end = editorView.screenPositionFromMouseEvent(e).row
if end > start then end++ else start++
editor.getSelection().setScreenRange([[start, 0], [end, 0]])
$(document).on "mousemove.gutter-#{editorView.id}", moveHandler
$(document).one "mouseup.gutter-#{editorView.id}", => $(document).off 'mousemove', moveHandler
# Retrieves the containing {EditorView}.
#
# Returns an {EditorView}.
getEditorView: ->
@parentView
getEditor: ->
@getEditorView().getEditor()
# Defines whether to show the gutter or not.
#
# showLineNumbers - A {Boolean} which, if `false`, hides the gutter
setShowLineNumbers: (showLineNumbers) ->
if showLineNumbers then @lineNumbers.show() else @lineNumbers.hide()
# Get all the line-number divs.
#
# Returns a list of {HTMLElement}s.
getLineNumberElements: ->
@lineNumbers[0].children
# Get all the line-number divs.
#
# Returns a list of {HTMLElement}s.
getLineNumberElementsForClass: (klass) ->
@lineNumbers[0].getElementsByClassName(klass)
# Get a single line-number div.
#
# * bufferRow: 0 based line number
#
# Returns a list of {HTMLElement}s that correspond to the bufferRow. More than
# one in the list indicates a wrapped line.
getLineNumberElement: (bufferRow) ->
@getLineNumberElementsForClass("line-number-#{bufferRow}")
# Add a class to all line-number divs.
#
# * klass: string class name
#
# Returns true if the class was added to any lines
addClassToAllLines: (klass)->
elements = @getLineNumberElements()
el.classList.add(klass) for el in elements
!!elements.length
# Remove a class from all line-number divs.
#
# * klass: string class name. Can only be one class name. i.e. 'my-class'
#
# Returns true if the class was removed from any lines
removeClassFromAllLines: (klass)->
# This is faster than calling $.removeClass on all lines, and faster than
# making a new array and iterating through it.
elements = @getLineNumberElementsForClass(klass)
willRemoveClasses = !!elements.length
elements[0].classList.remove(klass) while elements.length > 0
willRemoveClasses
# Add a class to a single line-number div
#
# * bufferRow: 0 based line number
# * klass: string class name
#
# Returns true if there were lines the class was added to
addClassToLine: (bufferRow, klass)->
elements = @getLineNumberElement(bufferRow)
el.classList.add(klass) for el in elements
!!elements.length
# Remove a class from a single line-number div
#
# * bufferRow: 0 based line number
# * klass: string class name
#
# Returns true if there were lines the class was removed from
removeClassFromLine: (bufferRow, klass)->
classesRemoved = false
elements = @getLineNumberElement(bufferRow)
for el in elements
hasClass = el.classList.contains(klass)
classesRemoved |= hasClass
el.classList.remove(klass) if hasClass
classesRemoved
updateLineNumbers: (changes, startScreenRow, endScreenRow) ->
# Check if we have something already rendered that overlaps the requested range
updateAllLines = not (startScreenRow? and endScreenRow?)
updateAllLines |= endScreenRow <= @firstScreenRow or startScreenRow >= @lastScreenRow
unless updateAllLines
for change in changes
if change.screenDelta or change.bufferDelta
updateAllLines = true
break
# Rebuild the entire gutter if a change added or removed lines
if updateAllLines
@lineNumbers[0].innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
# Handle changes that didn't add/remove lines more optimally
else
if startScreenRow < @firstScreenRow
@prependLineElements(@buildLineElements(startScreenRow, @firstScreenRow-1))
else if startScreenRow != @firstScreenRow
@removeLineElements(startScreenRow - @firstScreenRow)
if endScreenRow > @lastScreenRow
@appendLineElements(@buildLineElements(@lastScreenRow+1, endScreenRow))
else if endScreenRow != @lastScreenRow
@removeLineElements(endScreenRow - @lastScreenRow)
@updateFoldableClasses(changes)
@firstScreenRow = startScreenRow
@lastScreenRow = endScreenRow
@highlightedRows = null
@highlightLines()
prependLineElements: (lineElements) ->
anchor = @lineNumbers[0].children[0]
return appendLineElements(lineElements) unless anchor?
@lineNumbers[0].insertBefore(lineElements[0], anchor) while lineElements.length > 0
null # defeat coffeescript array return
appendLineElements: (lineElements) ->
@lineNumbers[0].appendChild(lineElements[0]) while lineElements.length > 0
null # defeat coffeescript array return
removeLineElements: (numberOfElements) ->
children = @getLineNumberElements()
# children is a live NodeList, so remove from the desired end {numberOfElements} times
if numberOfElements < 0
@lineNumbers[0].removeChild(children[children.length-1]) while numberOfElements++
else if numberOfElements > 0
@lineNumbers[0].removeChild(children[0]) while numberOfElements--
null # defeat coffeescript array return
buildLineElements: (startScreenRow, endScreenRow) ->
@elementBuilder.innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
@elementBuilder.children
buildLineElementsHtml: (startScreenRow, endScreenRow) =>
editor = @getEditor()
maxDigits = editor.getLineCount().toString().length
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
html = ''
for row in rows
if row is lastRow
rowValue = ''
else
rowValue = (row + 1).toString()
classes = "line-number line-number-#{row}"
classes += ' foldable' if row isnt lastRow and editor.isFoldableAtBufferRow(row)
classes += ' folded' if editor.isFoldedAtBufferRow(row)
rowValuePadding = _.multiplyString('&nbsp;', maxDigits - rowValue.length)
html += """<div class="#{classes}" data-buffer-row=#{row}>#{rowValuePadding}#{rowValue}<div class="icon-right"></div></div>"""
lastRow = row
html
# Called to update the 'foldable' class of line numbers when there's
# a change to the display buffer that doesn't regenerate all the line numbers
# anyway.
updateFoldableClasses: (changes) ->
editor = @getEditor()
for {start, end} in changes when start <= @lastScreenRow and end >= @firstScreenRow
startScreenRow = Math.max(start - 1, @firstScreenRow)
endScreenRow = Math.min(end + 1, @lastScreenRow)
lastBufferRow = null
for bufferRow in editor.bufferRowsForScreenRows(startScreenRow, endScreenRow) when bufferRow isnt lastBufferRow
lastBufferRow = bufferRow
if lineNumberElement = @getLineNumberElement(bufferRow)[0]
if editor.isFoldableAtBufferRow(bufferRow)
lineNumberElement.classList.add('foldable')
else
lineNumberElement.classList.remove('foldable')
removeLineHighlights: ->
return unless @highlightedLineNumbers
for line in @highlightedLineNumbers
line.classList.remove('cursor-line')
line.classList.remove('cursor-line-no-selection')
@highlightedLineNumbers = null
addLineHighlight: (row, emptySelection) ->
return if row < @firstScreenRow or row > @lastScreenRow
@highlightedLineNumbers ?= []
if highlightedLineNumber = @lineNumbers[0].children[row - @firstScreenRow]
highlightedLineNumber.classList.add('cursor-line')
highlightedLineNumber.classList.add('cursor-line-no-selection') if emptySelection
@highlightedLineNumbers.push(highlightedLineNumber)
highlightLines: ->
editor = @getEditor()
return unless editor?.isAlive()
if editor.getSelection().isEmpty()
row = editor.getCursorScreenPosition().row
rowRange = new Range([row, 0], [row, 0])
return if @selectionEmpty and @highlightedRows?.isEqual(rowRange)
@removeLineHighlights()
@addLineHighlight(row, true)
@highlightedRows = rowRange
@selectionEmpty = true
else
selectedRows = editor.getSelection().getScreenRange()
endRow = selectedRows.end.row
endRow-- if selectedRows.end.column is 0
selectedRows = new Range([selectedRows.start.row, 0], [endRow, 0])
return if not @selectionEmpty and @highlightedRows?.isEqual(selectedRows)
@removeLineHighlights()
for row in [selectedRows.start.row..selectedRows.end.row]
@addLineHighlight(row, false)
@highlightedRows = selectedRows
@selectionEmpty = false
+2 -3
Ver Arquivo
@@ -22,12 +22,11 @@ HighlightComponent = React.createClass
{editor, decoration} = @props
if decoration.id?
@decoration = editor.decorationForId(decoration.id)
@decorationDisposable = @decoration.onDidFlash @startFlashAnimation
@decoration.on 'flash', @startFlashAnimation
@startFlashAnimation()
componentWillUnmount: ->
@decorationDisposable?.dispose()
@decorationDisposable = null
@decoration?.off 'flash', @startFlashAnimation
startFlashAnimation: ->
return unless flash = @decoration.consumeNextFlash()
+1 -5
Ver Arquivo
@@ -4,13 +4,9 @@ KeymapManager = require 'atom-keymap'
CSON = require 'season'
{jQuery} = require 'space-pen'
KeymapManager::onDidLoadBundledKeymaps = (callback) ->
@emitter.on 'did-load-bundled-keymaps', callback
KeymapManager::loadBundledKeymaps = ->
@loadKeymap(path.join(@resourcePath, 'keymaps'))
@emit 'bundled-keymaps-loaded'
@emitter.emit 'did-load-bundled-keymaps'
@emit('bundled-keymaps-loaded')
KeymapManager::getUserKeymapPath = ->
if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap'))

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais