mirror of
https://github.com/atom/atom.git
synced 2026-01-22 21:38:10 -05:00
Merge remote-tracking branch 'origin/master' into squirrel-installer
This commit is contained in:
11
README.md
11
README.md
@@ -25,6 +25,17 @@ 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)
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.93.2"
|
||||
"atom-package-manager": "0.96.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,8 +229,8 @@ module.exports = (grunt) ->
|
||||
|
||||
ciTasks = ['output-disk-space', 'download-atom-shell', 'build']
|
||||
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
|
||||
ciTasks.push('mkdeb') if process.platform is 'linux'
|
||||
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')
|
||||
ciTasks.push('create-installer') if process.platform is 'win32'
|
||||
|
||||
@@ -58,11 +58,12 @@ getAssets = ->
|
||||
]
|
||||
when 'linux'
|
||||
buildDir = grunt.config.get('atom.buildDir')
|
||||
sourcePath = fs.listSync(buildDir, ['.deb'])[0]
|
||||
if process.arch is 'ia32g'
|
||||
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)
|
||||
|
||||
@@ -53,8 +53,9 @@ 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)
|
||||
|
||||
@@ -143,6 +143,8 @@
|
||||
# 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'
|
||||
|
||||
32
package.json
32
package.json
@@ -28,7 +28,7 @@
|
||||
"delegato": "^1",
|
||||
"emissary": "^1.3.1",
|
||||
"event-kit": "0.7.2",
|
||||
"first-mate": "^2.1.1",
|
||||
"first-mate": "^2.1.2",
|
||||
"fs-plus": "^2.2.6",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
@@ -36,13 +36,13 @@
|
||||
"grim": "0.12.0",
|
||||
"guid": "0.0.10",
|
||||
"jasmine-tagged": "^1.1.2",
|
||||
"less-cache": "0.14.0",
|
||||
"less-cache": "0.15.0",
|
||||
"mixto": "^1",
|
||||
"mkdirp": "0.3.5",
|
||||
"nslog": "^1.0.1",
|
||||
"oniguruma": "^3.0.4",
|
||||
"optimist": "0.4.0",
|
||||
"pathwatcher": "^2.1.2",
|
||||
"pathwatcher": "^2.1.3",
|
||||
"property-accessors": "^1",
|
||||
"q": "^1.0.1",
|
||||
"random-words": "0.0.1",
|
||||
@@ -57,7 +57,7 @@
|
||||
"serializable": "^1",
|
||||
"space-pen": "3.4.7",
|
||||
"temp": "0.7.0",
|
||||
"text-buffer": "^3.2.4",
|
||||
"text-buffer": "^3.2.6",
|
||||
"theorist": "^1.0.2",
|
||||
"underscore-plus": "^1.5.1",
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
@@ -78,7 +78,7 @@
|
||||
"background-tips": "0.16.0",
|
||||
"bookmarks": "0.28.0",
|
||||
"bracket-matcher": "0.55.0",
|
||||
"command-palette": "0.24.0",
|
||||
"command-palette": "0.25.0",
|
||||
"deprecation-cop": "0.10.0",
|
||||
"dev-live-reload": "0.34.0",
|
||||
"exception-reporting": "0.20.0",
|
||||
@@ -87,7 +87,7 @@
|
||||
"fuzzy-finder": "0.58.0",
|
||||
"git-diff": "0.39.0",
|
||||
"go-to-line": "0.25.0",
|
||||
"grammar-selector": "0.29.0",
|
||||
"grammar-selector": "0.34.0",
|
||||
"image-view": "0.36.0",
|
||||
"incompatible-packages": "0.9.0",
|
||||
"keybinding-resolver": "0.20.0",
|
||||
@@ -97,21 +97,21 @@
|
||||
"open-on-github": "0.30.0",
|
||||
"package-generator": "0.31.0",
|
||||
"release-notes": "0.36.0",
|
||||
"settings-view": "0.142.0",
|
||||
"settings-view": "0.145.0",
|
||||
"snippets": "0.52.0",
|
||||
"spell-check": "0.42.0",
|
||||
"status-bar": "0.44.0",
|
||||
"status-bar": "0.45.0",
|
||||
"styleguide": "0.30.0",
|
||||
"symbols-view": "0.63.0",
|
||||
"tabs": "0.51.0",
|
||||
"symbols-view": "0.65.0",
|
||||
"tabs": "0.52.0",
|
||||
"timecop": "0.22.0",
|
||||
"tree-view": "0.125.0",
|
||||
"tree-view": "0.126.0",
|
||||
"update-package-dependencies": "0.6.0",
|
||||
"welcome": "0.18.0",
|
||||
"whitespace": "0.25.0",
|
||||
"wrap-guide": "0.22.0",
|
||||
"language-c": "0.28.0",
|
||||
"language-coffee-script": "0.30.0",
|
||||
"language-coffee-script": "0.34.0",
|
||||
"language-css": "0.17.0",
|
||||
"language-gfm": "0.50.0",
|
||||
"language-git": "0.9.0",
|
||||
@@ -119,7 +119,7 @@
|
||||
"language-html": "0.26.0",
|
||||
"language-hyperlink": "0.12.0",
|
||||
"language-java": "0.11.0",
|
||||
"language-javascript": "0.39.0",
|
||||
"language-javascript": "0.40.0",
|
||||
"language-json": "0.8.0",
|
||||
"language-less": "0.15.0",
|
||||
"language-make": "0.12.0",
|
||||
@@ -129,16 +129,16 @@
|
||||
"language-php": "0.16.0",
|
||||
"language-property-list": "0.7.0",
|
||||
"language-python": "0.19.0",
|
||||
"language-ruby": "0.37.0",
|
||||
"language-ruby": "0.38.0",
|
||||
"language-ruby-on-rails": "0.18.0",
|
||||
"language-sass": "0.21.0",
|
||||
"language-shellscript": "0.8.0",
|
||||
"language-source": "0.8.0",
|
||||
"language-sql": "0.10.0",
|
||||
"language-sql": "0.11.0",
|
||||
"language-text": "0.6.0",
|
||||
"language-todo": "0.11.0",
|
||||
"language-toml": "0.12.0",
|
||||
"language-xml": "0.20.0",
|
||||
"language-xml": "0.21.0",
|
||||
"language-yaml": "0.17.0"
|
||||
},
|
||||
"private": true,
|
||||
|
||||
@@ -8,6 +8,23 @@ 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", ->
|
||||
|
||||
126
spec/command-registry-spec.coffee
Normal file
126
spec/command-registry-spec.coffee
Normal file
@@ -0,0 +1,126 @@
|
||||
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'}
|
||||
]
|
||||
@@ -1011,7 +1011,7 @@ describe "DisplayBuffer", ->
|
||||
buffer.getMarker(marker2.id).destroy()
|
||||
expect(destroyedHandler).toHaveBeenCalled()
|
||||
|
||||
describe "DisplayBufferMarker::copy(attributes)", ->
|
||||
describe "Marker::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.getAttributes()).toEqual a: 1, b: 2
|
||||
expect(marker2.getAttributes()).toEqual a: 1, b: 3
|
||||
expect(marker1.getProperties()).toEqual a: 1, b: 2
|
||||
expect(marker2.getProperties()).toEqual a: 1, b: 3
|
||||
|
||||
describe "DisplayBufferMarker::getPixelRange()", ->
|
||||
describe "Marker::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]])
|
||||
|
||||
|
||||
@@ -197,6 +197,50 @@ 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
|
||||
|
||||
@@ -2195,7 +2239,7 @@ describe "EditorComponent", ->
|
||||
editor.setSoftWrapped(true)
|
||||
|
||||
callingOrder = []
|
||||
editor.onDidChangeScreenLines -> callingOrder.push 'screen-lines-changed'
|
||||
editor.onDidChange -> 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()
|
||||
|
||||
@@ -2330,6 +2330,16 @@ describe "Editor", ->
|
||||
expect(buffer.lineForRow(5)).toMatch /^\t\t\t$/
|
||||
expect(editor.getCursorBufferPosition()).toEqual [5, 3]
|
||||
|
||||
describe "when the difference between the suggested level of indentation and the current level of indentation is greater than 0 but less than 1", ->
|
||||
it "inserts one tab", ->
|
||||
editor.setSoftTabs(false)
|
||||
buffer.setText(" \ntest")
|
||||
editor.setCursorBufferPosition [1, 0]
|
||||
|
||||
editor.indent(autoIndent: true)
|
||||
expect(buffer.lineForRow(1)).toBe '\ttest'
|
||||
expect(editor.getCursorBufferPosition()).toEqual [1, 1]
|
||||
|
||||
describe "when the line's indent level is greater than the suggested level of indentation", ->
|
||||
describe "when 'softTabs' is true (the default)", ->
|
||||
it "moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", ->
|
||||
@@ -3229,6 +3239,13 @@ describe "Editor", ->
|
||||
editor.destroy()
|
||||
expect(buffer.getMarkerCount()).toBe 0
|
||||
|
||||
it "notifies ::onDidDestroy observers when the editor is destroyed", ->
|
||||
destroyObserverCalled = false
|
||||
editor.onDidDestroy -> destroyObserverCalled = true
|
||||
|
||||
editor.destroy()
|
||||
expect(destroyObserverCalled).toBe true
|
||||
|
||||
describe ".joinLines()", ->
|
||||
describe "when no text is selected", ->
|
||||
describe "when the line below isn't empty", ->
|
||||
@@ -3343,7 +3360,7 @@ describe "Editor", ->
|
||||
editor2.destroy()
|
||||
expect(editor.shouldPromptToSave()).toBeTruthy()
|
||||
|
||||
describe "when the edit session contains surrogate pair characters", ->
|
||||
describe "when the editor contains surrogate pair characters", ->
|
||||
it "correctly backspaces over them", ->
|
||||
editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97')
|
||||
editor.moveToBottom()
|
||||
@@ -3384,6 +3401,47 @@ describe "Editor", ->
|
||||
editor.moveLeft()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
||||
|
||||
describe "when the editor contains variation sequence character pairs", ->
|
||||
it "correctly backspaces over them", ->
|
||||
editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E')
|
||||
editor.moveToBottom()
|
||||
editor.backspace()
|
||||
expect(editor.getText()).toBe '\u2714\uFE0E\u2714\uFE0E'
|
||||
editor.backspace()
|
||||
expect(editor.getText()).toBe '\u2714\uFE0E'
|
||||
editor.backspace()
|
||||
expect(editor.getText()).toBe ''
|
||||
|
||||
it "correctly deletes over them", ->
|
||||
editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E')
|
||||
editor.moveToTop()
|
||||
editor.delete()
|
||||
expect(editor.getText()).toBe '\u2714\uFE0E\u2714\uFE0E'
|
||||
editor.delete()
|
||||
expect(editor.getText()).toBe '\u2714\uFE0E'
|
||||
editor.delete()
|
||||
expect(editor.getText()).toBe ''
|
||||
|
||||
it "correctly moves over them", ->
|
||||
editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E\n')
|
||||
editor.moveToTop()
|
||||
editor.moveRight()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 2]
|
||||
editor.moveRight()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 4]
|
||||
editor.moveRight()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 6]
|
||||
editor.moveRight()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
|
||||
editor.moveLeft()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 6]
|
||||
editor.moveLeft()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 4]
|
||||
editor.moveLeft()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 2]
|
||||
editor.moveLeft()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
||||
|
||||
describe ".setIndentationForBufferRow", ->
|
||||
describe "when the editor uses soft tabs but the row has hard tabs", ->
|
||||
it "only replaces whitespace characters", ->
|
||||
|
||||
@@ -7,6 +7,8 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
|
||||
|
||||
{TerminalReporter} = require 'jasmine-tagged'
|
||||
|
||||
disableFocusMethods() if process.env.JANKY_SHA1
|
||||
|
||||
TimeReporter = require './time-reporter'
|
||||
timeReporter = new TimeReporter()
|
||||
|
||||
@@ -38,3 +40,10 @@ 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
|
||||
|
||||
@@ -70,6 +70,30 @@ 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")
|
||||
console.log buffer.getLastRow()
|
||||
expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined()
|
||||
expect(languageMode.rowRangeForCommentAtBufferRow(1)).toBeUndefined()
|
||||
|
||||
buffer.setText("//this is a single line comment")
|
||||
console.log languageMode.isLineCommentedAtBufferRow(0)
|
||||
expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined()
|
||||
|
||||
describe "suggestedIndentForBufferRow", ->
|
||||
it "returns the suggested indentation based on auto-indent/outdent rules", ->
|
||||
expect(languageMode.suggestedIndentForBufferRow(0)).toBe 0
|
||||
|
||||
@@ -64,6 +64,7 @@ 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 = {}
|
||||
@@ -120,6 +121,8 @@ beforeEach ->
|
||||
addCustomMatchers(this)
|
||||
|
||||
afterEach ->
|
||||
atom.commands.clear()
|
||||
|
||||
atom.packages.deactivatePackages()
|
||||
atom.menu.template = []
|
||||
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
textUtils = require '../src/text-utils'
|
||||
|
||||
describe 'text utilities', ->
|
||||
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 '.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 '.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
|
||||
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
|
||||
|
||||
449
src/atom.coffee
449
src/atom.coffee
@@ -7,21 +7,22 @@ screen = require 'screen'
|
||||
shell = require 'shell'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
{deprecated} = require 'grim'
|
||||
{deprecate} = require 'grim'
|
||||
{Emitter} = require 'event-kit'
|
||||
{Model} = require 'theorist'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
{$} = require './space-pen-extensions'
|
||||
WindowEventHandler = require './window-event-handler'
|
||||
|
||||
# Public: Atom global for dealing with packages, themes, menus, and the window.
|
||||
# Essential: Atom global for dealing with packages, themes, menus, and the window.
|
||||
#
|
||||
# An instance of this class is always available as the `atom` global.
|
||||
module.exports =
|
||||
class Atom extends Model
|
||||
@version: 1 # Increment this when the serialization format changes
|
||||
|
||||
# Public: Load or create the Atom environment in the given mode.
|
||||
# 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.
|
||||
@@ -98,6 +99,10 @@ class Atom extends Model
|
||||
workspaceViewParentSelector: 'body'
|
||||
lastUncaughtError: null
|
||||
|
||||
###
|
||||
Section: Properties
|
||||
###
|
||||
|
||||
# Public: A {Clipboard} instance
|
||||
clipboard: null
|
||||
|
||||
@@ -113,6 +118,9 @@ class Atom extends Model
|
||||
# Public: A {KeymapManager} instance
|
||||
keymaps: null
|
||||
|
||||
# Public: A {CommandRegistry} instance
|
||||
commands: null
|
||||
|
||||
# Public: A {MenuManager} instance
|
||||
menu: null
|
||||
|
||||
@@ -134,13 +142,18 @@ class Atom extends Model
|
||||
# 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()
|
||||
|
||||
# Public: Sets up the basic services that should be available in all modes
|
||||
# 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.
|
||||
@@ -158,6 +171,7 @@ 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'
|
||||
@@ -179,6 +193,7 @@ 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})
|
||||
@@ -198,22 +213,172 @@ class Atom extends Model
|
||||
|
||||
@windowEventHandler = new WindowEventHandler
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClass: ->
|
||||
deprecated("Callers should be converted to use atom.deserializers")
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClasses: ->
|
||||
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
|
||||
|
||||
setBodyPlatformClass: ->
|
||||
document.body.classList.add("platform-#{process.platform}")
|
||||
###
|
||||
Section: Atom Details
|
||||
###
|
||||
|
||||
# Public: Get the current window
|
||||
# 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
|
||||
getCurrentWindow: ->
|
||||
@constructor.getCurrentWindow()
|
||||
|
||||
# Public: Get the dimensions of this window.
|
||||
# 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.
|
||||
#
|
||||
# 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}.
|
||||
@@ -227,7 +392,7 @@ class Atom extends Model
|
||||
maximized = browserWindow.isMaximized()
|
||||
{x, y, width, height, maximized}
|
||||
|
||||
# Public: Set the dimensions of the window.
|
||||
# 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
|
||||
@@ -284,41 +449,6 @@ class Atom extends Model
|
||||
dimensions = @getWindowDimensions()
|
||||
@state.windowDimensions = dimensions if @isValidDimensions(dimensions)
|
||||
|
||||
# Public: Get the load settings for the current window.
|
||||
#
|
||||
# Returns an {Object} containing all the load setting key/value pairs.
|
||||
getLoadSettings: ->
|
||||
@constructor.getLoadSettings()
|
||||
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
|
||||
startTime = Date.now()
|
||||
@project ?= @deserializers.deserialize(@state.project) ? new Project(path: @getLoadSettings().initialPath)
|
||||
@deserializeTimings.project = Date.now() - startTime
|
||||
|
||||
deserializeWorkspaceView: ->
|
||||
Workspace = require './workspace'
|
||||
WorkspaceView = require './workspace-view'
|
||||
|
||||
startTime = Date.now()
|
||||
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
|
||||
@workspaceView = new WorkspaceView(@workspace)
|
||||
@deserializeTimings.workspace = Date.now() - startTime
|
||||
|
||||
@keymaps.defaultTarget = @workspaceView[0]
|
||||
$(@workspaceViewParentSelector).append(@workspaceView)
|
||||
|
||||
deserializePackageStates: ->
|
||||
@packages.packageStates = @state.packageStates ? {}
|
||||
delete @state.packageStates
|
||||
|
||||
deserializeEditorWindow: ->
|
||||
@deserializeTimings = {}
|
||||
@deserializePackageStates()
|
||||
@deserializeProject()
|
||||
@deserializeWorkspaceView()
|
||||
|
||||
# Call this method when establishing a real application window.
|
||||
startEditorWindow: ->
|
||||
{resourcePath, safeMode} = @getLoadSettings()
|
||||
@@ -369,41 +499,17 @@ class Atom extends Model
|
||||
|
||||
@windowEventHandler?.unsubscribe()
|
||||
|
||||
loadThemes: ->
|
||||
@themes.load()
|
||||
###
|
||||
Section: Messaging the User
|
||||
###
|
||||
|
||||
watchThemes: ->
|
||||
@themes.onDidReloadAll =>
|
||||
# Only reload stylesheets from non-theme packages
|
||||
for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
|
||||
pack.reloadStylesheets?()
|
||||
null
|
||||
# Essential: Visually and audibly trigger a beep.
|
||||
beep: ->
|
||||
shell.beep() if @config.get('core.audioBeep')
|
||||
@workspaceView.trigger 'beep'
|
||||
@emitter.emit 'did-beep'
|
||||
|
||||
# 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.
|
||||
# * `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)
|
||||
|
||||
# Public: Open a confirm dialog.
|
||||
# Essential: A flexible way to open a dialog akin to an alert dialog.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
@@ -413,13 +519,13 @@ class Atom extends Model
|
||||
# detailedMessage: 'Be honest.'
|
||||
# buttons:
|
||||
# Good: -> window.alert('good to hear')
|
||||
# Bad: -> window.alert('bummer')
|
||||
# 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
|
||||
# * `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.
|
||||
@@ -443,77 +549,71 @@ class Atom extends Model
|
||||
callback = buttons[buttonLabels[chosen]]
|
||||
callback?()
|
||||
|
||||
showSaveDialog: (callback) ->
|
||||
callback(showSaveDialogSync())
|
||||
###
|
||||
Section: Managing the Dev Tools
|
||||
###
|
||||
|
||||
showSaveDialogSync: (defaultPath) ->
|
||||
defaultPath ?= @project?.getPath()
|
||||
currentWindow = @getCurrentWindow()
|
||||
dialog = remote.require('dialog')
|
||||
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
|
||||
|
||||
# Public: Open the dev tools for the current window.
|
||||
# Extended: 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.
|
||||
# Extended: Toggle the visibility of the dev tools for the current window.
|
||||
toggleDevTools: ->
|
||||
ipc.send('call-window-method', 'toggleDevTools')
|
||||
|
||||
# Public: Execute code in dev tools.
|
||||
# Extended: 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')
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
# Public: Focus the current window.
|
||||
focus: ->
|
||||
ipc.send('call-window-method', 'focus')
|
||||
$(window).focus()
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
|
||||
# Public: Show the current window.
|
||||
show: ->
|
||||
ipc.send('call-window-method', 'show')
|
||||
startTime = Date.now()
|
||||
@project ?= @deserializers.deserialize(@state.project) ? new Project(path: @getLoadSettings().initialPath)
|
||||
@deserializeTimings.project = Date.now() - startTime
|
||||
|
||||
# Public: Hide the current window.
|
||||
hide: ->
|
||||
ipc.send('call-window-method', 'hide')
|
||||
deserializeWorkspaceView: ->
|
||||
Workspace = require './workspace'
|
||||
WorkspaceView = require './workspace-view'
|
||||
|
||||
# Public: Set the size of current window.
|
||||
#
|
||||
# * `width` The {Number} of pixels.
|
||||
# * `height` The {Number} of pixels.
|
||||
setSize: (width, height) ->
|
||||
@getCurrentWindow().setSize(width, height)
|
||||
startTime = Date.now()
|
||||
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
|
||||
@workspaceView = new WorkspaceView(@workspace)
|
||||
@deserializeTimings.workspace = Date.now() - startTime
|
||||
|
||||
# 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)
|
||||
@keymaps.defaultTarget = @workspaceView[0]
|
||||
$(@workspaceViewParentSelector).append(@workspaceView)
|
||||
|
||||
# Public: Move current window to the center of the screen.
|
||||
center: ->
|
||||
ipc.send('call-window-method', 'center')
|
||||
deserializePackageStates: ->
|
||||
@packages.packageStates = @state.packageStates ? {}
|
||||
delete @state.packageStates
|
||||
|
||||
deserializeEditorWindow: ->
|
||||
@deserializeTimings = {}
|
||||
@deserializePackageStates()
|
||||
@deserializeProject()
|
||||
@deserializeWorkspaceView()
|
||||
|
||||
# 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: ({maximize}={}) ->
|
||||
setImmediate =>
|
||||
@show()
|
||||
@focus()
|
||||
@setFullScreen(true) if @workspace.fullScreen
|
||||
@maximize() if maximize
|
||||
loadThemes: ->
|
||||
@themes.load()
|
||||
|
||||
# Public: Close the current window.
|
||||
close: ->
|
||||
@getCurrentWindow().close()
|
||||
watchThemes: ->
|
||||
@themes.onDidReloadAll =>
|
||||
# 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()
|
||||
|
||||
exit: (status) ->
|
||||
app = remote.require('app')
|
||||
@@ -526,45 +626,14 @@ class Atom extends Model
|
||||
setRepresentedFilename: (filename) ->
|
||||
ipc.send('call-window-method', 'setRepresentedFilename', filename)
|
||||
|
||||
# Public: Is the current window in development mode?
|
||||
inDevMode: ->
|
||||
@getLoadSettings().devMode
|
||||
showSaveDialog: (callback) ->
|
||||
callback(showSaveDialogSync())
|
||||
|
||||
# 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()
|
||||
|
||||
maximize: ->
|
||||
ipc.send('call-window-method', 'maximize')
|
||||
|
||||
# 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()
|
||||
showSaveDialogSync: (defaultPath) ->
|
||||
defaultPath ?= @project?.getPath()
|
||||
currentWindow = @getCurrentWindow()
|
||||
dialog = remote.require('dialog')
|
||||
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
|
||||
|
||||
saveSync: ->
|
||||
stateString = JSON.stringify(@state)
|
||||
@@ -573,27 +642,12 @@ 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')
|
||||
@@ -605,7 +659,7 @@ class Atom extends Model
|
||||
catch error
|
||||
console.error "Failed to load `#{userInitScriptPath}`", error.stack, error
|
||||
|
||||
# Public: Require the module with the given globals.
|
||||
# Require the module with the given globals.
|
||||
#
|
||||
# The globals will be set on the `window` object and removed after the
|
||||
# require completes.
|
||||
@@ -625,3 +679,14 @@ 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,7 +1,7 @@
|
||||
BufferedProcess = require './buffered-process'
|
||||
path = require 'path'
|
||||
|
||||
# Public: Like {BufferedProcess}, but accepts a Node script as the command
|
||||
# Extended: Like {BufferedProcess}, but accepts a Node script as the command
|
||||
# to run.
|
||||
#
|
||||
# This is necessary on Windows since it doesn't support shebang `#!` lines.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
_ = require 'underscore-plus'
|
||||
ChildProcess = require 'child_process'
|
||||
|
||||
# Public: A wrapper which provides standard error/output line buffering for
|
||||
# Extended: A wrapper which provides standard error/output line buffering for
|
||||
# Node's ChildProcess.
|
||||
#
|
||||
# ## Examples
|
||||
@@ -44,13 +44,14 @@ class BufferedProcess
|
||||
if process.platform is "win32"
|
||||
# Quote all arguments and escapes inner quotes
|
||||
if args?
|
||||
cmdArgs = args.map (arg) ->
|
||||
cmdArgs = args.filter (arg) -> arg?
|
||||
cmdArgs = cmdArgs.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.replace(/"/g, '\\"')}\""
|
||||
"\"#{arg.toString().replace(/"/g, '\\"')}\""
|
||||
else
|
||||
cmdArgs = []
|
||||
if /\s/.test(command)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
clipboard = require 'clipboard'
|
||||
crypto = require 'crypto'
|
||||
|
||||
# Public: Represents the clipboard used for copying and pasting in Atom.
|
||||
# Extended: Represents the clipboard used for copying and pasting in Atom.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.clipboard` global.
|
||||
#
|
||||
|
||||
176
src/command-registry.coffee
Normal file
176
src/command-registry.coffee
Normal file
@@ -0,0 +1,176 @@
|
||||
{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
|
||||
@@ -6,7 +6,7 @@ path = require 'path'
|
||||
async = require 'async'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
# Public: Used to access all of Atom's configuration details.
|
||||
# Essential: Used to access all of Atom's configuration details.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.config` global.
|
||||
#
|
||||
|
||||
@@ -5,7 +5,7 @@ path = require 'path'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
# Public: Provides a registry for commands that you'd like to appear in the
|
||||
# Extended: 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`
|
||||
|
||||
@@ -54,11 +54,14 @@ class Cursor extends Model
|
||||
@emitter.dispose()
|
||||
@needsAutoscroll = true
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Calls your `callback` when the cursor has been moved.
|
||||
# Public: Calls your `callback` when the cursor has been moved.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
# * `event` {Object}
|
||||
@@ -72,7 +75,7 @@ class Cursor extends Model
|
||||
onDidChangePosition: (callback) ->
|
||||
@emitter.on 'did-change-position', callback
|
||||
|
||||
# Extended: Calls your `callback` when the cursor is destroyed
|
||||
# Public: Calls your `callback` when the cursor is destroyed
|
||||
#
|
||||
# * `callback` {Function}
|
||||
#
|
||||
@@ -80,7 +83,7 @@ class Cursor extends Model
|
||||
onDidDestroy: (callback) ->
|
||||
@emitter.on 'did-destroy', callback
|
||||
|
||||
# Extended: Calls your `callback` when the cursor's visibility has changed
|
||||
# Public: Calls your `callback` when the cursor's visibility has changed
|
||||
#
|
||||
# * `callback` {Function}
|
||||
# * `visibility` {Boolean}
|
||||
@@ -102,23 +105,9 @@ class Cursor extends Model
|
||||
super
|
||||
|
||||
###
|
||||
Section: Methods
|
||||
Section: Managing Cursor Position
|
||||
###
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
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())
|
||||
|
||||
# Public: Moves a cursor to a given screen position.
|
||||
#
|
||||
# * `screenPosition` {Array} of two numbers: the screen row, and the screen column.
|
||||
@@ -133,10 +122,6 @@ 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.
|
||||
@@ -151,47 +136,38 @@ class Cursor extends Model
|
||||
getBufferPosition: ->
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
autoscroll: (options) ->
|
||||
@editor.scrollToScreenRange(@getScreenRange(), options)
|
||||
# Public: Returns the cursor's current screen row.
|
||||
getScreenRow: ->
|
||||
@getScreenPosition().row
|
||||
|
||||
# Public: If the marker range is empty, the cursor is marked as being visible.
|
||||
updateVisibility: ->
|
||||
@setVisible(@marker.getBufferRange().isEmpty())
|
||||
# Public: Returns the cursor's current screen column.
|
||||
getScreenColumn: ->
|
||||
@getScreenPosition().column
|
||||
|
||||
# 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: Retrieves the cursor's current buffer row.
|
||||
getBufferRow: ->
|
||||
@getBufferPosition().row
|
||||
|
||||
# Public: Returns the visibility of the cursor.
|
||||
isVisible: -> @visible
|
||||
# Public: Returns the cursor's current buffer column.
|
||||
getBufferColumn: ->
|
||||
@getBufferPosition().column
|
||||
|
||||
# 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")
|
||||
# Public: Returns the cursor's current buffer row of text excluding its line
|
||||
# ending.
|
||||
getCurrentBufferLine: ->
|
||||
@editor.lineTextForBufferRow(@getBufferRow())
|
||||
|
||||
# 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: 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 the cursor is surrounded by whitespace.
|
||||
#
|
||||
@@ -229,34 +205,42 @@ class Cursor extends Model
|
||||
range = [[row, column], [row, Infinity]]
|
||||
@editor.getTextInBufferRange(range).search(@wordRegExp()) == 0
|
||||
|
||||
# Public: Prevents this cursor from causing scrolling.
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
# Public: Returns the indentation level of the current line.
|
||||
getIndentLevel: ->
|
||||
if @editor.getSoftTabs()
|
||||
@getBufferColumn() / @editor.getTabLength()
|
||||
else
|
||||
@getBufferColumn()
|
||||
|
||||
# Public: Deselects the current selection.
|
||||
clearSelection: ->
|
||||
@selection?.clear()
|
||||
# Public: Retrieves the grammar's token scopes for the line.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getScopes: ->
|
||||
@editor.scopesForBufferPosition(@getBufferPosition())
|
||||
|
||||
# Public: Returns the cursor's current screen row.
|
||||
getScreenRow: ->
|
||||
@getScreenPosition().row
|
||||
# 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 column.
|
||||
getScreenColumn: ->
|
||||
@getScreenPosition().column
|
||||
if firstCharacterColumn is -1
|
||||
false
|
||||
else
|
||||
bufferPosition.column > firstCharacterColumn
|
||||
|
||||
# Public: Retrieves the cursor's current buffer row.
|
||||
getBufferRow: ->
|
||||
@getBufferPosition().row
|
||||
# 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: 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.lineTextForBufferRow(@getBufferRow())
|
||||
###
|
||||
Section: Moving the Cursor
|
||||
###
|
||||
|
||||
# Public: Moves the cursor up one screen row.
|
||||
#
|
||||
@@ -417,6 +401,20 @@ class Cursor extends Model
|
||||
|
||||
@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:
|
||||
@@ -553,15 +551,98 @@ class Cursor extends Model
|
||||
getCurrentLineBufferRange: (options) ->
|
||||
@editor.bufferRangeForBufferRow(@getBufferRow(), options)
|
||||
|
||||
# Public: Moves the cursor to the beginning of the next paragraph
|
||||
moveToBeginningOfNextParagraph: ->
|
||||
if position = @getBeginningOfNextParagraphBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
# Public: Retrieves the range for the current paragraph.
|
||||
#
|
||||
# A paragraph is defined as a block of text surrounded by empty lines.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getCurrentParagraphBufferRange: ->
|
||||
@editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow())
|
||||
|
||||
# Public: Moves the cursor to the beginning of the previous paragraph
|
||||
moveToBeginningOfPreviousParagraph: ->
|
||||
if position = @getBeginningOfPreviousParagraphBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
# Public: Returns the characters preceding the cursor in the current word.
|
||||
getCurrentWordPrefix: ->
|
||||
@editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
|
||||
|
||||
###
|
||||
Section: Visibility
|
||||
###
|
||||
|
||||
# Public: If the marker range is empty, the cursor is marked as being visible.
|
||||
updateVisibility: ->
|
||||
@setVisible(@marker.getBufferRange().isEmpty())
|
||||
|
||||
# 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 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.
|
||||
#
|
||||
# See {Point::compare} for more details.
|
||||
#
|
||||
# * `otherCursor`{Cursor} to compare against
|
||||
compare: (otherCursor) ->
|
||||
@getBufferPosition().compare(otherCursor.getBufferPosition())
|
||||
|
||||
###
|
||||
Section: Utilities
|
||||
###
|
||||
|
||||
# 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()
|
||||
@@ -589,56 +670,3 @@ class Cursor extends Model
|
||||
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.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getCurrentParagraphBufferRange: ->
|
||||
@editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow())
|
||||
|
||||
# Public: Returns the characters preceding the cursor in the current word.
|
||||
getCurrentWordPrefix: ->
|
||||
@editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
|
||||
|
||||
# Public: Returns whether the cursor is at the start of a line.
|
||||
isAtBeginningOfLine: ->
|
||||
@getBufferPosition().column == 0
|
||||
|
||||
# Public: Returns the indentation level of the current line.
|
||||
getIndentLevel: ->
|
||||
if @editor.getSoftTabs()
|
||||
@getBufferColumn() / @editor.getTabLength()
|
||||
else
|
||||
@getBufferColumn()
|
||||
|
||||
# Public: Returns whether the cursor is on the line return character.
|
||||
isAtEndOfLine: ->
|
||||
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
|
||||
|
||||
# Public: Retrieves the grammar's token scopes for the line.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getScopes: ->
|
||||
@editor.scopesForBufferPosition(@getBufferPosition())
|
||||
|
||||
# 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/)
|
||||
|
||||
if firstCharacterColumn is -1
|
||||
false
|
||||
else
|
||||
bufferPosition.column > firstCharacterColumn
|
||||
|
||||
# Public: Compare this cursor's buffer position to another cursor's buffer position.
|
||||
#
|
||||
# See {Point::compare} for more details.
|
||||
#
|
||||
# * `otherCursor`{Cursor} to compare against
|
||||
compare: (otherCursor) ->
|
||||
@getBufferPosition().compare(otherCursor.getBufferPosition())
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports =
|
||||
class Decoration
|
||||
EmitterMixin.includeInto(this)
|
||||
|
||||
# Extended: Check if the `decorationProperties.type` matches `type`
|
||||
# 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
|
||||
@@ -46,6 +46,10 @@ class Decoration
|
||||
else
|
||||
type is decorationProperties.type
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: (@marker, @displayBuffer, @properties) ->
|
||||
@emitter = new Emitter
|
||||
@id = nextId()
|
||||
@@ -55,6 +59,19 @@ class Decoration
|
||||
|
||||
@markerDestroyDisposable = @marker.onDidDestroy => @destroy()
|
||||
|
||||
# Essential: 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
|
||||
@emit 'destroyed'
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
@@ -79,7 +96,7 @@ class Decoration
|
||||
@emitter.on 'did-destroy', callback
|
||||
|
||||
###
|
||||
Section: Methods
|
||||
Section: Decoration Details
|
||||
###
|
||||
|
||||
# Essential: An id unique across all {Decoration} objects
|
||||
@@ -98,6 +115,10 @@ class Decoration
|
||||
isType: (type) ->
|
||||
Decoration.isType(@properties, type)
|
||||
|
||||
###
|
||||
Section: Properties
|
||||
###
|
||||
|
||||
# Essential: Returns the {Decoration}'s properties.
|
||||
getProperties: ->
|
||||
@properties
|
||||
@@ -125,18 +146,9 @@ class Decoration
|
||||
Grim.deprecate 'Use Decoration::setProperties instead'
|
||||
@setProperties(newProperties)
|
||||
|
||||
# Essential: 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
|
||||
@emit 'destroyed'
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
###
|
||||
Section: Private methods
|
||||
###
|
||||
|
||||
matchesPattern: (decorationPattern) ->
|
||||
return false unless decorationPattern?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Public: Manages the deserializers used for serialized state
|
||||
# Extended: Manages the deserializers used for serialized state
|
||||
#
|
||||
# An instance of this class is always available as the `atom.deserializers`
|
||||
# global.
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
{Range} = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
{Subscriber} = require 'emissary'
|
||||
EmitterMixin = require('emissary').Emitter
|
||||
{Emitter} = require 'event-kit'
|
||||
Grim = require 'grim'
|
||||
|
||||
module.exports =
|
||||
class DisplayBufferMarker
|
||||
EmitterMixin.includeInto(this)
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
bufferMarkerSubscription: null
|
||||
oldHeadBufferPosition: null
|
||||
oldHeadScreenPosition: null
|
||||
oldTailBufferPosition: null
|
||||
oldTailScreenPosition: null
|
||||
wasValid: true
|
||||
deferredChangeEvents: null
|
||||
|
||||
constructor: ({@bufferMarker, @displayBuffer}) ->
|
||||
@emitter = new Emitter
|
||||
@id = @bufferMarker.id
|
||||
@oldHeadBufferPosition = @getHeadBufferPosition()
|
||||
@oldHeadScreenPosition = @getHeadScreenPosition()
|
||||
@oldTailBufferPosition = @getTailBufferPosition()
|
||||
@oldTailScreenPosition = @getTailScreenPosition()
|
||||
@wasValid = @isValid()
|
||||
|
||||
@subscribe @bufferMarker.onDidDestroy => @destroyed()
|
||||
@subscribe @bufferMarker.onDidChange (event) => @notifyObservers(event)
|
||||
|
||||
onDidChange: (callback) ->
|
||||
@emitter.on 'did-change', callback
|
||||
|
||||
onDidDestroy: (callback) ->
|
||||
@emitter.on 'did-destroy', callback
|
||||
|
||||
on: (eventName) ->
|
||||
switch eventName
|
||||
when 'changed'
|
||||
Grim.deprecate("Use DisplayBufferMarker::onDidChange instead")
|
||||
when 'destroyed'
|
||||
Grim.deprecate("Use DisplayBufferMarker::onDidDestroy instead")
|
||||
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
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'
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
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)
|
||||
|
||||
changeEvent = {
|
||||
@oldHeadScreenPosition, newHeadScreenPosition,
|
||||
@oldTailScreenPosition, newTailScreenPosition,
|
||||
@oldHeadBufferPosition, newHeadBufferPosition,
|
||||
@oldTailBufferPosition, newTailBufferPosition,
|
||||
textChanged,
|
||||
isValid
|
||||
}
|
||||
|
||||
if @deferredChangeEvents?
|
||||
@deferredChangeEvents.push(changeEvent)
|
||||
else
|
||||
@emit 'changed', changeEvent
|
||||
@emitter.emit 'did-change', changeEvent
|
||||
|
||||
@oldHeadBufferPosition = newHeadBufferPosition
|
||||
@oldHeadScreenPosition = newHeadScreenPosition
|
||||
@oldTailBufferPosition = newTailBufferPosition
|
||||
@oldTailScreenPosition = newTailScreenPosition
|
||||
@wasValid = isValid
|
||||
|
||||
pauseChangeEvents: ->
|
||||
@deferredChangeEvents = []
|
||||
|
||||
resumeChangeEvents: ->
|
||||
if deferredChangeEvents = @deferredChangeEvents
|
||||
@deferredChangeEvents = null
|
||||
|
||||
for event in deferredChangeEvents
|
||||
@emit 'changed', event
|
||||
@emitter.emit 'did-change', event
|
||||
@@ -10,7 +10,7 @@ RowMap = require './row-map'
|
||||
Fold = require './fold'
|
||||
Token = require './token'
|
||||
Decoration = require './decoration'
|
||||
DisplayBufferMarker = require './display-buffer-marker'
|
||||
Marker = require './marker'
|
||||
Grim = require 'grim'
|
||||
|
||||
class BufferToScreenConversionError extends Error
|
||||
@@ -865,21 +865,21 @@ class DisplayBuffer extends Model
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
|
||||
# Retrieves a {DisplayBufferMarker} based on its id.
|
||||
# Retrieves a {Marker} based on its id.
|
||||
#
|
||||
# id - A {Number} representing a marker id
|
||||
#
|
||||
# Returns the {DisplayBufferMarker} (if it exists).
|
||||
# Returns the {Marker} (if it exists).
|
||||
getMarker: (id) ->
|
||||
unless marker = @markers[id]
|
||||
if bufferMarker = @buffer.getMarker(id)
|
||||
marker = new DisplayBufferMarker({bufferMarker, displayBuffer: this})
|
||||
marker = new Marker({bufferMarker, displayBuffer: this})
|
||||
@markers[id] = marker
|
||||
marker
|
||||
|
||||
# Retrieves the active markers in the buffer.
|
||||
#
|
||||
# Returns an {Array} of existing {DisplayBufferMarker}s.
|
||||
# Returns an {Array} of existing {Marker}s.
|
||||
getMarkers: ->
|
||||
@buffer.getMarkers().map ({id}) => @getMarker(id)
|
||||
|
||||
@@ -934,7 +934,7 @@ class DisplayBuffer extends Model
|
||||
#
|
||||
# Refer to {DisplayBuffer::findMarkers} for details.
|
||||
#
|
||||
# Returns a {DisplayBufferMarker} or null
|
||||
# Returns a {Marker} or null
|
||||
findMarker: (params) ->
|
||||
@findMarkers(params)[0]
|
||||
|
||||
@@ -955,7 +955,7 @@ class DisplayBuffer extends Model
|
||||
# :containedInBufferRange - A {Range} or range-compatible {Array}. Only
|
||||
# returns markers contained within this range.
|
||||
#
|
||||
# Returns an {Array} of {DisplayBufferMarker}s
|
||||
# Returns an {Array} of {Marker}s
|
||||
findMarkers: (params) ->
|
||||
params = @translateToBufferMarkerParams(params)
|
||||
@buffer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id)
|
||||
@@ -1138,13 +1138,13 @@ class DisplayBuffer extends Model
|
||||
@pendingChangeEvent = null
|
||||
@emitDidChange(event, false)
|
||||
|
||||
handleBufferMarkerCreated: (marker) =>
|
||||
@createFoldForMarker(marker) if marker.matchesAttributes(@getFoldMarkerAttributes())
|
||||
if displayBufferMarker = @getMarker(marker.id)
|
||||
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', displayBufferMarker
|
||||
@emitter.emit 'did-create-marker', displayBufferMarker
|
||||
@emit 'marker-created', marker
|
||||
@emitter.emit 'did-create-marker', marker
|
||||
|
||||
createFoldForMarker: (marker) ->
|
||||
@decorateMarker(marker, type: 'gutter', class: 'folded')
|
||||
|
||||
@@ -128,6 +128,7 @@ EditorComponent = React.createClass
|
||||
scrollableInOppositeDirection: verticallyScrollable
|
||||
verticalScrollbarWidth: verticalScrollbarWidth
|
||||
horizontalScrollbarHeight: horizontalScrollbarHeight
|
||||
useHardwareAcceleration: @useHardwareAcceleration
|
||||
|
||||
ScrollbarComponent
|
||||
ref: 'verticalScrollbar'
|
||||
@@ -140,6 +141,7 @@ EditorComponent = React.createClass
|
||||
scrollableInOppositeDirection: horizontallyScrollable
|
||||
verticalScrollbarWidth: verticalScrollbarWidth
|
||||
horizontalScrollbarHeight: horizontalScrollbarHeight
|
||||
useHardwareAcceleration: @useHardwareAcceleration
|
||||
|
||||
# Also used to measure the height/width of scrollbars after the initial render
|
||||
ScrollbarCornerComponent
|
||||
@@ -350,7 +352,7 @@ EditorComponent = React.createClass
|
||||
|
||||
observeEditor: ->
|
||||
{editor} = @props
|
||||
@subscribe editor.onDidChangeScreenLines(@onScreenLinesChanged)
|
||||
@subscribe editor.onDidChange(@onScreenLinesChanged)
|
||||
@subscribe editor.observeCursors(@onCursorAdded)
|
||||
@subscribe editor.observeSelections(@onSelectionAdded)
|
||||
@subscribe editor.observeDecorations(@onDecorationAdded)
|
||||
@@ -954,7 +956,7 @@ EditorComponent = React.createClass
|
||||
{verticalScrollbar, horizontalScrollbar, scrollbarCorner} = @refs
|
||||
|
||||
verticalNode = verticalScrollbar.getDOMNode()
|
||||
horizontalNode = verticalScrollbar.getDOMNode()
|
||||
horizontalNode = horizontalScrollbar.getDOMNode()
|
||||
cornerNode = scrollbarCorner.getDOMNode()
|
||||
|
||||
originalVerticalDisplayValue = verticalNode.style.display
|
||||
|
||||
@@ -9,6 +9,8 @@ EditorComponent = require './editor-component'
|
||||
# Public: Represents the entire visual pane in Atom.
|
||||
#
|
||||
# The EditorView manages the {Editor}, which manages the file buffers.
|
||||
# `EditorView` is intentionally sparse. Most of the things you'll want
|
||||
# to do are on {Editor}.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
|
||||
1431
src/editor.coffee
1431
src/editor.coffee
File diff suppressed because it is too large
Load Diff
528
src/git.coffee
528
src/git.coffee
@@ -10,7 +10,7 @@ GitUtils = require 'git-utils'
|
||||
|
||||
Task = require './task'
|
||||
|
||||
# Public: Represents the underlying git operations performed by Atom.
|
||||
# Extended: 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
|
||||
@@ -47,8 +47,15 @@ class Git
|
||||
EmitterMixin.includeInto(this)
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
@exists: (path) ->
|
||||
if git = @open(path)
|
||||
git.destroy()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
###
|
||||
Section: Class Methods
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
# Public: Creates a new Git instance.
|
||||
@@ -66,17 +73,6 @@ class Git
|
||||
catch
|
||||
null
|
||||
|
||||
@exists: (path) ->
|
||||
if git = @open(path)
|
||||
git.destroy()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
###
|
||||
Section: Construction
|
||||
###
|
||||
|
||||
constructor: (path, options={}) ->
|
||||
@emitter = new Emitter
|
||||
@repo = GitUtils.open(path)
|
||||
@@ -100,11 +96,26 @@ class Git
|
||||
if @project?
|
||||
@subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer)
|
||||
|
||||
# Public: Destroy this {Git} object.
|
||||
#
|
||||
# This destroys any tasks and subscriptions and releases the underlying
|
||||
# libgit2 repository handle.
|
||||
destroy: ->
|
||||
if @statusTask?
|
||||
@statusTask.terminate()
|
||||
@statusTask = null
|
||||
|
||||
if @repo?
|
||||
@repo.release()
|
||||
@repo = null
|
||||
|
||||
@unsubscribe()
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke the given callback when a specific file's status has
|
||||
# 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.
|
||||
#
|
||||
@@ -118,7 +129,7 @@ class Git
|
||||
onDidChangeStatus: (callback) ->
|
||||
@emitter.on 'did-change-status', callback
|
||||
|
||||
# Essential: Invoke the given callback when a multiple files' statuses have
|
||||
# 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.
|
||||
@@ -140,7 +151,250 @@ class Git
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
###
|
||||
Section: Instance Methods
|
||||
Section: Repository Details
|
||||
###
|
||||
|
||||
# Public: Returns the {String} path of the repository.
|
||||
getPath: ->
|
||||
@path ?= fs.absolute(@getRepo().getPath())
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Returns a {Number} representing the status. This value can be passed to
|
||||
# {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
getPathStatus: (path) ->
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
pathStatus = repo.getStatus(repo.relativize(path)) ? 0
|
||||
pathStatus = 0 if repo.isStatusIgnored(pathStatus)
|
||||
if pathStatus > 0
|
||||
@statuses[relativePath] = pathStatus
|
||||
else
|
||||
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.
|
||||
#
|
||||
# * `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 status indicates modification.
|
||||
isStatusModified: (status) -> @getRepo().isStatusModified(status)
|
||||
|
||||
# Public: Returns true if the given status indicates a new path.
|
||||
isStatusNew: (status) -> @getRepo().isStatusNew(status)
|
||||
|
||||
###
|
||||
Section: Retrieving Diffs
|
||||
###
|
||||
|
||||
# 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: 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)
|
||||
|
||||
###
|
||||
Section: Checking Out
|
||||
###
|
||||
|
||||
# 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>
|
||||
# ```
|
||||
#
|
||||
# * `path` The {String} path to checkout.
|
||||
#
|
||||
# Returns a {Boolean} that's true if the method was successful.
|
||||
checkoutHead: (path) ->
|
||||
repo = @getRepo(path)
|
||||
headCheckedOut = repo.checkoutHead(repo.relativize(path))
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Returns a Boolean that's true if the method was successful.
|
||||
checkoutReference: (reference, create) ->
|
||||
@getRepo().checkoutReference(reference, create)
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
# Subscribes to buffer events.
|
||||
@@ -175,21 +429,6 @@ class Git
|
||||
else
|
||||
checkoutHead()
|
||||
|
||||
# Public: Destroy this {Git} object.
|
||||
#
|
||||
# This destroys any tasks and subscriptions and releases the underlying
|
||||
# libgit2 repository handle.
|
||||
destroy: ->
|
||||
if @statusTask?
|
||||
@statusTask.terminate()
|
||||
@statusTask = null
|
||||
|
||||
if @repo?
|
||||
@repo.release()
|
||||
@repo = null
|
||||
|
||||
@unsubscribe()
|
||||
|
||||
# Returns the corresponding {Repository}
|
||||
getRepo: (path) ->
|
||||
if @repo?
|
||||
@@ -201,233 +440,6 @@ class Git
|
||||
# last time the index was read.
|
||||
refreshIndex: -> @getRepo().refreshIndex()
|
||||
|
||||
# Public: Returns the {String} path of the repository.
|
||||
getPath: ->
|
||||
@path ?= fs.absolute(@getRepo().getPath())
|
||||
|
||||
# Public: Returns the {String} working directory path of the repository.
|
||||
getWorkingDirectory: -> @getRepo().getWorkingDirectory()
|
||||
|
||||
# Public: Get the status of a single path in the repository.
|
||||
#
|
||||
# `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.
|
||||
getPathStatus: (path) ->
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
pathStatus = repo.getStatus(repo.relativize(path)) ? 0
|
||||
pathStatus = 0 if repo.isStatusIgnored(pathStatus)
|
||||
if pathStatus > 0
|
||||
@statuses[relativePath] = pathStatus
|
||||
else
|
||||
delete @statuses[relativePath]
|
||||
if currentPathStatus isnt pathStatus
|
||||
@emit 'status-changed', path, pathStatus
|
||||
@emitter.emit 'did-change-status', {path, pathStatus}
|
||||
|
||||
pathStatus
|
||||
|
||||
# Public: Is the given path ignored?
|
||||
#
|
||||
# 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)
|
||||
|
||||
# Public: Returns true if the given path is new.
|
||||
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
|
||||
|
||||
# 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: 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>
|
||||
# ```
|
||||
#
|
||||
# * `path` The {String} path to checkout.
|
||||
#
|
||||
# Returns a {Boolean} that's true if the method was successful.
|
||||
checkoutHead: (path) ->
|
||||
repo = @getRepo(path)
|
||||
headCheckedOut = repo.checkoutHead(repo.relativize(path))
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Returns a Boolean that's true if the method was successful.
|
||||
checkoutReference: (reference, create) ->
|
||||
@getRepo().checkoutReference(reference, create)
|
||||
|
||||
# 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: 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: 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: 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 {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)
|
||||
|
||||
# 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.
|
||||
refreshStatus: ->
|
||||
|
||||
@@ -148,15 +148,20 @@ class LanguageMode
|
||||
return unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
|
||||
startRow = bufferRow
|
||||
for currentRow in [bufferRow-1..0]
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
startRow = currentRow
|
||||
endRow = bufferRow
|
||||
for currentRow in [bufferRow+1..@buffer.getLastRow()]
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
endRow = currentRow
|
||||
|
||||
if bufferRow > 0
|
||||
for currentRow in [bufferRow-1..0]
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
startRow = currentRow
|
||||
|
||||
if bufferRow < @buffer.getLastRow()
|
||||
for currentRow in [bufferRow+1..@buffer.getLastRow()]
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
endRow = currentRow
|
||||
|
||||
return [startRow, endRow] if startRow isnt endRow
|
||||
|
||||
rowRangeForCodeFoldAtBufferRow: (bufferRow) ->
|
||||
|
||||
399
src/marker.coffee
Normal file
399
src/marker.coffee
Normal file
@@ -0,0 +1,399 @@
|
||||
{Range} = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
{Subscriber} = require 'emissary'
|
||||
EmitterMixin = require('emissary').Emitter
|
||||
{Emitter} = require 'event-kit'
|
||||
Grim = require 'grim'
|
||||
|
||||
# Essential: Represents a buffer annotation that remains logically stationary
|
||||
# even as the buffer changes. This is used to represent cursors, folds, snippet
|
||||
# targets, misspelled words, and anything else that needs to track a logical
|
||||
# location in the buffer over time.
|
||||
#
|
||||
# ### Marker Creation
|
||||
#
|
||||
# Use {Editor::markBufferRange} rather than creating Markers directly.
|
||||
#
|
||||
# ### Head and Tail
|
||||
#
|
||||
# Markers always have a *head* and sometimes have a *tail*. If you think of a
|
||||
# marker as an editor selection, the tail is the part that's stationary and the
|
||||
# head is the part that moves when the mouse is moved. A marker without a tail
|
||||
# always reports an empty range at the head position. A marker with a head position
|
||||
# greater than the tail is in a "normal" orientation. If the head precedes the
|
||||
# tail the marker is in a "reversed" orientation.
|
||||
#
|
||||
# ### Validity
|
||||
#
|
||||
# Markers are considered *valid* when they are first created. Depending on the
|
||||
# invalidation strategy you choose, certain changes to the buffer can cause a
|
||||
# marker to become invalid, for example if the text surrounding the marker is
|
||||
# deleted. The strategies, in order of descending fragility:
|
||||
#
|
||||
# * __never__: The marker is never marked as invalid. This is a good choice for
|
||||
# markers representing selections in an editor.
|
||||
# * __surround__: The marker is invalidated by changes that completely surround it.
|
||||
# * __overlap__: The marker is invalidated by changes that surround the
|
||||
# start or end of the marker. This is the default.
|
||||
# * __inside__: The marker is invalidated by changes that extend into the
|
||||
# inside of the marker. Changes that end at the marker's start or
|
||||
# start at the marker's end do not invalidate the marker.
|
||||
# * __touch__: The marker is invalidated by a change that touches the marked
|
||||
# region in any way, including changes that end at the marker's
|
||||
# start or start at the marker's end. This is the most fragile strategy.
|
||||
#
|
||||
# See {Editor::markBufferRange} for usage.
|
||||
module.exports =
|
||||
class Marker
|
||||
EmitterMixin.includeInto(this)
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
bufferMarkerSubscription: null
|
||||
oldHeadBufferPosition: null
|
||||
oldHeadScreenPosition: null
|
||||
oldTailBufferPosition: null
|
||||
oldTailScreenPosition: null
|
||||
wasValid: true
|
||||
deferredChangeEvents: null
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: ({@bufferMarker, @displayBuffer}) ->
|
||||
@emitter = new Emitter
|
||||
@id = @bufferMarker.id
|
||||
@oldHeadBufferPosition = @getHeadBufferPosition()
|
||||
@oldHeadScreenPosition = @getHeadScreenPosition()
|
||||
@oldTailBufferPosition = @getTailBufferPosition()
|
||||
@oldTailScreenPosition = @getTailScreenPosition()
|
||||
@wasValid = @isValid()
|
||||
|
||||
@subscribe @bufferMarker.onDidDestroy => @destroyed()
|
||||
@subscribe @bufferMarker.onDidChange (event) => @notifyObservers(event)
|
||||
|
||||
# Essential: Destroys the marker, causing it to emit the 'destroyed' event. Once
|
||||
# destroyed, a marker cannot be restored by undo/redo operations.
|
||||
destroy: ->
|
||||
@bufferMarker.destroy()
|
||||
@unsubscribe()
|
||||
|
||||
# Essential: Creates and returns a new {Marker} with the same properties as this
|
||||
# marker.
|
||||
#
|
||||
# * `properties` {Object}
|
||||
copy: (properties) ->
|
||||
@displayBuffer.getMarker(@bufferMarker.copy(properties).id)
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke the given callback when the state of the marker changes.
|
||||
#
|
||||
# * `callback` {Function} to be called when the marker changes.
|
||||
# * `event` {Object} with the following keys:
|
||||
# * `oldHeadPosition` {Point} representing the former head position
|
||||
# * `newHeadPosition` {Point} representing the new head position
|
||||
# * `oldTailPosition` {Point} representing the former tail position
|
||||
# * `newTailPosition` {Point} representing the new tail position
|
||||
# * `wasValid` {Boolean} indicating whether the marker was valid before the change
|
||||
# * `isValid` {Boolean} indicating whether the marker is now valid
|
||||
# * `hadTail` {Boolean} indicating whether the marker had a tail before the change
|
||||
# * `hasTail` {Boolean} indicating whether the marker now has a tail
|
||||
# * `oldProperties` {Object} containing the marker's custom properties before the change.
|
||||
# * `newProperties` {Object} containing the marker's custom properties after the change.
|
||||
# * `textChanged` {Boolean} indicating whether this change was caused by a textual change
|
||||
# to the buffer or whether the marker was manipulated directly via its public API.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChange: (callback) ->
|
||||
@emitter.on 'did-change', callback
|
||||
|
||||
# Essential: Invoke the given callback when the marker is destroyed.
|
||||
#
|
||||
# * `callback` {Function} to be called when the marker is destroyed.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidDestroy: (callback) ->
|
||||
@emitter.on 'did-destroy', callback
|
||||
|
||||
on: (eventName) ->
|
||||
switch eventName
|
||||
when 'changed'
|
||||
Grim.deprecate("Use Marker::onDidChange instead")
|
||||
when 'destroyed'
|
||||
Grim.deprecate("Use Marker::onDidDestroy instead")
|
||||
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
###
|
||||
Section: Marker Details
|
||||
###
|
||||
|
||||
# Essential: 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()
|
||||
|
||||
# Essential: 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()
|
||||
|
||||
# Essential: Returns a {Boolean} indicating whether the head precedes the tail.
|
||||
isReversed: ->
|
||||
@bufferMarker.isReversed()
|
||||
|
||||
# Essential: Get the invalidation strategy for this marker.
|
||||
#
|
||||
# Valid values include: `never`, `surround`, `overlap`, `inside`, and `touch`.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getInvalidationStrategy: ->
|
||||
@bufferMarker.getInvalidationStrategy()
|
||||
|
||||
# Essential: Returns an {Object} containing any custom properties associated with
|
||||
# the marker.
|
||||
getProperties: ->
|
||||
@bufferMarker.getProperties()
|
||||
getAttributes: ->
|
||||
deprecate 'Use Marker::getProperties instead'
|
||||
@getProperties()
|
||||
|
||||
# Essential: Merges an {Object} containing new properties into the marker's
|
||||
# existing properties.
|
||||
#
|
||||
# * `properties` {Object}
|
||||
setProperties: (properties) ->
|
||||
@bufferMarker.setProperties(properties)
|
||||
setAttributes: (properties) ->
|
||||
deprecate 'Use Marker::getProperties instead'
|
||||
@setProperties(properties)
|
||||
|
||||
matchesProperties: (attributes) ->
|
||||
attributes = @displayBuffer.translateToBufferMarkerParams(attributes)
|
||||
@bufferMarker.matchesParams(attributes)
|
||||
matchesAttributes: (attributes) ->
|
||||
deprecate 'Use Marker::matchesProperties instead'
|
||||
@matchesProperties(attributes)
|
||||
|
||||
###
|
||||
Section: Comparing to other markers
|
||||
###
|
||||
|
||||
# Essential: Returns a {Boolean} indicating whether this marker is equivalent to
|
||||
# another marker, meaning they have the same range and options.
|
||||
#
|
||||
# * `other` {Marker} other marker
|
||||
isEqual: (other) ->
|
||||
return false unless other instanceof @constructor
|
||||
@bufferMarker.isEqual(other.bufferMarker)
|
||||
|
||||
# Essential: Compares this marker to another based on their ranges.
|
||||
#
|
||||
# * `other` {Marker}
|
||||
#
|
||||
# Returns a {Number}
|
||||
compare: (other) ->
|
||||
@bufferMarker.compare(other.bufferMarker)
|
||||
|
||||
###
|
||||
Section: Managing the marker's range
|
||||
###
|
||||
|
||||
# Essential: Gets the buffer range of the display marker.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBufferRange: ->
|
||||
@bufferMarker.getRange()
|
||||
|
||||
# Essential: Modifies the buffer range of the display marker.
|
||||
#
|
||||
# * `bufferRange` The new {Range} to use
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
# * `reversed` {Boolean} If true, the marker will to be in a reversed orientation.
|
||||
setBufferRange: (bufferRange, properties) ->
|
||||
@bufferMarker.setRange(bufferRange, properties)
|
||||
|
||||
# Essential: Gets the screen range of the display marker.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getScreenRange: ->
|
||||
@displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Essential: Modifies the screen range of the display marker.
|
||||
#
|
||||
# * `screenRange` The new {Range} to use
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
# * `reversed` {Boolean} If true, the marker will to be in a reversed orientation.
|
||||
setScreenRange: (screenRange, options) ->
|
||||
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options)
|
||||
|
||||
# Essential: Retrieves the buffer position of the marker's start. This will always be
|
||||
# less than or equal to the result of {Marker::getEndBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartBufferPosition: ->
|
||||
@bufferMarker.getStartPosition()
|
||||
|
||||
# Essential: Retrieves the screen position of the marker's start. This will always be
|
||||
# less than or equal to the result of {Marker::getEndScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Essential: Retrieves the buffer position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {Marker::getStartBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndBufferPosition: ->
|
||||
@bufferMarker.getEndPosition()
|
||||
|
||||
# Essential: Retrieves the screen position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {Marker::getStartScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getEndBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Extended: Retrieves the buffer position of the marker's head.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getHeadBufferPosition: ->
|
||||
@bufferMarker.getHeadPosition()
|
||||
|
||||
# Extended: Sets the buffer position of the marker's head.
|
||||
#
|
||||
# * `screenRange` The new {Point} to use
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
setHeadBufferPosition: (bufferPosition, properties) ->
|
||||
@bufferMarker.setHeadPosition(bufferPosition, properties)
|
||||
|
||||
# Extended: Retrieves the screen position of the marker's head.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getHeadScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Extended: Sets the screen position of the marker's head.
|
||||
#
|
||||
# * `screenRange` The new {Point} to use
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
setHeadScreenPosition: (screenPosition, properties) ->
|
||||
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, properties)
|
||||
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, properties))
|
||||
|
||||
# Extended: Retrieves the buffer position of the marker's tail.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getTailBufferPosition: ->
|
||||
@bufferMarker.getTailPosition()
|
||||
|
||||
# Extended: Sets the buffer position of the marker's tail.
|
||||
#
|
||||
# * `screenRange` The new {Point} to use
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
setTailBufferPosition: (bufferPosition) ->
|
||||
@bufferMarker.setTailPosition(bufferPosition)
|
||||
|
||||
# Extended: Retrieves the screen position of the marker's tail.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getTailScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Extended: Sets the screen position of the marker's tail.
|
||||
#
|
||||
# * `screenRange` The new {Point} to use
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
setTailScreenPosition: (screenPosition, options) ->
|
||||
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
|
||||
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
|
||||
|
||||
# Extended: Returns a {Boolean} indicating whether the marker has a tail.
|
||||
hasTail: ->
|
||||
@bufferMarker.hasTail()
|
||||
|
||||
# Extended: Plants the marker's tail at the current head position. After calling
|
||||
# the marker's tail position will be its head position at the time of the
|
||||
# call, regardless of where the marker's head is moved.
|
||||
#
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
plantTail: ->
|
||||
@bufferMarker.plantTail()
|
||||
|
||||
# Extended: Removes the marker's tail. After calling the marker's head position
|
||||
# will be reported as its current tail position until the tail is planted
|
||||
# again.
|
||||
#
|
||||
# * `properties` (optional) {Object} properties to associate with the marker.
|
||||
clearTail: (properties) ->
|
||||
@bufferMarker.clearTail(properties)
|
||||
|
||||
###
|
||||
Section: Private utility methods
|
||||
###
|
||||
|
||||
# Returns a {String} representation of the marker
|
||||
inspect: ->
|
||||
"Marker(id: #{@id}, bufferRange: #{@getBufferRange()})"
|
||||
|
||||
destroyed: ->
|
||||
delete @displayBuffer.markers[@id]
|
||||
@emit 'destroyed'
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
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)
|
||||
|
||||
changeEvent = {
|
||||
@oldHeadScreenPosition, newHeadScreenPosition,
|
||||
@oldTailScreenPosition, newTailScreenPosition,
|
||||
@oldHeadBufferPosition, newHeadBufferPosition,
|
||||
@oldTailBufferPosition, newTailBufferPosition,
|
||||
textChanged,
|
||||
isValid
|
||||
}
|
||||
|
||||
if @deferredChangeEvents?
|
||||
@deferredChangeEvents.push(changeEvent)
|
||||
else
|
||||
@emit 'changed', changeEvent
|
||||
@emitter.emit 'did-change', changeEvent
|
||||
|
||||
@oldHeadBufferPosition = newHeadBufferPosition
|
||||
@oldHeadScreenPosition = newHeadScreenPosition
|
||||
@oldTailBufferPosition = newTailBufferPosition
|
||||
@oldTailScreenPosition = newTailScreenPosition
|
||||
@wasValid = isValid
|
||||
|
||||
pauseChangeEvents: ->
|
||||
@deferredChangeEvents = []
|
||||
|
||||
resumeChangeEvents: ->
|
||||
if deferredChangeEvents = @deferredChangeEvents
|
||||
@deferredChangeEvents = null
|
||||
|
||||
for event in deferredChangeEvents
|
||||
@emit 'changed', event
|
||||
@emitter.emit 'did-change', event
|
||||
|
||||
getPixelRange: ->
|
||||
@displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false)
|
||||
@@ -5,7 +5,7 @@ ipc = require 'ipc'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
# Public: Provides a registry for menu items that you'd like to appear in the
|
||||
# Extended: Provides a registry for menu items that you'd like to appear in the
|
||||
# application menu.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.menu` global.
|
||||
|
||||
@@ -48,7 +48,7 @@ class PackageManager
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke the given callback when all packages have been activated.
|
||||
# Public: Invoke the given callback when all packages have been activated.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
#
|
||||
@@ -56,7 +56,7 @@ class PackageManager
|
||||
onDidLoadAll: (callback) ->
|
||||
@emitter.on 'did-load-all', callback
|
||||
|
||||
# Essential: Invoke the given callback when all packages have been activated.
|
||||
# Public: Invoke the given callback when all packages have been activated.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
#
|
||||
@@ -75,10 +75,10 @@ class PackageManager
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
###
|
||||
Section: Instance Methods
|
||||
Section: Package system data
|
||||
###
|
||||
|
||||
# Extended: Get the path to the apm command.
|
||||
# Public: Get the path to the apm command.
|
||||
#
|
||||
# Return a {String} file path to apm.
|
||||
getApmPath: ->
|
||||
@@ -86,19 +86,43 @@ class PackageManager
|
||||
commandName += '.cmd' if process.platform is 'win32'
|
||||
@apmPath ?= path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager', 'bin', commandName)
|
||||
|
||||
# Extended: Get the paths being used to look for packages.
|
||||
# Public: Get the paths being used to look for packages.
|
||||
#
|
||||
# Returns an {Array} of {String} directory paths.
|
||||
getPackageDirPaths: ->
|
||||
_.clone(@packageDirPaths)
|
||||
|
||||
getPackageState: (name) ->
|
||||
@packageStates[name]
|
||||
###
|
||||
Section: General package data
|
||||
###
|
||||
|
||||
setPackageState: (name, state) ->
|
||||
@packageStates[name] = state
|
||||
# Public: Resolve the given package name to a path on disk.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Return a {String} folder path or undefined if it could not be resolved.
|
||||
resolvePackagePath: (name) ->
|
||||
return name if fs.isDirectorySync(name)
|
||||
|
||||
# Extended: Enable the package with the given name.
|
||||
packagePath = fs.resolve(@packageDirPaths..., name)
|
||||
return packagePath if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagePath = path.join(@resourcePath, 'node_modules', name)
|
||||
return packagePath if @hasAtomEngine(packagePath)
|
||||
|
||||
# Public: Is the package with the given name bundled with Atom?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isBundledPackage: (name) ->
|
||||
@getPackageDependencies().hasOwnProperty(name)
|
||||
|
||||
###
|
||||
Section: Enabling and disabling packages
|
||||
###
|
||||
|
||||
# Public: Enable the package with the given name.
|
||||
#
|
||||
# Returns the {Package} that was enabled or null if it isn't loaded.
|
||||
enablePackage: (name) ->
|
||||
@@ -106,7 +130,7 @@ class PackageManager
|
||||
pack?.enable()
|
||||
pack
|
||||
|
||||
# Extended: Disable the package with the given name.
|
||||
# Public: Disable the package with the given name.
|
||||
#
|
||||
# Returns the {Package} that was disabled or null if it isn't loaded.
|
||||
disablePackage: (name) ->
|
||||
@@ -114,51 +138,23 @@ class PackageManager
|
||||
pack?.disable()
|
||||
pack
|
||||
|
||||
# Activate all the packages that should be activated.
|
||||
activate: ->
|
||||
for [activator, types] in @packageActivators
|
||||
packages = @getLoadedPackagesForTypes(types)
|
||||
activator.activatePackages(packages)
|
||||
@emit 'activated'
|
||||
@emitter.emit 'did-activate-all'
|
||||
# Public: Is the package with the given name disabled?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageDisabled: (name) ->
|
||||
_.include(atom.config.get('core.disabledPackages') ? [], name)
|
||||
|
||||
# another type of package manager can handle other package types.
|
||||
# See ThemeManager
|
||||
registerPackageActivator: (activator, types) ->
|
||||
@packageActivators.push([activator, types])
|
||||
###
|
||||
Section: Accessing active packages
|
||||
###
|
||||
|
||||
activatePackages: (packages) ->
|
||||
@activatePackage(pack.name) for pack in packages
|
||||
@observeDisabledPackages()
|
||||
|
||||
# Activate a single package by name
|
||||
activatePackage: (name) ->
|
||||
if pack = @getActivePackage(name)
|
||||
Q(pack)
|
||||
else
|
||||
pack = @loadPackage(name)
|
||||
pack.activate().then =>
|
||||
@activePackages[pack.name] = pack
|
||||
pack
|
||||
|
||||
# Deactivate all packages
|
||||
deactivatePackages: ->
|
||||
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
|
||||
@unobserveDisabledPackages()
|
||||
|
||||
# Deactivate the package with the given name
|
||||
deactivatePackage: (name) ->
|
||||
pack = @getLoadedPackage(name)
|
||||
if @isPackageActive(name)
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
pack.deactivate()
|
||||
delete @activePackages[pack.name]
|
||||
|
||||
# Essential: Get an {Array} of all the active {Package}s.
|
||||
# Public: Get an {Array} of all the active {Package}s.
|
||||
getActivePackages: ->
|
||||
_.values(@activePackages)
|
||||
|
||||
# Essential: Get the active {Package} with the given name.
|
||||
# Public: Get the active {Package} with the given name.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
@@ -174,6 +170,91 @@ class PackageManager
|
||||
isPackageActive: (name) ->
|
||||
@getActivePackage(name)?
|
||||
|
||||
###
|
||||
Section: Accessing loaded packages
|
||||
###
|
||||
|
||||
# Public: Get an {Array} of all the loaded {Package}s
|
||||
getLoadedPackages: ->
|
||||
_.values(@loadedPackages)
|
||||
|
||||
# Get packages for a certain package type
|
||||
#
|
||||
# * `types` an {Array} of {String}s like ['atom', 'textmate'].
|
||||
getLoadedPackagesForTypes: (types) ->
|
||||
pack for pack in @getLoadedPackages() when pack.getType() in types
|
||||
|
||||
# Public: Get the loaded {Package} with the given name.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Package} or undefined.
|
||||
getLoadedPackage: (name) ->
|
||||
@loadedPackages[name]
|
||||
|
||||
# Public: Is the package with the given name loaded?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageLoaded: (name) ->
|
||||
@getLoadedPackage(name)?
|
||||
|
||||
###
|
||||
Section: Accessing available packages
|
||||
###
|
||||
|
||||
# Public: Get an {Array} of {String}s of all the available package paths.
|
||||
getAvailablePackagePaths: ->
|
||||
packagePaths = []
|
||||
|
||||
for packageDirPath in @packageDirPaths
|
||||
for packagePath in fs.listSync(packageDirPath)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagesPath = path.join(@resourcePath, 'node_modules')
|
||||
for packageName, packageVersion of @getPackageDependencies()
|
||||
packagePath = path.join(packagesPath, packageName)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
_.uniq(packagePaths)
|
||||
|
||||
# Public: Get an {Array} of {String}s of all the available package names.
|
||||
getAvailablePackageNames: ->
|
||||
_.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
|
||||
|
||||
# Public: Get an {Array} of {String}s of all the available package metadata.
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
for packagePath in @getAvailablePackagePaths()
|
||||
name = path.basename(packagePath)
|
||||
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
packages.push(metadata)
|
||||
packages
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
getPackageState: (name) ->
|
||||
@packageStates[name]
|
||||
|
||||
setPackageState: (name, state) ->
|
||||
@packageStates[name] = state
|
||||
|
||||
getPackageDependencies: ->
|
||||
unless @packageDependencies?
|
||||
try
|
||||
metadataPath = path.join(@resourcePath, 'package.json')
|
||||
{@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
|
||||
@packageDependencies ?= {}
|
||||
|
||||
@packageDependencies
|
||||
|
||||
hasAtomEngine: (packagePath) ->
|
||||
metadata = Package.loadMetadata(packagePath, true)
|
||||
metadata?.engines?.atom?
|
||||
|
||||
unobserveDisabledPackages: ->
|
||||
@disabledPackagesSubscription?.off()
|
||||
@disabledPackagesSubscription = null
|
||||
@@ -234,99 +315,42 @@ class PackageManager
|
||||
else
|
||||
throw new Error("No loaded package for name '#{name}'")
|
||||
|
||||
# Essential: Get the loaded {Package} with the given name.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Package} or undefined.
|
||||
getLoadedPackage: (name) ->
|
||||
@loadedPackages[name]
|
||||
# Activate all the packages that should be activated.
|
||||
activate: ->
|
||||
for [activator, types] in @packageActivators
|
||||
packages = @getLoadedPackagesForTypes(types)
|
||||
activator.activatePackages(packages)
|
||||
@emit 'activated'
|
||||
@emitter.emit 'did-activate-all'
|
||||
|
||||
# Essential: Is the package with the given name loaded?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageLoaded: (name) ->
|
||||
@getLoadedPackage(name)?
|
||||
# another type of package manager can handle other package types.
|
||||
# See ThemeManager
|
||||
registerPackageActivator: (activator, types) ->
|
||||
@packageActivators.push([activator, types])
|
||||
|
||||
# Essential: Get an {Array} of all the loaded {Package}s
|
||||
getLoadedPackages: ->
|
||||
_.values(@loadedPackages)
|
||||
activatePackages: (packages) ->
|
||||
@activatePackage(pack.name) for pack in packages
|
||||
@observeDisabledPackages()
|
||||
|
||||
# Get packages for a certain package type
|
||||
#
|
||||
# * `types` an {Array} of {String}s like ['atom', 'textmate'].
|
||||
getLoadedPackagesForTypes: (types) ->
|
||||
pack for pack in @getLoadedPackages() when pack.getType() in types
|
||||
# Activate a single package by name
|
||||
activatePackage: (name) ->
|
||||
if pack = @getActivePackage(name)
|
||||
Q(pack)
|
||||
else
|
||||
pack = @loadPackage(name)
|
||||
pack.activate().then =>
|
||||
@activePackages[pack.name] = pack
|
||||
pack
|
||||
|
||||
# Extended: Resolve the given package name to a path on disk.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Return a {String} folder path or undefined if it could not be resolved.
|
||||
resolvePackagePath: (name) ->
|
||||
return name if fs.isDirectorySync(name)
|
||||
# Deactivate all packages
|
||||
deactivatePackages: ->
|
||||
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
|
||||
@unobserveDisabledPackages()
|
||||
|
||||
packagePath = fs.resolve(@packageDirPaths..., name)
|
||||
return packagePath if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagePath = path.join(@resourcePath, 'node_modules', name)
|
||||
return packagePath if @hasAtomEngine(packagePath)
|
||||
|
||||
# Essential: Is the package with the given name disabled?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageDisabled: (name) ->
|
||||
_.include(atom.config.get('core.disabledPackages') ? [], name)
|
||||
|
||||
hasAtomEngine: (packagePath) ->
|
||||
metadata = Package.loadMetadata(packagePath, true)
|
||||
metadata?.engines?.atom?
|
||||
|
||||
# Extended: Is the package with the given name bundled with Atom?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isBundledPackage: (name) ->
|
||||
@getPackageDependencies().hasOwnProperty(name)
|
||||
|
||||
getPackageDependencies: ->
|
||||
unless @packageDependencies?
|
||||
try
|
||||
metadataPath = path.join(@resourcePath, 'package.json')
|
||||
{@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
|
||||
@packageDependencies ?= {}
|
||||
|
||||
@packageDependencies
|
||||
|
||||
# Extended: Get an {Array} of {String}s of all the available package paths.
|
||||
getAvailablePackagePaths: ->
|
||||
packagePaths = []
|
||||
|
||||
for packageDirPath in @packageDirPaths
|
||||
for packagePath in fs.listSync(packageDirPath)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagesPath = path.join(@resourcePath, 'node_modules')
|
||||
for packageName, packageVersion of @getPackageDependencies()
|
||||
packagePath = path.join(packagesPath, packageName)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
_.uniq(packagePaths)
|
||||
|
||||
# Extended: Get an {Array} of {String}s of all the available package names.
|
||||
getAvailablePackageNames: ->
|
||||
_.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
|
||||
|
||||
# Extended: Get an {Array} of {String}s of all the available package metadata.
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
for packagePath in @getAvailablePackagePaths()
|
||||
name = path.basename(packagePath)
|
||||
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
packages.push(metadata)
|
||||
packages
|
||||
# Deactivate the package with the given name
|
||||
deactivatePackage: (name) ->
|
||||
pack = @getLoadedPackage(name)
|
||||
if @isPackageActive(name)
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
pack.deactivate()
|
||||
delete @activePackages[pack.name]
|
||||
|
||||
@@ -6,7 +6,7 @@ PropertyAccessors = require 'property-accessors'
|
||||
|
||||
Pane = require './pane'
|
||||
|
||||
# Extended: A container which can contains multiple items to be switched between.
|
||||
# A container which can contains multiple items to be switched between.
|
||||
#
|
||||
# Items can be almost anything however most commonly they're {EditorView}s.
|
||||
#
|
||||
@@ -162,7 +162,7 @@ class PaneView extends View
|
||||
deprecate 'Please return a Disposable object from your ::onDidChangeTitle method!' unless disposable?.dispose?
|
||||
@activeItemDisposables.add(disposable) if disposable?.dispose?
|
||||
else if item.on?
|
||||
deprecate '::on methods for items are no longer supported. If you would like your item to title change behavior, please implement a ::onDidChangeTitle() method.'
|
||||
deprecate '::on methods for items are no longer supported. If you would like your item to support title change behavior, please implement a ::onDidChangeTitle() method.'
|
||||
disposable = item.on('title-changed', @activeItemTitleChanged)
|
||||
@activeItemDisposables.add(disposable) if disposable?.dispose?
|
||||
|
||||
|
||||
@@ -518,6 +518,7 @@ class Pane extends Model
|
||||
destroyed: ->
|
||||
@container.activateNextPane() if @isActive()
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
item.destroy?() for item in @items.slice()
|
||||
|
||||
###
|
||||
|
||||
@@ -23,14 +23,16 @@ class Project extends Model
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
# Public: Find the local path for the given repository URL.
|
||||
#
|
||||
# * `repoUrl` {String} url to a git repository
|
||||
@pathForRepositoryUrl: (repoUrl) ->
|
||||
deprecate '::pathForRepositoryUrl will be removed. Please remove from your code.'
|
||||
[repoName] = url.parse(repoUrl).path.split('/')[-1..]
|
||||
repoName = repoName.replace(/\.git$/, '')
|
||||
path.join(atom.config.get('core.projectHome'), repoName)
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: ({path, @buffers}={}) ->
|
||||
@buffers ?= []
|
||||
|
||||
@@ -40,14 +42,6 @@ class Project extends Model
|
||||
|
||||
@setPath(path)
|
||||
|
||||
serializeParams: ->
|
||||
path: @path
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState)
|
||||
params
|
||||
|
||||
destroyed: ->
|
||||
buffer.destroy() for buffer in @getBuffers()
|
||||
@destroyRepo()
|
||||
@@ -60,9 +54,29 @@ class Project extends Model
|
||||
destroyUnretainedBuffers: ->
|
||||
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
|
||||
|
||||
###
|
||||
Section: Serialization
|
||||
###
|
||||
|
||||
serializeParams: ->
|
||||
path: @path
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState)
|
||||
params
|
||||
|
||||
###
|
||||
Section: Accessing the git repository
|
||||
###
|
||||
|
||||
# Public: Returns the {Git} repository if available.
|
||||
getRepo: -> @repo
|
||||
|
||||
###
|
||||
Section: Managing Paths
|
||||
###
|
||||
|
||||
# Public: Returns the project's {String} fullpath.
|
||||
getPath: ->
|
||||
@rootDirectory?.path
|
||||
@@ -124,6 +138,99 @@ class Project extends Model
|
||||
contains: (pathToCheck) ->
|
||||
@rootDirectory?.contains(pathToCheck) ? false
|
||||
|
||||
###
|
||||
Section: Searching and Replacing
|
||||
###
|
||||
|
||||
# Public: Performs a search across all the files in the project.
|
||||
#
|
||||
# * `regex` {RegExp} to search with.
|
||||
# * `options` (optional) {Object} (default: {})
|
||||
# * `paths` An {Array} of glob patterns to search within
|
||||
# * `iterator` {Function} callback on each file found
|
||||
scan: (regex, options={}, iterator) ->
|
||||
if _.isFunction(options)
|
||||
iterator = options
|
||||
options = {}
|
||||
|
||||
deferred = Q.defer()
|
||||
|
||||
searchOptions =
|
||||
ignoreCase: regex.ignoreCase
|
||||
inclusions: options.paths
|
||||
includeHidden: true
|
||||
excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
|
||||
exclusions: atom.config.get('core.ignoredNames')
|
||||
|
||||
task = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, ->
|
||||
deferred.resolve()
|
||||
|
||||
task.on 'scan:result-found', (result) =>
|
||||
iterator(result) unless @isPathModified(result.filePath)
|
||||
|
||||
task.on 'scan:file-error', (error) ->
|
||||
iterator(null, error)
|
||||
|
||||
if _.isFunction(options.onPathsSearched)
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
|
||||
for buffer in @getBuffers() when buffer.isModified()
|
||||
filePath = buffer.getPath()
|
||||
continue unless @contains(filePath)
|
||||
matches = []
|
||||
buffer.scan regex, (match) -> matches.push match
|
||||
iterator {filePath, matches} if matches.length > 0
|
||||
|
||||
promise = deferred.promise
|
||||
promise.cancel = ->
|
||||
task.terminate()
|
||||
deferred.resolve('cancelled')
|
||||
promise
|
||||
|
||||
# Public: Performs a replace across all the specified files in the project.
|
||||
#
|
||||
# * `regex` A {RegExp} to search with.
|
||||
# * `replacementText` Text to replace all matches of regex with
|
||||
# * `filePaths` List of file path strings to run the replace on.
|
||||
# * `iterator` A {Function} callback on each file with replacements:
|
||||
# * `options` {Object} with keys `filePath` and `replacements`
|
||||
replace: (regex, replacementText, filePaths, iterator) ->
|
||||
deferred = Q.defer()
|
||||
|
||||
openPaths = (buffer.getPath() for buffer in @getBuffers())
|
||||
outOfProcessPaths = _.difference(filePaths, openPaths)
|
||||
|
||||
inProcessFinished = !openPaths.length
|
||||
outOfProcessFinished = !outOfProcessPaths.length
|
||||
checkFinished = ->
|
||||
deferred.resolve() if outOfProcessFinished and inProcessFinished
|
||||
|
||||
unless outOfProcessFinished.length
|
||||
flags = 'g'
|
||||
flags += 'i' if regex.ignoreCase
|
||||
|
||||
task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
|
||||
outOfProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
task.on 'replace:path-replaced', iterator
|
||||
task.on 'replace:file-error', (error) -> iterator(null, error)
|
||||
|
||||
for buffer in @getBuffers()
|
||||
continue unless buffer.getPath() in filePaths
|
||||
replacements = buffer.replace(regex, replacementText, iterator)
|
||||
iterator({filePath: buffer.getPath(), replacements}) if replacements
|
||||
|
||||
inProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
deferred.promise
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
# Given a path to a file, this constructs and associates a new
|
||||
# {Editor}, showing the file.
|
||||
#
|
||||
@@ -222,91 +329,6 @@ class Project extends Model
|
||||
[buffer] = @buffers.splice(index, 1)
|
||||
buffer?.destroy()
|
||||
|
||||
# Public: Performs a search across all the files in the project.
|
||||
#
|
||||
# * `regex` {RegExp} to search with.
|
||||
# * `options` (optional) {Object} (default: {})
|
||||
# * `paths` An {Array} of glob patterns to search within
|
||||
# * `iterator` {Function} callback on each file found
|
||||
scan: (regex, options={}, iterator) ->
|
||||
if _.isFunction(options)
|
||||
iterator = options
|
||||
options = {}
|
||||
|
||||
deferred = Q.defer()
|
||||
|
||||
searchOptions =
|
||||
ignoreCase: regex.ignoreCase
|
||||
inclusions: options.paths
|
||||
includeHidden: true
|
||||
excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
|
||||
exclusions: atom.config.get('core.ignoredNames')
|
||||
|
||||
task = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, ->
|
||||
deferred.resolve()
|
||||
|
||||
task.on 'scan:result-found', (result) =>
|
||||
iterator(result) unless @isPathModified(result.filePath)
|
||||
|
||||
task.on 'scan:file-error', (error) ->
|
||||
iterator(null, error)
|
||||
|
||||
if _.isFunction(options.onPathsSearched)
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
|
||||
for buffer in @getBuffers() when buffer.isModified()
|
||||
filePath = buffer.getPath()
|
||||
continue unless @contains(filePath)
|
||||
matches = []
|
||||
buffer.scan regex, (match) -> matches.push match
|
||||
iterator {filePath, matches} if matches.length > 0
|
||||
|
||||
promise = deferred.promise
|
||||
promise.cancel = ->
|
||||
task.terminate()
|
||||
deferred.resolve('cancelled')
|
||||
promise
|
||||
|
||||
# Public: Performs a replace across all the specified files in the project.
|
||||
#
|
||||
# * `regex` A {RegExp} to search with.
|
||||
# * `replacementText` Text to replace all matches of regex with
|
||||
# * `filePaths` List of file path strings to run the replace on.
|
||||
# * `iterator` A {Function} callback on each file with replacements:
|
||||
# * `options` {Object} with keys `filePath` and `replacements`
|
||||
replace: (regex, replacementText, filePaths, iterator) ->
|
||||
deferred = Q.defer()
|
||||
|
||||
openPaths = (buffer.getPath() for buffer in @getBuffers())
|
||||
outOfProcessPaths = _.difference(filePaths, openPaths)
|
||||
|
||||
inProcessFinished = !openPaths.length
|
||||
outOfProcessFinished = !outOfProcessPaths.length
|
||||
checkFinished = ->
|
||||
deferred.resolve() if outOfProcessFinished and inProcessFinished
|
||||
|
||||
unless outOfProcessFinished.length
|
||||
flags = 'g'
|
||||
flags += 'i' if regex.ignoreCase
|
||||
|
||||
task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
|
||||
outOfProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
task.on 'replace:path-replaced', iterator
|
||||
task.on 'replace:file-error', (error) -> iterator(null, error)
|
||||
|
||||
for buffer in @getBuffers()
|
||||
continue unless buffer.getPath() in filePaths
|
||||
replacements = buffer.replace(regex, replacementText, iterator)
|
||||
iterator({filePath: buffer.getPath(), replacements}) if replacements
|
||||
|
||||
inProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
deferred.promise
|
||||
|
||||
buildEditorForBuffer: (buffer, editorOptions) ->
|
||||
editor = new Editor(_.extend({buffer, registerEditor: true}, editorOptions))
|
||||
editor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{View} = require './space-pen-extensions'
|
||||
|
||||
# Public: Represents a view that scrolls.
|
||||
# Extended: Represents a view that scrolls.
|
||||
#
|
||||
# Handles several core events to update scroll position:
|
||||
#
|
||||
|
||||
@@ -9,9 +9,11 @@ ScrollbarComponent = React.createClass
|
||||
render: ->
|
||||
{orientation, className, scrollHeight, scrollWidth, visible} = @props
|
||||
{scrollableInOppositeDirection, horizontalScrollbarHeight, verticalScrollbarWidth} = @props
|
||||
{useHardwareAcceleration} = @props
|
||||
|
||||
style = {}
|
||||
style.display = 'none' unless visible
|
||||
style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559
|
||||
switch orientation
|
||||
when 'vertical'
|
||||
style.width = verticalScrollbarWidth
|
||||
|
||||
@@ -46,7 +46,11 @@ class SelectListView extends View
|
||||
inputThrottle: 50
|
||||
cancelling: false
|
||||
|
||||
# Public: Initialize the select list view.
|
||||
###
|
||||
Section: Construction
|
||||
###
|
||||
|
||||
# Essential: Initialize the select list view.
|
||||
#
|
||||
# This method can be overridden by subclasses but `super` should always
|
||||
# be called.
|
||||
@@ -85,13 +89,39 @@ class SelectListView extends View
|
||||
@confirmSelection() if $(e.target).closest('li').hasClass('selected')
|
||||
e.preventDefault()
|
||||
|
||||
schedulePopulateList: ->
|
||||
clearTimeout(@scheduleTimeout)
|
||||
populateCallback = =>
|
||||
@populateList() if @isOnDom()
|
||||
@scheduleTimeout = setTimeout(populateCallback, @inputThrottle)
|
||||
###
|
||||
Section: Methods that must be overridden
|
||||
###
|
||||
|
||||
# Public: Set the array of items to display in the list.
|
||||
# Essential: Create a view for the given model item.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# This is called when the item is about to appended to the list view.
|
||||
#
|
||||
# * `item` The model item being rendered. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a String of HTML, DOM element, jQuery object, or View.
|
||||
viewForItem: (item) ->
|
||||
throw new Error("Subclass must implement a viewForItem(item) method")
|
||||
|
||||
# Essential: Callback function for when an item is selected.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# * `item` The selected model item. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a DOM element, jQuery object, or {View}.
|
||||
confirmed: (item) ->
|
||||
throw new Error("Subclass must implement a confirmed(item) method")
|
||||
|
||||
###
|
||||
Section: Managing the list of items
|
||||
###
|
||||
|
||||
# Essential: Set the array of items to display in the list.
|
||||
#
|
||||
# This should be model items not actual views. {::viewForItem} will be
|
||||
# called to render the item when it is being appended to the list view.
|
||||
@@ -101,30 +131,26 @@ class SelectListView extends View
|
||||
@populateList()
|
||||
@setLoading()
|
||||
|
||||
# Public: Set the error message to display.
|
||||
# Essential: Get the model item that is currently selected in the list view.
|
||||
#
|
||||
# * `message` The {String} error message (default: '').
|
||||
setError: (message='') ->
|
||||
if message.length is 0
|
||||
@error.text('').hide()
|
||||
else
|
||||
@setLoading()
|
||||
@error.text(message).show()
|
||||
# Returns a model item.
|
||||
getSelectedItem: ->
|
||||
@getSelectedItemView().data('select-list-item')
|
||||
|
||||
# Public: Set the loading message to display.
|
||||
# Extended: Get the property name to use when filtering items.
|
||||
#
|
||||
# * `message` The {String} loading message (default: '').
|
||||
setLoading: (message='') ->
|
||||
if message.length is 0
|
||||
@loading.text("")
|
||||
@loadingBadge.text("")
|
||||
@loadingArea.hide()
|
||||
else
|
||||
@setError()
|
||||
@loading.text(message)
|
||||
@loadingArea.show()
|
||||
# This method may be overridden by classes to allow fuzzy filtering based
|
||||
# on a specific property of the item objects.
|
||||
#
|
||||
# For example if the objects you pass to {::setItems} are of the type
|
||||
# `{"id": 3, "name": "Atom"}` then you would return `"name"` from this method
|
||||
# to fuzzy filter by that property when text is entered into this view's
|
||||
# editor.
|
||||
#
|
||||
# Returns the property name to fuzzy filter by.
|
||||
getFilterKey: ->
|
||||
|
||||
# Public: Get the filter query to use when fuzzy filtering the visible
|
||||
# Extended: Get the filter query to use when fuzzy filtering the visible
|
||||
# elements.
|
||||
#
|
||||
# By default this method returns the text in the mini editor but it can be
|
||||
@@ -134,7 +160,12 @@ class SelectListView extends View
|
||||
getFilterQuery: ->
|
||||
@filterEditorView.getEditor().getText()
|
||||
|
||||
# Public: Populate the list view with the model items previously set by
|
||||
# Extended: Set the maximum numbers of items to display in the list.
|
||||
#
|
||||
# * `maxItems` The maximum {Number} of items to display.
|
||||
setMaxItems: (@maxItems) ->
|
||||
|
||||
# Extended: Populate the list view with the model items previously set by
|
||||
# calling {::setItems}.
|
||||
#
|
||||
# Subclasses may override this method but should always call `super`.
|
||||
@@ -161,7 +192,34 @@ class SelectListView extends View
|
||||
else
|
||||
@setError(@getEmptyMessage(@items.length, filteredItems.length))
|
||||
|
||||
# Public: Get the message to display when there are no items.
|
||||
###
|
||||
Section: Messages to the user
|
||||
###
|
||||
|
||||
# Essential: Set the error message to display.
|
||||
#
|
||||
# * `message` The {String} error message (default: '').
|
||||
setError: (message='') ->
|
||||
if message.length is 0
|
||||
@error.text('').hide()
|
||||
else
|
||||
@setLoading()
|
||||
@error.text(message).show()
|
||||
|
||||
# Essential: Set the loading message to display.
|
||||
#
|
||||
# * `message` The {String} loading message (default: '').
|
||||
setLoading: (message='') ->
|
||||
if message.length is 0
|
||||
@loading.text("")
|
||||
@loadingBadge.text("")
|
||||
@loadingArea.hide()
|
||||
else
|
||||
@setError()
|
||||
@loading.text(message)
|
||||
@loadingArea.show()
|
||||
|
||||
# Extended: Get the message to display when there are no items.
|
||||
#
|
||||
# Subclasses may override this method to customize the message.
|
||||
#
|
||||
@@ -171,10 +229,36 @@ class SelectListView extends View
|
||||
# Returns a {String} message (default: 'No matches found').
|
||||
getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found'
|
||||
|
||||
# Public: Set the maximum numbers of items to display in the list.
|
||||
###
|
||||
Section: View Actions
|
||||
###
|
||||
|
||||
# Essential: Cancel and close this select list view.
|
||||
#
|
||||
# * `maxItems` The maximum {Number} of items to display.
|
||||
setMaxItems: (@maxItems) ->
|
||||
# This restores focus to the previously focused element if
|
||||
# {::storeFocusedElement} was called prior to this view being attached.
|
||||
cancel: ->
|
||||
@list.empty()
|
||||
@cancelling = true
|
||||
filterEditorViewFocused = @filterEditorView.isFocused
|
||||
@cancelled()
|
||||
@detach()
|
||||
@restoreFocus() if filterEditorViewFocused
|
||||
@cancelling = false
|
||||
clearTimeout(@scheduleTimeout)
|
||||
|
||||
# Extended: Focus the fuzzy filter editor view.
|
||||
focusFilterEditor: ->
|
||||
@filterEditorView.focus()
|
||||
|
||||
# Extended: Store the currently focused element. This element will be given
|
||||
# back focus when {::cancel} is called.
|
||||
storeFocusedElement: ->
|
||||
@previouslyFocusedElement = $(':focus')
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
selectPreviousItemView: ->
|
||||
view = @getSelectedItemView().prev()
|
||||
@@ -202,68 +286,6 @@ class SelectListView extends View
|
||||
else if desiredBottom > @list.scrollBottom()
|
||||
@list.scrollBottom(desiredBottom)
|
||||
|
||||
getSelectedItemView: ->
|
||||
@list.find('li.selected')
|
||||
|
||||
# Public: Get the model item that is currently selected in the list view.
|
||||
#
|
||||
# Returns a model item.
|
||||
getSelectedItem: ->
|
||||
@getSelectedItemView().data('select-list-item')
|
||||
|
||||
confirmSelection: ->
|
||||
item = @getSelectedItem()
|
||||
if item?
|
||||
@confirmed(item)
|
||||
else
|
||||
@cancel()
|
||||
|
||||
# Public: Create a view for the given model item.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# This is called when the item is about to appended to the list view.
|
||||
#
|
||||
# * `item` The model item being rendered. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a String of HTML, DOM element, jQuery object, or View.
|
||||
viewForItem: (item) ->
|
||||
throw new Error("Subclass must implement a viewForItem(item) method")
|
||||
|
||||
# Public: Callback function for when an item is selected.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# * `item` The selected model item. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a DOM element, jQuery object, or {View}.
|
||||
confirmed: (item) ->
|
||||
throw new Error("Subclass must implement a confirmed(item) method")
|
||||
|
||||
# Public: Get the property name to use when filtering items.
|
||||
#
|
||||
# This method may be overridden by classes to allow fuzzy filtering based
|
||||
# on a specific property of the item objects.
|
||||
#
|
||||
# For example if the objects you pass to {::setItems} are of the type
|
||||
# `{"id": 3, "name": "Atom"}` then you would return `"name"` from this method
|
||||
# to fuzzy filter by that property when text is entered into this view's
|
||||
# editor.
|
||||
#
|
||||
# Returns the property name to fuzzy filter by.
|
||||
getFilterKey: ->
|
||||
|
||||
# Public: Focus the fuzzy filter editor view.
|
||||
focusFilterEditor: ->
|
||||
@filterEditorView.focus()
|
||||
|
||||
# Public: Store the currently focused element. This element will be given
|
||||
# back focus when {::cancel} is called.
|
||||
storeFocusedElement: ->
|
||||
@previouslyFocusedElement = $(':focus')
|
||||
|
||||
restoreFocus: ->
|
||||
if @previouslyFocusedElement?.isOnDom()
|
||||
@previouslyFocusedElement.focus()
|
||||
@@ -273,16 +295,18 @@ class SelectListView extends View
|
||||
cancelled: ->
|
||||
@filterEditorView.getEditor().setText('')
|
||||
|
||||
# Public: Cancel and close this select list view.
|
||||
#
|
||||
# This restores focus to the previously focused element if
|
||||
# {::storeFocusedElement} was called prior to this view being attached.
|
||||
cancel: ->
|
||||
@list.empty()
|
||||
@cancelling = true
|
||||
filterEditorViewFocused = @filterEditorView.isFocused
|
||||
@cancelled()
|
||||
@detach()
|
||||
@restoreFocus() if filterEditorViewFocused
|
||||
@cancelling = false
|
||||
getSelectedItemView: ->
|
||||
@list.find('li.selected')
|
||||
|
||||
confirmSelection: ->
|
||||
item = @getSelectedItem()
|
||||
if item?
|
||||
@confirmed(item)
|
||||
else
|
||||
@cancel()
|
||||
|
||||
schedulePopulateList: ->
|
||||
clearTimeout(@scheduleTimeout)
|
||||
populateCallback = =>
|
||||
@populateList() if @isOnDom()
|
||||
@scheduleTimeout = setTimeout(populateCallback, @inputThrottle)
|
||||
|
||||
@@ -30,6 +30,9 @@ class Selection extends Model
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
@@ -60,37 +63,11 @@ class Selection extends Model
|
||||
|
||||
super
|
||||
|
||||
|
||||
###
|
||||
Section: Methods
|
||||
Section: Managing the selection range
|
||||
###
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
finalize: ->
|
||||
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
|
||||
if @isEmpty()
|
||||
@wordwise = false
|
||||
@linewise = false
|
||||
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
|
||||
# Public: Determines if the selection contains anything.
|
||||
isEmpty: ->
|
||||
@getBufferRange().isEmpty()
|
||||
|
||||
# Public: Determines if the ending position of a marker is greater than the
|
||||
# starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {TextBuffer}.
|
||||
isReversed: ->
|
||||
@marker.isReversed()
|
||||
|
||||
# Public: Returns whether the selection is a single line or not.
|
||||
isSingleScreenLine: ->
|
||||
@getScreenRange().isSingleLine()
|
||||
|
||||
# Public: Returns the screen {Range} for the selection.
|
||||
getScreenRange: ->
|
||||
@marker.getScreenRange()
|
||||
@@ -148,55 +125,61 @@ class Selection extends Model
|
||||
getHeadBufferPosition: ->
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
###
|
||||
Section: Info about the selection
|
||||
###
|
||||
|
||||
# Public: Determines if the selection contains anything.
|
||||
isEmpty: ->
|
||||
@getBufferRange().isEmpty()
|
||||
|
||||
# Public: Determines if the ending position of a marker is greater than the
|
||||
# starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {TextBuffer}.
|
||||
isReversed: ->
|
||||
@marker.isReversed()
|
||||
|
||||
# Public: Returns whether the selection is a single line or not.
|
||||
isSingleScreenLine: ->
|
||||
@getScreenRange().isSingleLine()
|
||||
|
||||
# Public: Returns the text in the selection.
|
||||
getText: ->
|
||||
@editor.buffer.getTextInRange(@getBufferRange())
|
||||
|
||||
# Public: Identifies if a selection intersects with a given buffer range.
|
||||
#
|
||||
# * `bufferRange` A {Range} to check against.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsBufferRange: (bufferRange) ->
|
||||
@getBufferRange().intersectsWith(bufferRange)
|
||||
|
||||
intersectsScreenRowRange: (startRow, endRow) ->
|
||||
@getScreenRange().intersectsRowRange(startRow, endRow)
|
||||
|
||||
intersectsScreenRow: (screenRow) ->
|
||||
@getScreenRange().intersectsRow(screenRow)
|
||||
|
||||
# Public: Identifies if a selection intersects with another selection.
|
||||
#
|
||||
# * `otherSelection` A {Selection} to check against.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsWith: (otherSelection, exclusive) ->
|
||||
@getBufferRange().intersectsWith(otherSelection.getBufferRange(), exclusive)
|
||||
|
||||
###
|
||||
Section: Modifying the selected range
|
||||
###
|
||||
|
||||
# Public: Clears the selection, moving the marker to the head.
|
||||
clear: ->
|
||||
@marker.setAttributes(goalBufferRange: null)
|
||||
@marker.setProperties(goalBufferRange: null)
|
||||
@marker.clearTail() unless @retainSelection
|
||||
@finalize()
|
||||
|
||||
# Public: Modifies the selection to encompass the current word.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
selectWord: ->
|
||||
options = {}
|
||||
options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace()
|
||||
if @cursor.isBetweenWordAndNonWord()
|
||||
options.includeNonWordCharacters = false
|
||||
|
||||
@setBufferRange(@cursor.getCurrentWordBufferRange(options))
|
||||
@wordwise = true
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
# Public: Expands the newest selection to include the entire word on which
|
||||
# the cursors rests.
|
||||
expandOverWord: ->
|
||||
@setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()))
|
||||
|
||||
# Public: Selects an entire line in the buffer.
|
||||
#
|
||||
# * `row` The line {Number} to select (default: the row of the cursor).
|
||||
selectLine: (row=@cursor.getBufferPosition().row) ->
|
||||
range = @editor.bufferRangeForBufferRow(row, includeNewline: true)
|
||||
@setBufferRange(@getBufferRange().union(range))
|
||||
@linewise = true
|
||||
@wordwise = false
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
# Public: Expands the newest selection to include the entire line on which
|
||||
# the cursor currently rests.
|
||||
#
|
||||
# It also includes the newline character.
|
||||
expandOverLine: ->
|
||||
range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true))
|
||||
@setBufferRange(range)
|
||||
|
||||
# Public: Selects the text from the current cursor position to a given screen
|
||||
# position.
|
||||
#
|
||||
@@ -311,46 +294,45 @@ class Selection extends Model
|
||||
selectToBeginningOfPreviousParagraph: ->
|
||||
@modifySelection => @cursor.moveToBeginningOfPreviousParagraph()
|
||||
|
||||
# Public: Moves the selection down one row.
|
||||
addSelectionBelow: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
nextRow = range.end.row + 1
|
||||
# Public: Modifies the selection to encompass the current word.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
selectWord: ->
|
||||
options = {}
|
||||
options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace()
|
||||
if @cursor.isBetweenWordAndNonWord()
|
||||
options.includeNonWordCharacters = false
|
||||
|
||||
for row in [nextRow..@editor.getLastBufferRow()]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
@setBufferRange(@cursor.getCurrentWordBufferRange(options))
|
||||
@wordwise = true
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
# Public: Expands the newest selection to include the entire word on which
|
||||
# the cursors rests.
|
||||
expandOverWord: ->
|
||||
@setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()))
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
# Public: Selects an entire line in the buffer.
|
||||
#
|
||||
# * `row` The line {Number} to select (default: the row of the cursor).
|
||||
selectLine: (row=@cursor.getBufferPosition().row) ->
|
||||
range = @editor.bufferRangeForBufferRow(row, includeNewline: true)
|
||||
@setBufferRange(@getBufferRange().union(range))
|
||||
@linewise = true
|
||||
@wordwise = false
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
# FIXME: I have no idea what this does.
|
||||
getGoalBufferRange: ->
|
||||
if goalBufferRange = @marker.getAttributes().goalBufferRange
|
||||
Range.fromObject(goalBufferRange)
|
||||
# Public: Expands the newest selection to include the entire line on which
|
||||
# the cursor currently rests.
|
||||
#
|
||||
# It also includes the newline character.
|
||||
expandOverLine: ->
|
||||
range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true))
|
||||
@setBufferRange(range)
|
||||
|
||||
# Public: Moves the selection up one row.
|
||||
addSelectionAbove: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
previousRow = range.end.row - 1
|
||||
|
||||
for row in [previousRow..0]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
###
|
||||
Section: Modifying the selected text
|
||||
###
|
||||
|
||||
# Public: Replaces text at the current selection.
|
||||
#
|
||||
@@ -391,71 +373,6 @@ class Selection extends Model
|
||||
|
||||
newBufferRange
|
||||
|
||||
# Public: Indents the given text to the suggested level based on the grammar.
|
||||
#
|
||||
# * `text` The {String} to indent within the selection.
|
||||
# * `indentBasis` The beginning indent level.
|
||||
normalizeIndents: (text, indentBasis) ->
|
||||
textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
|
||||
isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
|
||||
|
||||
lines = text.split('\n')
|
||||
firstLineIndentLevel = @editor.indentLevelForLine(lines[0])
|
||||
if isCursorInsideExistingLine
|
||||
minimumIndentLevel = @editor.indentationForBufferRow(@cursor.getBufferRow())
|
||||
else
|
||||
minimumIndentLevel = @cursor.getIndentLevel()
|
||||
|
||||
normalizedLines = []
|
||||
for line, i in lines
|
||||
if i == 0
|
||||
indentLevel = 0
|
||||
else if line == '' # remove all indentation from empty lines
|
||||
indentLevel = 0
|
||||
else
|
||||
lineIndentLevel = @editor.indentLevelForLine(lines[i])
|
||||
indentLevel = minimumIndentLevel + (lineIndentLevel - indentBasis)
|
||||
|
||||
normalizedLines.push(@setIndentationForLine(line, indentLevel))
|
||||
|
||||
normalizedLines.join('\n')
|
||||
|
||||
# Indent the current line(s).
|
||||
#
|
||||
# If the selection is empty, indents the current line if the cursor precedes
|
||||
# non-whitespace characters, and otherwise inserts a tab. If the selection is
|
||||
# non empty, calls {::indentSelectedRows}.
|
||||
#
|
||||
# * `options` (optional) {Object} with the keys:
|
||||
# * `autoIndent` If `true`, the line is indented to an automatically-inferred
|
||||
# level. Otherwise, {Editor::getTabText} is inserted.
|
||||
indent: ({ autoIndent }={}) ->
|
||||
{ row, column } = @cursor.getBufferPosition()
|
||||
|
||||
if @isEmpty()
|
||||
@cursor.skipLeadingWhitespace()
|
||||
desiredIndent = @editor.suggestedIndentForBufferRow(row)
|
||||
delta = desiredIndent - @cursor.getIndentLevel()
|
||||
|
||||
if autoIndent and delta > 0
|
||||
@insertText(@editor.buildIndentString(delta))
|
||||
else
|
||||
@insertText(@editor.buildIndentString(1, @cursor.getBufferColumn()))
|
||||
else
|
||||
@indentSelectedRows()
|
||||
|
||||
# Public: If the selection spans multiple rows, indent all of them.
|
||||
indentSelectedRows: ->
|
||||
[start, end] = @getBufferRowRange()
|
||||
for row in [start..end]
|
||||
@editor.buffer.insert([row, 0], @editor.getTabText()) unless @editor.buffer.lineLengthForRow(row) == 0
|
||||
|
||||
# Public: ?
|
||||
setIndentationForLine: (line, indentLevel) ->
|
||||
desiredIndentLevel = Math.max(0, indentLevel)
|
||||
desiredIndentString = @editor.buildIndentString(desiredIndentLevel)
|
||||
line.replace(/^[\t ]*/, desiredIndentString)
|
||||
|
||||
# Public: Removes the first character before the selection if the selection
|
||||
# is empty otherwise it deletes the selection.
|
||||
backspace: ->
|
||||
@@ -632,41 +549,110 @@ class Selection extends Model
|
||||
@editor.createFold(range.start.row, range.end.row)
|
||||
@cursor.setBufferPosition([range.end.row + 1, 0])
|
||||
|
||||
modifySelection: (fn) ->
|
||||
@retainSelection = true
|
||||
@plantTail()
|
||||
fn()
|
||||
@retainSelection = false
|
||||
# Public: Indents the given text to the suggested level based on the grammar.
|
||||
#
|
||||
# * `text` The {String} to indent within the selection.
|
||||
# * `indentBasis` The beginning indent level.
|
||||
normalizeIndents: (text, indentBasis) ->
|
||||
textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
|
||||
isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
|
||||
|
||||
# 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: ->
|
||||
@marker.plantTail()
|
||||
lines = text.split('\n')
|
||||
firstLineIndentLevel = @editor.indentLevelForLine(lines[0])
|
||||
if isCursorInsideExistingLine
|
||||
minimumIndentLevel = @editor.indentationForBufferRow(@cursor.getBufferRow())
|
||||
else
|
||||
minimumIndentLevel = @cursor.getIndentLevel()
|
||||
|
||||
# Public: Identifies if a selection intersects with a given buffer range.
|
||||
#
|
||||
# * `bufferRange` A {Range} to check against.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsBufferRange: (bufferRange) ->
|
||||
@getBufferRange().intersectsWith(bufferRange)
|
||||
normalizedLines = []
|
||||
for line, i in lines
|
||||
if i == 0
|
||||
indentLevel = 0
|
||||
else if line == '' # remove all indentation from empty lines
|
||||
indentLevel = 0
|
||||
else
|
||||
lineIndentLevel = @editor.indentLevelForLine(lines[i])
|
||||
indentLevel = minimumIndentLevel + (lineIndentLevel - indentBasis)
|
||||
|
||||
intersectsScreenRowRange: (startRow, endRow) ->
|
||||
@getScreenRange().intersectsRowRange(startRow, endRow)
|
||||
normalizedLines.push(@setIndentationForLine(line, indentLevel))
|
||||
|
||||
intersectsScreenRow: (screenRow) ->
|
||||
@getScreenRange().intersectsRow(screenRow)
|
||||
normalizedLines.join('\n')
|
||||
|
||||
# Public: Identifies if a selection intersects with another selection.
|
||||
# Indent the current line(s).
|
||||
#
|
||||
# * `otherSelection` A {Selection} to check against.
|
||||
# If the selection is empty, indents the current line if the cursor precedes
|
||||
# non-whitespace characters, and otherwise inserts a tab. If the selection is
|
||||
# non empty, calls {::indentSelectedRows}.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsWith: (otherSelection, exclusive) ->
|
||||
@getBufferRange().intersectsWith(otherSelection.getBufferRange(), exclusive)
|
||||
# * `options` (optional) {Object} with the keys:
|
||||
# * `autoIndent` If `true`, the line is indented to an automatically-inferred
|
||||
# level. Otherwise, {Editor::getTabText} is inserted.
|
||||
indent: ({ autoIndent }={}) ->
|
||||
{ row, column } = @cursor.getBufferPosition()
|
||||
|
||||
if @isEmpty()
|
||||
@cursor.skipLeadingWhitespace()
|
||||
desiredIndent = @editor.suggestedIndentForBufferRow(row)
|
||||
delta = desiredIndent - @cursor.getIndentLevel()
|
||||
|
||||
if autoIndent and delta > 0
|
||||
delta = Math.max(delta, 1) unless @editor.getSoftTabs()
|
||||
@insertText(@editor.buildIndentString(delta))
|
||||
else
|
||||
@insertText(@editor.buildIndentString(1, @cursor.getBufferColumn()))
|
||||
else
|
||||
@indentSelectedRows()
|
||||
|
||||
# Public: If the selection spans multiple rows, indent all of them.
|
||||
indentSelectedRows: ->
|
||||
[start, end] = @getBufferRowRange()
|
||||
for row in [start..end]
|
||||
@editor.buffer.insert([row, 0], @editor.getTabText()) unless @editor.buffer.lineLengthForRow(row) == 0
|
||||
|
||||
setIndentationForLine: (line, indentLevel) ->
|
||||
desiredIndentLevel = Math.max(0, indentLevel)
|
||||
desiredIndentString = @editor.buildIndentString(desiredIndentLevel)
|
||||
line.replace(/^[\t ]*/, desiredIndentString)
|
||||
|
||||
###
|
||||
Section: Managing multiple selections
|
||||
###
|
||||
|
||||
# Public: Moves the selection down one row.
|
||||
addSelectionBelow: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
nextRow = range.end.row + 1
|
||||
|
||||
for row in [nextRow..@editor.getLastBufferRow()]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
|
||||
# Public: Moves the selection up one row.
|
||||
addSelectionAbove: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
previousRow = range.end.row - 1
|
||||
|
||||
for row in [previousRow..0]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
|
||||
# Public: Combines the given selection into this selection and then destroys
|
||||
# the given selection.
|
||||
@@ -683,6 +669,10 @@ class Selection extends Model
|
||||
@setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options)
|
||||
otherSelection.destroy()
|
||||
|
||||
###
|
||||
Section: Comparing to other selections
|
||||
###
|
||||
|
||||
# Public: Compare this selection's buffer range to another selection's buffer
|
||||
# range.
|
||||
#
|
||||
@@ -692,7 +682,41 @@ class Selection extends Model
|
||||
compare: (otherSelection) ->
|
||||
@getBufferRange().compare(otherSelection.getBufferRange())
|
||||
|
||||
###
|
||||
Section: Private Utilities
|
||||
###
|
||||
|
||||
screenRangeChanged: ->
|
||||
@emit 'screen-range-changed', @getScreenRange()
|
||||
@emitter.emit 'did-change-range'
|
||||
@editor.selectionRangeChanged(this)
|
||||
|
||||
finalize: ->
|
||||
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
|
||||
if @isEmpty()
|
||||
@wordwise = false
|
||||
@linewise = false
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
|
||||
modifySelection: (fn) ->
|
||||
@retainSelection = true
|
||||
@plantTail()
|
||||
fn()
|
||||
@retainSelection = false
|
||||
|
||||
# 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: ->
|
||||
@marker.plantTail()
|
||||
|
||||
getGoalBufferRange: ->
|
||||
if goalBufferRange = @marker.getProperties().goalBufferRange
|
||||
Range.fromObject(goalBufferRange)
|
||||
|
||||
@@ -9,7 +9,7 @@ PropertyAccessors = require 'property-accessors'
|
||||
{$, $$} = require './space-pen-extensions'
|
||||
Token = require './token'
|
||||
|
||||
# Public: Syntax class holding the grammars used for tokenizing.
|
||||
# Extended: Syntax class holding the grammars used for tokenizing.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.syntax` global.
|
||||
#
|
||||
|
||||
@@ -2,14 +2,38 @@ _ = require 'underscore-plus'
|
||||
child_process = require 'child_process'
|
||||
{Emitter} = require 'emissary'
|
||||
|
||||
# Public: Run a node script in a separate process.
|
||||
# Extended: Run a node script in a separate process.
|
||||
#
|
||||
# Used by the fuzzy-finder.
|
||||
# Used by the fuzzy-finder and [find in project](https://github.com/atom/atom/blob/master/src/scan-handler.coffee).
|
||||
#
|
||||
# For a real-world example, see the [scan-handler](https://github.com/atom/atom/blob/master/src/scan-handler.coffee)
|
||||
# and the [instantiation of the task](https://github.com/atom/atom/blob/4a20f13162f65afc816b512ad7201e528c3443d7/src/project.coffee#L245).
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# In your package code:
|
||||
#
|
||||
# ```coffee
|
||||
# {Task} = require 'atom'
|
||||
#
|
||||
# task = Task.once '/path/to/task-file.coffee', parameter1, parameter2, ->
|
||||
# console.log 'task has finished'
|
||||
#
|
||||
# task.on 'some-event-from-the-task', (data) =>
|
||||
# console.log data.someString # prints 'yep this is it'
|
||||
# ```
|
||||
#
|
||||
# In `'/path/to/task-file.coffee'`:
|
||||
#
|
||||
# ```coffee
|
||||
# module.exports = (parameter1, parameter2) ->
|
||||
# # Indicates that this task will be async.
|
||||
# # Call the `callback` to finish the task
|
||||
# callback = @async()
|
||||
#
|
||||
# emit('some-event-from-the-task', {someString: 'yep this is it'})
|
||||
#
|
||||
# callback()
|
||||
# ```
|
||||
#
|
||||
# ## Events
|
||||
@@ -55,7 +79,7 @@ class Task
|
||||
# receives a completion callback, this is overridden.
|
||||
callback: null
|
||||
|
||||
# Public: Creates a task.
|
||||
# Public: Creates a task. You should probably use {.once}
|
||||
#
|
||||
# * `taskPath` The {String} path to the CoffeeScript/JavaScript file that
|
||||
# exports a single {Function} to execute.
|
||||
|
||||
@@ -4,34 +4,47 @@ isHighSurrogate = (string, index) ->
|
||||
isLowSurrogate = (string, index) ->
|
||||
0xDC00 <= string.charCodeAt(index) <= 0xDFFF
|
||||
|
||||
isVariationSelector = (string, index) ->
|
||||
0xFE00 <= string.charCodeAt(index) <= 0xFE0F
|
||||
|
||||
# Is the character at the given index the start of a high/low surrogate pair?
|
||||
#
|
||||
# string - The {String} to check for a surrogate pair.
|
||||
# index - The {Number} index to look for a surrogate pair at.
|
||||
# * `string` The {String} to check for a surrogate pair.
|
||||
# * `index` The {Number} index to look for a surrogate pair at.
|
||||
#
|
||||
# Return a {Boolean}.
|
||||
isSurrogatePair = (string, index=0) ->
|
||||
isHighSurrogate(string, index) and isLowSurrogate(string, index + 1)
|
||||
|
||||
# Get the number of characters in the string accounting for surrogate pairs.
|
||||
# Is the character at the given index the start of a variation sequence?
|
||||
#
|
||||
# This method counts high/low surrogate pairs as a single character and will
|
||||
# always returns a value less than or equal to `string.length`.
|
||||
# * `string` The {String} to check for a variation sequence.
|
||||
# * `index` The {Number} index to look for a variation sequence at.
|
||||
#
|
||||
# string - The {String} to count the number of full characters in.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getCharacterCount = (string) ->
|
||||
count = string.length
|
||||
count-- for index in [0...string.length] when isSurrogatePair(string, index)
|
||||
count
|
||||
# Return a {Boolean}.
|
||||
isVariationSequence = (string, index=0) ->
|
||||
not isVariationSelector(string, index) and isVariationSelector(string, index + 1)
|
||||
|
||||
# Does the given string contain at least one surrogate pair?
|
||||
# Is the character at the given index the start of high/low surrogate pair
|
||||
# or a variation sequence?
|
||||
#
|
||||
# string - The {String} to check for the presence of surrogate pairs.
|
||||
# * `string` The {String} to check for a surrogate pair or variation sequence.
|
||||
# * `index` The {Number} index to look for a surrogate pair at.
|
||||
#
|
||||
# Return a {Boolean}.
|
||||
isPairedCharacter = (string, index=0) ->
|
||||
isSurrogatePair(string, index) or isVariationSequence(string, index)
|
||||
|
||||
# Does the given string contain at least surrogate pair or variation sequence?
|
||||
#
|
||||
# * `string` The {String} to check for the presence of paired characters.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
hasSurrogatePair = (string) ->
|
||||
string.length isnt getCharacterCount(string)
|
||||
hasPairedCharacter = (string) ->
|
||||
index = 0
|
||||
while index < string.length
|
||||
return true if isPairedCharacter(string, index)
|
||||
index++
|
||||
false
|
||||
|
||||
module.exports = {getCharacterCount, isSurrogatePair, hasSurrogatePair}
|
||||
module.exports = {isPairedCharacter, hasPairedCharacter}
|
||||
|
||||
@@ -85,17 +85,29 @@ class ThemeManager
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
###
|
||||
Section: Instance Methods
|
||||
Section: Accessing Available Themes
|
||||
###
|
||||
|
||||
getAvailableNames: ->
|
||||
# TODO: Maybe should change to list all the available themes out there?
|
||||
@getLoadedNames()
|
||||
|
||||
###
|
||||
Section: Accessing Loaded Themes
|
||||
###
|
||||
|
||||
# Public: Get an array of all the loaded theme names.
|
||||
getLoadedNames: ->
|
||||
theme.name for theme in @getLoadedThemes()
|
||||
|
||||
# Public: Get an array of all the loaded themes.
|
||||
getLoadedThemes: ->
|
||||
pack for pack in @packageManager.getLoadedPackages() when pack.isTheme()
|
||||
|
||||
###
|
||||
Section: Accessing Active Themes
|
||||
###
|
||||
|
||||
# Public: Get an array of all the active theme names.
|
||||
getActiveNames: ->
|
||||
theme.name for theme in @getActiveThemes()
|
||||
@@ -104,13 +116,13 @@ class ThemeManager
|
||||
getActiveThemes: ->
|
||||
pack for pack in @packageManager.getActivePackages() when pack.isTheme()
|
||||
|
||||
# Public: Get an array of all the loaded themes.
|
||||
getLoadedThemes: ->
|
||||
pack for pack in @packageManager.getLoadedPackages() when pack.isTheme()
|
||||
|
||||
activatePackages: -> @activateThemes()
|
||||
|
||||
# Get the enabled theme names from the config.
|
||||
###
|
||||
Section: Managing Enabled Themes
|
||||
###
|
||||
|
||||
# Public: Get the enabled theme names from the config.
|
||||
#
|
||||
# Returns an array of theme names in the order that they should be activated.
|
||||
getEnabledThemeNames: ->
|
||||
@@ -145,6 +157,147 @@ class ThemeManager
|
||||
# the first/top theme to override later themes in the stack.
|
||||
themeNames.reverse()
|
||||
|
||||
# Public: Set the list of enabled themes.
|
||||
#
|
||||
# * `enabledThemeNames` An {Array} of {String} theme names.
|
||||
setEnabledThemes: (enabledThemeNames) ->
|
||||
atom.config.set('core.themes', enabledThemeNames)
|
||||
|
||||
###
|
||||
Section: Managing Stylesheets
|
||||
###
|
||||
|
||||
# Public: Returns the {String} path to the user's stylesheet under ~/.atom
|
||||
getUserStylesheetPath: ->
|
||||
stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less'])
|
||||
if fs.isFileSync(stylesheetPath)
|
||||
stylesheetPath
|
||||
else
|
||||
path.join(@configDirPath, 'styles.less')
|
||||
|
||||
# Public: Resolve and apply the stylesheet specified by the path.
|
||||
#
|
||||
# This supports both CSS and Less stylsheets.
|
||||
#
|
||||
# * `stylesheetPath` A {String} path to the stylesheet that can be an absolute
|
||||
# path or a relative path that will be resolved against the load path.
|
||||
#
|
||||
# Returns the absolute path to the required stylesheet.
|
||||
requireStylesheet: (stylesheetPath, type='bundled') ->
|
||||
if fullPath = @resolveStylesheet(stylesheetPath)
|
||||
content = @loadStylesheet(fullPath)
|
||||
@applyStylesheet(fullPath, content, type)
|
||||
else
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
|
||||
fullPath
|
||||
|
||||
unwatchUserStylesheet: ->
|
||||
@userStylesheetFile?.off()
|
||||
@userStylesheetFile = null
|
||||
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
|
||||
|
||||
loadUserStylesheet: ->
|
||||
@unwatchUserStylesheet()
|
||||
userStylesheetPath = @getUserStylesheetPath()
|
||||
return unless fs.isFileSync(userStylesheetPath)
|
||||
|
||||
@userStylesheetPath = userStylesheetPath
|
||||
@userStylesheetFile = new File(userStylesheetPath)
|
||||
@userStylesheetFile.on 'contents-changed moved removed', =>
|
||||
@loadUserStylesheet()
|
||||
userStylesheetContents = @loadStylesheet(userStylesheetPath, true)
|
||||
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
|
||||
|
||||
loadBaseStylesheets: ->
|
||||
@requireStylesheet('bootstrap/less/bootstrap')
|
||||
@reloadBaseStylesheets()
|
||||
|
||||
reloadBaseStylesheets: ->
|
||||
@requireStylesheet('../static/atom')
|
||||
if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less'])
|
||||
@requireStylesheet(nativeStylesheetPath)
|
||||
|
||||
stylesheetElementForId: (id) ->
|
||||
document.head.querySelector("""style[id="#{id}"]""")
|
||||
|
||||
resolveStylesheet: (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath).length > 0
|
||||
fs.resolveOnLoadPath(stylesheetPath)
|
||||
else
|
||||
fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
|
||||
loadStylesheet: (stylesheetPath, importFallbackVariables) ->
|
||||
if path.extname(stylesheetPath) is '.less'
|
||||
@loadLessStylesheet(stylesheetPath, importFallbackVariables)
|
||||
else
|
||||
fs.readFileSync(stylesheetPath, 'utf8')
|
||||
|
||||
loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) ->
|
||||
unless @lessCache?
|
||||
LessCompileCache = require './less-compile-cache'
|
||||
@lessCache = new LessCompileCache({@resourcePath, importPaths: @getImportPaths()})
|
||||
|
||||
try
|
||||
if importFallbackVariables
|
||||
baseVarImports = """
|
||||
@import "variables/ui-variables";
|
||||
@import "variables/syntax-variables";
|
||||
"""
|
||||
less = fs.readFileSync(lessStylesheetPath, 'utf8')
|
||||
@lessCache.cssForFile(lessStylesheetPath, [baseVarImports, less].join('\n'))
|
||||
else
|
||||
@lessCache.read(lessStylesheetPath)
|
||||
catch error
|
||||
console.error """
|
||||
Error compiling Less stylesheet: #{lessStylesheetPath}
|
||||
Line number: #{error.line}
|
||||
#{error.message}
|
||||
"""
|
||||
|
||||
removeStylesheet: (stylesheetPath) ->
|
||||
fullPath = @resolveStylesheet(stylesheetPath) ? stylesheetPath
|
||||
element = @stylesheetElementForId(@stringToId(fullPath))
|
||||
if element?
|
||||
{sheet} = element
|
||||
element.remove()
|
||||
@emit 'stylesheet-removed', sheet
|
||||
@emitter.emit 'did-remove-stylesheet', sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
applyStylesheet: (path, text, type='bundled') ->
|
||||
styleId = @stringToId(path)
|
||||
styleElement = @stylesheetElementForId(styleId)
|
||||
|
||||
if styleElement?
|
||||
@emit 'stylesheet-removed', styleElement.sheet
|
||||
@emitter.emit 'did-remove-stylesheet', styleElement.sheet
|
||||
styleElement.textContent = text
|
||||
else
|
||||
styleElement = document.createElement('style')
|
||||
styleElement.setAttribute('class', type)
|
||||
styleElement.setAttribute('id', styleId)
|
||||
styleElement.textContent = text
|
||||
|
||||
elementToInsertBefore = _.last(document.head.querySelectorAll("style.#{type}"))?.nextElementSibling
|
||||
if elementToInsertBefore?
|
||||
document.head.insertBefore(styleElement, elementToInsertBefore)
|
||||
else
|
||||
document.head.appendChild(styleElement)
|
||||
|
||||
@emit 'stylesheet-added', styleElement.sheet
|
||||
@emitter.emit 'did-add-stylesheet', styleElement.sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
stringToId: (string) ->
|
||||
string.replace(/\\/g, '/')
|
||||
|
||||
activateThemes: ->
|
||||
deferred = Q.defer()
|
||||
|
||||
@@ -194,12 +347,6 @@ class ThemeManager
|
||||
refreshLessCache: ->
|
||||
@lessCache?.setImportPaths(@getImportPaths())
|
||||
|
||||
# Public: Set the list of enabled themes.
|
||||
#
|
||||
# * `enabledThemeNames` An {Array} of {String} theme names.
|
||||
setEnabledThemes: (enabledThemeNames) ->
|
||||
atom.config.set('core.themes', enabledThemeNames)
|
||||
|
||||
getImportPaths: ->
|
||||
activeThemes = @getActiveThemes()
|
||||
if activeThemes.length > 0
|
||||
@@ -212,133 +359,6 @@ class ThemeManager
|
||||
|
||||
themePaths.filter (themePath) -> fs.isDirectorySync(themePath)
|
||||
|
||||
# Public: Returns the {String} path to the user's stylesheet under ~/.atom
|
||||
getUserStylesheetPath: ->
|
||||
stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less'])
|
||||
if fs.isFileSync(stylesheetPath)
|
||||
stylesheetPath
|
||||
else
|
||||
path.join(@configDirPath, 'styles.less')
|
||||
|
||||
unwatchUserStylesheet: ->
|
||||
@userStylesheetFile?.off()
|
||||
@userStylesheetFile = null
|
||||
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
|
||||
|
||||
loadUserStylesheet: ->
|
||||
@unwatchUserStylesheet()
|
||||
userStylesheetPath = @getUserStylesheetPath()
|
||||
return unless fs.isFileSync(userStylesheetPath)
|
||||
|
||||
@userStylesheetPath = userStylesheetPath
|
||||
@userStylesheetFile = new File(userStylesheetPath)
|
||||
@userStylesheetFile.on 'contents-changed moved removed', =>
|
||||
@loadUserStylesheet()
|
||||
userStylesheetContents = @loadStylesheet(userStylesheetPath, true)
|
||||
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
|
||||
|
||||
loadBaseStylesheets: ->
|
||||
@requireStylesheet('bootstrap/less/bootstrap')
|
||||
@reloadBaseStylesheets()
|
||||
|
||||
reloadBaseStylesheets: ->
|
||||
@requireStylesheet('../static/atom')
|
||||
if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less'])
|
||||
@requireStylesheet(nativeStylesheetPath)
|
||||
|
||||
stylesheetElementForId: (id) ->
|
||||
document.head.querySelector("""style[id="#{id}"]""")
|
||||
|
||||
resolveStylesheet: (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath).length > 0
|
||||
fs.resolveOnLoadPath(stylesheetPath)
|
||||
else
|
||||
fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
|
||||
# Public: Resolve and apply the stylesheet specified by the path.
|
||||
#
|
||||
# This supports both CSS and Less stylsheets.
|
||||
#
|
||||
# * `stylesheetPath` A {String} path to the stylesheet that can be an absolute
|
||||
# path or a relative path that will be resolved against the load path.
|
||||
#
|
||||
# Returns the absolute path to the required stylesheet.
|
||||
requireStylesheet: (stylesheetPath, type='bundled') ->
|
||||
if fullPath = @resolveStylesheet(stylesheetPath)
|
||||
content = @loadStylesheet(fullPath)
|
||||
@applyStylesheet(fullPath, content, type)
|
||||
else
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
|
||||
fullPath
|
||||
|
||||
loadStylesheet: (stylesheetPath, importFallbackVariables) ->
|
||||
if path.extname(stylesheetPath) is '.less'
|
||||
@loadLessStylesheet(stylesheetPath, importFallbackVariables)
|
||||
else
|
||||
fs.readFileSync(stylesheetPath, 'utf8')
|
||||
|
||||
loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) ->
|
||||
unless @lessCache?
|
||||
LessCompileCache = require './less-compile-cache'
|
||||
@lessCache = new LessCompileCache({@resourcePath, importPaths: @getImportPaths()})
|
||||
|
||||
try
|
||||
if importFallbackVariables
|
||||
baseVarImports = """
|
||||
@import "variables/ui-variables";
|
||||
@import "variables/syntax-variables";
|
||||
"""
|
||||
less = fs.readFileSync(lessStylesheetPath, 'utf8')
|
||||
@lessCache.cssForFile(lessStylesheetPath, [baseVarImports, less].join('\n'))
|
||||
else
|
||||
@lessCache.read(lessStylesheetPath)
|
||||
catch error
|
||||
console.error """
|
||||
Error compiling Less stylesheet: #{lessStylesheetPath}
|
||||
Line number: #{error.line}
|
||||
#{error.message}
|
||||
"""
|
||||
|
||||
stringToId: (string) ->
|
||||
string.replace(/\\/g, '/')
|
||||
|
||||
removeStylesheet: (stylesheetPath) ->
|
||||
fullPath = @resolveStylesheet(stylesheetPath) ? stylesheetPath
|
||||
element = @stylesheetElementForId(@stringToId(fullPath))
|
||||
if element?
|
||||
{sheet} = element
|
||||
element.remove()
|
||||
@emit 'stylesheet-removed', sheet
|
||||
@emitter.emit 'did-remove-stylesheet', sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
applyStylesheet: (path, text, type='bundled') ->
|
||||
styleId = @stringToId(path)
|
||||
styleElement = @stylesheetElementForId(styleId)
|
||||
|
||||
if styleElement?
|
||||
@emit 'stylesheet-removed', styleElement.sheet
|
||||
@emitter.emit 'did-remove-stylesheet', styleElement.sheet
|
||||
styleElement.textContent = text
|
||||
else
|
||||
styleElement = document.createElement('style')
|
||||
styleElement.setAttribute('class', type)
|
||||
styleElement.setAttribute('id', styleId)
|
||||
styleElement.textContent = text
|
||||
|
||||
elementToInsertBefore = _.last(document.head.querySelectorAll("style.#{type}"))?.nextElementSibling
|
||||
if elementToInsertBefore?
|
||||
document.head.insertBefore(styleElement, elementToInsertBefore)
|
||||
else
|
||||
document.head.appendChild(styleElement)
|
||||
|
||||
@emit 'stylesheet-added', styleElement.sheet
|
||||
@emitter.emit 'did-add-stylesheet', styleElement.sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
updateGlobalEditorStyle: (property, value) ->
|
||||
unless styleNode = @stylesheetElementForId('global-editor-styles')
|
||||
@applyStylesheet('global-editor-styles', '.editor {}')
|
||||
|
||||
@@ -12,7 +12,7 @@ MaxTokenLength = 20000
|
||||
module.exports =
|
||||
class Token
|
||||
value: null
|
||||
hasSurrogatePair: false
|
||||
hasPairedCharacter: false
|
||||
scopes: null
|
||||
isAtomic: null
|
||||
isHardTab: null
|
||||
@@ -23,7 +23,7 @@ class Token
|
||||
constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab}) ->
|
||||
@screenDelta = @value.length
|
||||
@bufferDelta ?= @screenDelta
|
||||
@hasSurrogatePair = textUtils.hasSurrogatePair(@value)
|
||||
@hasPairedCharacter = textUtils.hasPairedCharacter(@value)
|
||||
|
||||
isEqual: (other) ->
|
||||
@value == other.value and _.isEqual(@scopes, other.scopes) and !!@isAtomic == !!other.isAtomic
|
||||
@@ -57,11 +57,11 @@ class Token
|
||||
WhitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
|
||||
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingSoftTabs, startColumn) ->
|
||||
if @hasSurrogatePair
|
||||
if @hasPairedCharacter
|
||||
outputTokens = []
|
||||
column = startColumn
|
||||
|
||||
for token in @breakOutSurrogatePairs()
|
||||
for token in @breakOutPairedCharacters()
|
||||
if token.isAtomic
|
||||
outputTokens.push(token)
|
||||
else
|
||||
@@ -98,27 +98,27 @@ class Token
|
||||
|
||||
outputTokens
|
||||
|
||||
breakOutSurrogatePairs: ->
|
||||
breakOutPairedCharacters: ->
|
||||
outputTokens = []
|
||||
index = 0
|
||||
nonSurrogatePairStart = 0
|
||||
nonPairStart = 0
|
||||
|
||||
while index < @value.length
|
||||
if textUtils.isSurrogatePair(@value, index)
|
||||
if nonSurrogatePairStart isnt index
|
||||
outputTokens.push(new Token({value: @value[nonSurrogatePairStart...index], @scopes}))
|
||||
outputTokens.push(@buildSurrogatePairToken(@value, index))
|
||||
if textUtils.isPairedCharacter(@value, index)
|
||||
if nonPairStart isnt index
|
||||
outputTokens.push(new Token({value: @value[nonPairStart...index], @scopes}))
|
||||
outputTokens.push(@buildPairedCharacterToken(@value, index))
|
||||
index += 2
|
||||
nonSurrogatePairStart = index
|
||||
nonPairStart = index
|
||||
else
|
||||
index++
|
||||
|
||||
if nonSurrogatePairStart isnt index
|
||||
outputTokens.push(new Token({value: @value[nonSurrogatePairStart...index], @scopes}))
|
||||
if nonPairStart isnt index
|
||||
outputTokens.push(new Token({value: @value[nonPairStart...index], @scopes}))
|
||||
|
||||
outputTokens
|
||||
|
||||
buildSurrogatePairToken: (value, index) ->
|
||||
buildPairedCharacterToken: (value, index) ->
|
||||
new Token(
|
||||
value: value[index..index + 1]
|
||||
scopes: @scopes
|
||||
@@ -153,6 +153,8 @@ class Token
|
||||
getValueAsHtml: ({hasIndentGuide}) ->
|
||||
if @isHardTab
|
||||
classes = 'hard-tab'
|
||||
classes += ' leading-whitespace' if @hasLeadingWhitespace()
|
||||
classes += ' trailing-whitespace' if @hasTrailingWhitespace()
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if @hasInvisibleCharacters
|
||||
html = "<span class='#{classes}'>#{@escapeString(@value)}</span>"
|
||||
|
||||
@@ -15,7 +15,12 @@ PaneRowView = require './pane-row-view'
|
||||
PaneContainerView = require './pane-container-view'
|
||||
Editor = require './editor'
|
||||
|
||||
# Essential: The top-level view for the entire window. An instance of this class is
|
||||
atom.commands.add '.workspace',
|
||||
'window:increase-font-size': -> @getModel().increaseFontSize()
|
||||
'window:decrease-font-size': -> @getModel().decreaseFontSize()
|
||||
'window:reset-font-size': -> @getModel().resetFontSize()
|
||||
|
||||
# Extended: The top-level view for the entire window. An instance of this class is
|
||||
# available via the `atom.workspaceView` global.
|
||||
#
|
||||
# It is backed by a model object, an instance of {Workspace}, which is available
|
||||
@@ -77,8 +82,10 @@ class WorkspaceView extends View
|
||||
@div class: 'vertical', outlet: 'vertical', =>
|
||||
@div class: 'panes', outlet: 'panes'
|
||||
|
||||
initialize: (@model) ->
|
||||
@model = atom.workspace ? new Workspace unless @model?
|
||||
initialize: (model) ->
|
||||
@model = model ? atom.workspace ? new Workspace unless @model?
|
||||
@element.getModel = -> model
|
||||
atom.commands.setRootNode(@[0])
|
||||
|
||||
panes = new PaneContainerView(@model.paneContainer)
|
||||
@panes.replaceWith(panes)
|
||||
@@ -136,12 +143,10 @@ class WorkspaceView extends View
|
||||
@command 'application:open-your-stylesheet', -> ipc.send('command', 'application:open-your-stylesheet')
|
||||
@command 'application:open-license', => @model.openLicense()
|
||||
|
||||
@command 'window:install-shell-commands', => @installShellCommands()
|
||||
if process.platform is 'darwin'
|
||||
@command 'window:install-shell-commands', => @installShellCommands()
|
||||
|
||||
@command 'window:run-package-specs', -> ipc.send('run-package-specs', path.join(atom.project.getPath(), 'spec'))
|
||||
@command 'window:increase-font-size', => @increaseFontSize()
|
||||
@command 'window:decrease-font-size', => @decreaseFontSize()
|
||||
@command 'window:reset-font-size', => @model.resetFontSize()
|
||||
|
||||
@command 'window:focus-next-pane', => @focusNextPaneView()
|
||||
@command 'window:focus-previous-pane', => @focusPreviousPaneView()
|
||||
@@ -162,12 +167,175 @@ class WorkspaceView extends View
|
||||
@command 'core:save', => @saveActivePaneItem()
|
||||
@command 'core:save-as', => @saveActivePaneItemAs()
|
||||
|
||||
# Public: Get the underlying model object.
|
||||
@deprecatedViewEvents()
|
||||
|
||||
###
|
||||
Section: Accessing the Workspace Model
|
||||
###
|
||||
|
||||
# Essential: Get the underlying model object.
|
||||
#
|
||||
# Returns a {Workspace}.
|
||||
getModel: -> @model
|
||||
|
||||
# Public: Install the Atom shell commands on the user's system.
|
||||
###
|
||||
Section: Accessing Views
|
||||
###
|
||||
|
||||
# Essential: Register a function to be called for every current and future
|
||||
# editor view in the workspace (only includes {EditorView}s that are pane
|
||||
# items).
|
||||
#
|
||||
# * `callback` A {Function} with an {EditorView} as its only argument.
|
||||
# * `editorView` {EditorView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachEditorView: (callback) ->
|
||||
callback(editorView) for editorView in @getEditorViews()
|
||||
attachedCallback = (e, editorView) ->
|
||||
callback(editorView) unless editorView.mini
|
||||
@on('editor:attached', attachedCallback)
|
||||
off: => @off('editor:attached', attachedCallback)
|
||||
|
||||
# Essential: Register a function to be called for every current and future
|
||||
# pane view in the workspace.
|
||||
#
|
||||
# * `callback` A {Function} with a {PaneView} as its only argument.
|
||||
# * `paneView` {PaneView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachPaneView: (callback) ->
|
||||
@panes.eachPaneView(callback)
|
||||
|
||||
# Essential: Get all existing pane views.
|
||||
#
|
||||
# Prefer {Workspace::getPanes} if you don't need access to the view objects.
|
||||
# Also consider using {::eachPaneView} if you want to register a callback for
|
||||
# all current and *future* pane views.
|
||||
#
|
||||
# Returns an Array of all open {PaneView}s.
|
||||
getPaneViews: ->
|
||||
@panes.getPaneViews()
|
||||
|
||||
# Essential: Get the active pane view.
|
||||
#
|
||||
# Prefer {Workspace::getActivePane} if you don't actually need access to the
|
||||
# view.
|
||||
#
|
||||
# Returns a {PaneView}.
|
||||
getActivePaneView: ->
|
||||
@panes.getActivePaneView()
|
||||
|
||||
# Essential: Get the view associated with the active pane item.
|
||||
#
|
||||
# Returns a view.
|
||||
getActiveView: ->
|
||||
@panes.getActiveView()
|
||||
|
||||
###
|
||||
Section: Adding elements to the workspace
|
||||
###
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the top of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToTop: (element) ->
|
||||
@vertical.prepend(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the top of the workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToTop: (element) ->
|
||||
@panes.before(element)
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToBottom: (element) ->
|
||||
@panes.after(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToBottom: (element) ->
|
||||
@vertical.append(element)
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToLeft: (element) ->
|
||||
@horizontal.prepend(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToLeft: (element) ->
|
||||
@vertical.before(element)
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToRight: (element) ->
|
||||
@vertical.after(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToRight: (element) ->
|
||||
@horizontal.append(element)
|
||||
|
||||
###
|
||||
Section: Focusing pane views
|
||||
###
|
||||
|
||||
# Focus the previous pane by id.
|
||||
focusPreviousPaneView: -> @model.activatePreviousPane()
|
||||
|
||||
# Focus the next pane by id.
|
||||
focusNextPaneView: -> @model.activateNextPane()
|
||||
|
||||
# Focus the pane directly above the active pane.
|
||||
focusPaneViewAbove: -> @panes.focusPaneViewAbove()
|
||||
|
||||
# Focus the pane directly below the active pane.
|
||||
focusPaneViewBelow: -> @panes.focusPaneViewBelow()
|
||||
|
||||
# Focus the pane directly to the left of the active pane.
|
||||
focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft()
|
||||
|
||||
# Focus the pane directly to the right of the active pane.
|
||||
focusPaneViewOnRight: -> @panes.focusPaneViewOnRight()
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if onDom
|
||||
|
||||
# Called by SpacePen
|
||||
beforeRemove: ->
|
||||
@model.destroy()
|
||||
|
||||
setEditorFontSize: (fontSize) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-size', fontSize + 'px')
|
||||
|
||||
setEditorFontFamily: (fontFamily) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-family', fontFamily)
|
||||
|
||||
setEditorLineHeight: (lineHeight) ->
|
||||
atom.themes.updateGlobalEditorStyle('line-height', lineHeight)
|
||||
|
||||
# Install the Atom shell commands on the user's system.
|
||||
installShellCommands: ->
|
||||
showErrorDialog = (error) ->
|
||||
installDirectory = CommandInstaller.getInstallDirectory()
|
||||
@@ -202,9 +370,6 @@ class WorkspaceView extends View
|
||||
$(document.body).focus()
|
||||
true
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if onDom
|
||||
|
||||
# Prompts to save all unsaved items
|
||||
confirmClose: ->
|
||||
@panes.confirmClose()
|
||||
@@ -243,143 +408,104 @@ class WorkspaceView extends View
|
||||
for editorElement in @panes.element.querySelectorAll('.pane > .item-views > .editor')
|
||||
$(editorElement).view()
|
||||
|
||||
# Public: Prepend an element or view to the panels at the top of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToTop: (element) ->
|
||||
@vertical.prepend(element)
|
||||
|
||||
# Public: Append an element or view to the panels at the top of the workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToTop: (element) ->
|
||||
@panes.before(element)
|
||||
###
|
||||
Section: Deprecated
|
||||
###
|
||||
|
||||
# Public: Prepend an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToBottom: (element) ->
|
||||
@panes.after(element)
|
||||
deprecatedViewEvents: ->
|
||||
originalWorkspaceViewOn = @on
|
||||
|
||||
# Public: Append an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToBottom: (element) ->
|
||||
@vertical.append(element)
|
||||
@on = (eventName) =>
|
||||
switch eventName
|
||||
when 'beep'
|
||||
deprecate('Use Atom::onDidBeep instead')
|
||||
when 'cursor:moved'
|
||||
deprecate('Use Editor::onDidChangeCursorPosition instead')
|
||||
when 'editor:attached'
|
||||
deprecate('Use Editor::onDidAddTextEditor instead')
|
||||
when 'editor:detached'
|
||||
deprecate('Use Editor::onDidDestroy instead')
|
||||
when 'editor:will-be-removed'
|
||||
deprecate('Use Editor::onDidDestroy instead')
|
||||
when 'pane:active-item-changed'
|
||||
deprecate('Use Pane::onDidChangeActiveItem instead')
|
||||
when 'pane:active-item-modified-status-changed'
|
||||
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead')
|
||||
when 'pane:active-item-title-changed'
|
||||
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead')
|
||||
when 'pane:attached'
|
||||
deprecate('Use Workspace::onDidAddPane instead')
|
||||
when 'pane:became-active'
|
||||
deprecate('Use Pane::onDidActivate instead')
|
||||
when 'pane:became-inactive'
|
||||
deprecate('Use Pane::onDidChangeActive instead')
|
||||
when 'pane:item-added'
|
||||
deprecate('Use Pane::onDidAddItem instead')
|
||||
when 'pane:item-moved'
|
||||
deprecate('Use Pane::onDidMoveItem instead')
|
||||
when 'pane:item-removed'
|
||||
deprecate('Use Pane::onDidRemoveItem instead')
|
||||
when 'pane:removed'
|
||||
deprecate('Use Pane::onDidDestroy instead')
|
||||
when 'pane-container:active-pane-item-changed'
|
||||
deprecate('Use Workspace::onDidChangeActivePaneItem instead')
|
||||
when 'selection:changed'
|
||||
deprecate('Use Editor::onDidChangeSelectionRange instead')
|
||||
when 'uri-opened'
|
||||
deprecate('Use Workspace::onDidOpen instead')
|
||||
originalWorkspaceViewOn.apply(this, arguments)
|
||||
|
||||
# Public: Prepend an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToLeft: (element) ->
|
||||
@horizontal.prepend(element)
|
||||
EditorView = require './editor-view'
|
||||
originalEditorViewOn = EditorView::on
|
||||
EditorView::on = (eventName) ->
|
||||
switch eventName
|
||||
when 'cursor:moved'
|
||||
deprecate('Use Editor::onDidChangeCursorPosition instead')
|
||||
when 'editor:attached'
|
||||
deprecate('Use Editor::onDidAddTextEditor instead')
|
||||
when 'editor:detached'
|
||||
deprecate('Use Editor::onDidDestroy instead')
|
||||
when 'editor:will-be-removed'
|
||||
deprecate('Use Editor::onDidDestroy instead')
|
||||
when 'selection:changed'
|
||||
deprecate('Use Editor::onDidChangeSelectionRange instead')
|
||||
originalEditorViewOn.apply(this, arguments)
|
||||
|
||||
# Public: Append an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToLeft: (element) ->
|
||||
@vertical.before(element)
|
||||
|
||||
# Public: Prepend an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToRight: (element) ->
|
||||
@vertical.after(element)
|
||||
|
||||
# Public: Append an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToRight: (element) ->
|
||||
@horizontal.append(element)
|
||||
|
||||
# Public: Get the active pane view.
|
||||
#
|
||||
# Prefer {Workspace::getActivePane} if you don't actually need access to the
|
||||
# view.
|
||||
#
|
||||
# Returns a {PaneView}.
|
||||
getActivePaneView: ->
|
||||
@panes.getActivePaneView()
|
||||
|
||||
# Public: Get the view associated with the active pane item.
|
||||
#
|
||||
# Returns a view.
|
||||
getActiveView: ->
|
||||
@panes.getActiveView()
|
||||
|
||||
# Focus the previous pane by id.
|
||||
focusPreviousPaneView: -> @model.activatePreviousPane()
|
||||
|
||||
# Focus the next pane by id.
|
||||
focusNextPaneView: -> @model.activateNextPane()
|
||||
|
||||
# Public: Focus the pane directly above the active pane.
|
||||
focusPaneViewAbove: -> @panes.focusPaneViewAbove()
|
||||
|
||||
# Public: Focus the pane directly below the active pane.
|
||||
focusPaneViewBelow: -> @panes.focusPaneViewBelow()
|
||||
|
||||
# Public: Focus the pane directly to the left of the active pane.
|
||||
focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft()
|
||||
|
||||
# Public: Focus the pane directly to the right of the active pane.
|
||||
focusPaneViewOnRight: -> @panes.focusPaneViewOnRight()
|
||||
|
||||
# Public: Register a function to be called for every current and future
|
||||
# pane view in the workspace.
|
||||
#
|
||||
# * `callback` A {Function} with a {PaneView} as its only argument.
|
||||
# * `paneView` {PaneView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachPaneView: (callback) ->
|
||||
@panes.eachPaneView(callback)
|
||||
|
||||
# Public: Get all existing pane views.
|
||||
#
|
||||
# Prefer {Workspace::getPanes} if you don't need access to the view objects.
|
||||
# Also consider using {::eachPaneView} if you want to register a callback for
|
||||
# all current and *future* pane views.
|
||||
#
|
||||
# Returns an Array of all open {PaneView}s.
|
||||
getPaneViews: ->
|
||||
@panes.getPaneViews()
|
||||
|
||||
# Public: Register a function to be called for every current and future
|
||||
# editor view in the workspace (only includes {EditorView}s that are pane
|
||||
# items).
|
||||
#
|
||||
# * `callback` A {Function} with an {EditorView} as its only argument.
|
||||
# * `editorView` {EditorView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachEditorView: (callback) ->
|
||||
callback(editorView) for editorView in @getEditorViews()
|
||||
attachedCallback = (e, editorView) ->
|
||||
callback(editorView) unless editorView.mini
|
||||
@on('editor:attached', attachedCallback)
|
||||
off: => @off('editor:attached', attachedCallback)
|
||||
|
||||
# Called by SpacePen
|
||||
beforeRemove: ->
|
||||
@model.destroy()
|
||||
|
||||
setEditorFontSize: (fontSize) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-size', fontSize + 'px')
|
||||
|
||||
setEditorFontFamily: (fontFamily) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-family', fontFamily)
|
||||
|
||||
setEditorLineHeight: (lineHeight) ->
|
||||
atom.themes.updateGlobalEditorStyle('line-height', lineHeight)
|
||||
originalPaneViewOn = PaneView::on
|
||||
PaneView::on = (eventName) ->
|
||||
switch eventName
|
||||
when 'cursor:moved'
|
||||
deprecate('Use Editor::onDidChangeCursorPosition instead')
|
||||
when 'editor:attached'
|
||||
deprecate('Use Editor::onDidAddTextEditor instead')
|
||||
when 'editor:detached'
|
||||
deprecate('Use Editor::onDidDestroy instead')
|
||||
when 'editor:will-be-removed'
|
||||
deprecate('Use Editor::onDidDestroy instead')
|
||||
when 'pane:active-item-changed'
|
||||
deprecate('Use Pane::onDidChangeActiveItem instead')
|
||||
when 'pane:active-item-modified-status-changed'
|
||||
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead')
|
||||
when 'pane:active-item-title-changed'
|
||||
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead')
|
||||
when 'pane:attached'
|
||||
deprecate('Use Workspace::onDidAddPane instead')
|
||||
when 'pane:became-active'
|
||||
deprecate('Use Pane::onDidActivate instead')
|
||||
when 'pane:became-inactive'
|
||||
deprecate('Use Pane::onDidChangeActive instead')
|
||||
when 'pane:item-added'
|
||||
deprecate('Use Pane::onDidAddItem instead')
|
||||
when 'pane:item-moved'
|
||||
deprecate('Use Pane::onDidMoveItem instead')
|
||||
when 'pane:item-removed'
|
||||
deprecate('Use Pane::onDidRemoveItem instead')
|
||||
when 'pane:removed'
|
||||
deprecate('Use Pane::onDidDestroy instead')
|
||||
when 'selection:changed'
|
||||
deprecate('Use Editor::onDidChangeSelectionRange instead')
|
||||
originalPaneViewOn.apply(this, arguments)
|
||||
|
||||
# Deprecated
|
||||
eachPane: (callback) ->
|
||||
|
||||
@@ -10,7 +10,7 @@ Editor = require './editor'
|
||||
PaneContainer = require './pane-container'
|
||||
Pane = require './pane'
|
||||
|
||||
# Public: Represents the state of the user interface for the entire window.
|
||||
# Essential: Represents the state of the user interface for the entire window.
|
||||
# An instance of this class is available via the `atom.workspace` global.
|
||||
#
|
||||
# Interact with this object to open files, be notified of current and future
|
||||
@@ -91,6 +91,52 @@ class Workspace extends Model
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke the given callback with all current and future text
|
||||
# editors in the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future text editors.
|
||||
# * `editor` An {Editor} that is present in {::getTextEditors} at the time
|
||||
# of subscription or that is added at some later time.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observeTextEditors: (callback) ->
|
||||
callback(textEditor) for textEditor in @getTextEditors()
|
||||
@onDidAddTextEditor ({textEditor}) -> callback(textEditor)
|
||||
|
||||
# Essential: Invoke the given callback with all current and future panes items in
|
||||
# the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future pane items.
|
||||
# * `item` An item that is present in {::getPaneItems} at the time of
|
||||
# subscription or that is added at some later time.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observePaneItems: (callback) -> @paneContainer.observePaneItems(callback)
|
||||
|
||||
# Essential: Invoke the given callback when the active pane item changes.
|
||||
#
|
||||
# * `callback` {Function} to be called when the active pane item changes.
|
||||
# * `event` {Object} with the following keys:
|
||||
# * `activeItem` The active pane item.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeActivePaneItem: (callback) -> @paneContainer.onDidChangeActivePaneItem(callback)
|
||||
|
||||
# Essential: Invoke the given callback whenever an item is opened. Unlike
|
||||
# {::onDidAddPaneItem}, observers will be notified for items that are already
|
||||
# present in the workspace when they are reopened.
|
||||
#
|
||||
# * `callback` {Function} to be called whenever an item is opened.
|
||||
# * `event` {Object} with the following keys:
|
||||
# * `uri` {String} representing the opened URI. Could be `undefined`.
|
||||
# * `item` The opened item.
|
||||
# * `pane` The pane in which the item was opened.
|
||||
# * `index` The index of the opened item on its pane.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidOpen: (callback) ->
|
||||
@emitter.on 'did-open', callback
|
||||
|
||||
# Extended: Invoke the given callback when a pane is added to the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called panes are added.
|
||||
@@ -140,25 +186,6 @@ class Workspace extends Model
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidAddPaneItem: (callback) -> @paneContainer.onDidAddPaneItem(callback)
|
||||
|
||||
# Extended: Invoke the given callback when the active pane item changes.
|
||||
#
|
||||
# * `callback` {Function} to be called when the active pane item changes.
|
||||
# * `event` {Object} with the following keys:
|
||||
# * `activeItem` The active pane item.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeActivePaneItem: (callback) -> @paneContainer.onDidChangeActivePaneItem(callback)
|
||||
|
||||
# Extended: Invoke the given callback with all current and future panes items in
|
||||
# the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future pane items.
|
||||
# * `item` An item that is present in {::getPaneItems} at the time of
|
||||
# subscription or that is added at some later time.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observePaneItems: (callback) -> @paneContainer.observePaneItems(callback)
|
||||
|
||||
# Extended: Invoke the given callback when a text editor is added to the
|
||||
# workspace.
|
||||
#
|
||||
@@ -174,33 +201,6 @@ class Workspace extends Model
|
||||
@onDidAddPaneItem ({item, pane, index}) ->
|
||||
callback({textEditor: item, pane, index}) if item instanceof Editor
|
||||
|
||||
# Essential: Invoke the given callback with all current and future text
|
||||
# editors in the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future text editors.
|
||||
# * `editor` An {Editor} that is present in {::getTextEditors} at the time
|
||||
# of subscription or that is added at some later time.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observeTextEditors: (callback) ->
|
||||
callback(textEditor) for textEditor in @getTextEditors()
|
||||
@onDidAddTextEditor ({textEditor}) -> callback(textEditor)
|
||||
|
||||
# Essential: Invoke the given callback whenever an item is opened. Unlike
|
||||
# ::onDidAddPaneItem, observers will be notified for items that are already
|
||||
# present in the workspace when they are reopened.
|
||||
#
|
||||
# * `callback` {Function} to be called whenever an item is opened.
|
||||
# * `event` {Object} with the following keys:
|
||||
# * `uri` {String} representing the opened URI. Could be `undefined`.
|
||||
# * `item` The opened item.
|
||||
# * `pane` The pane in which the item was opened.
|
||||
# * `index` The index of the opened item on its pane.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidOpen: (callback) ->
|
||||
@emitter.on 'did-open', callback
|
||||
|
||||
eachEditor: (callback) ->
|
||||
deprecate("Use Workspace::observeTextEditors instead")
|
||||
|
||||
@@ -323,7 +323,7 @@ class Workspace extends Model
|
||||
.catch (error) ->
|
||||
console.error(error.stack ? error)
|
||||
|
||||
# Extended: Asynchronously reopens the last-closed item's URI if it hasn't already been
|
||||
# Public: Asynchronously reopens the last-closed item's URI if it hasn't already been
|
||||
# reopened.
|
||||
#
|
||||
# Returns a promise that is resolved when the item is opened
|
||||
@@ -339,7 +339,9 @@ class Workspace extends Model
|
||||
if uri = @destroyedItemUris.pop()
|
||||
@openSync(uri)
|
||||
|
||||
# Extended: Register an opener for a uri.
|
||||
# TODO: make ::registerOpener() return a disposable
|
||||
|
||||
# Public: Register an opener for a uri.
|
||||
#
|
||||
# An {Editor} will be used if no openers return a value.
|
||||
#
|
||||
@@ -355,7 +357,7 @@ class Workspace extends Model
|
||||
registerOpener: (opener) ->
|
||||
@openers.push(opener)
|
||||
|
||||
# Extended: Unregister an opener registered with {::registerOpener}.
|
||||
# Unregister an opener registered with {::registerOpener}.
|
||||
unregisterOpener: (opener) ->
|
||||
_.remove(@openers, opener)
|
||||
|
||||
@@ -396,7 +398,7 @@ class Workspace extends Model
|
||||
getActiveEditor: ->
|
||||
@activePane?.getActiveEditor()
|
||||
|
||||
# Extended: Save all pane items.
|
||||
# Save all pane items.
|
||||
saveAll: ->
|
||||
@paneContainer.saveAll()
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
@import "overlay";
|
||||
@import "lists";
|
||||
@import "popover-list";
|
||||
@import "notification";
|
||||
@import "messages";
|
||||
@import "markdown";
|
||||
@import "editor";
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
.notification {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 50%;
|
||||
margin-left: -5%;
|
||||
z-index: 9999;
|
||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 rgba(255, 255, 255, 0.05),
|
||||
0 0 5px rgba(0, 0, 0, 0.5);
|
||||
background: -webkit-linear-gradient(
|
||||
rgba(20, 20, 20, 0.5),
|
||||
rgba(0, 0, 0, 0.5));
|
||||
color: #eee;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.notification .content {
|
||||
padding: 10px;
|
||||
margin-left: 42px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.notification .content:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.notification .title {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.notification .message {
|
||||
font-size: 12px;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.notification .icon {
|
||||
display: inline-block;
|
||||
font-family: 'Octicons Regular';
|
||||
font-size: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 5px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* TODO: Full Octicon Support */
|
||||
.icon-gist {
|
||||
content: "\f20e";
|
||||
}
|
||||
Reference in New Issue
Block a user