Merge branch 'master' into privilege-escalation

Conflicts:
	src/text-buffer.coffee
This commit is contained in:
Cheng Zhao
2014-02-12 19:32:49 +08:00
33 changed files with 812 additions and 581 deletions

View File

@@ -43,11 +43,7 @@ class AtomPackage extends Package
@loadStylesheets()
@loadGrammars()
@loadScopedProperties()
if @metadata.activationEvents?
@registerDeferredDeserializers()
else
@requireMainModule()
@requireMainModule() unless @metadata.activationEvents?
catch e
console.warn "Failed to load package named '#{@name}'", e.stack ? e
@@ -73,21 +69,6 @@ class AtomPackage extends Package
@activationDeferred.promise
# Deprecated
activateSync: ({immediate}={}) ->
@activateResources()
if @metadata.activationEvents? and not immediate
@subscribeToActivationEvents()
else
try
@activateConfig()
@activateStylesheets()
if @requireMainModule()
@mainModule.activate(atom.packages.getPackageState(@name) ? {})
@mainActivated = true
catch e
console.warn "Failed to activate package named '#{@name}'", e.stack
activateNow: ->
try
@activateConfig()
@@ -228,12 +209,6 @@ class AtomPackage extends Package
path.join(@path, 'index')
@mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...])
registerDeferredDeserializers: ->
for deserializerName in @metadata.deferredDeserializers ? []
atom.deserializers.addDeferred deserializerName, =>
@activateStylesheets()
@requireMainModule()
subscribeToActivationEvents: ->
return unless @metadata.activationEvents?
if _.isArray(@metadata.activationEvents)

View File

@@ -150,6 +150,14 @@ class Config
toggle: (keyPath) ->
@set(keyPath, !@get(keyPath))
# Public: Restore the key path to its default value.
#
# keyPath - The {String} name of the key.
#
# Returns the new value.
restoreDefault: (keyPath) ->
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
# Public: Push the value to the array at the key path.
#
# keyPath - The {String} key path.

View File

@@ -16,7 +16,6 @@ module.exports =
class DeserializerManager
constructor: ->
@deserializers = {}
@deferredDeserializers = {}
# Public: Register the given class(es) as deserializers.
#
@@ -24,13 +23,6 @@ class DeserializerManager
add: (classes...) ->
@deserializers[klass.name] = klass for klass in classes
# Public: Add a deferred deserializer for the given class name.
#
# name - The {String} name of the deserializer.
# fn - The {Function} that creates the deserializer.
addDeferred: (name, fn) ->
@deferredDeserializers[name] = fn
# Public: Remove the given class(es) as deserializers.
#
# classes - One or more classes to remove.
@@ -59,8 +51,4 @@ class DeserializerManager
return unless state?
name = state.get?('deserializer') ? state.deserializer
if @deferredDeserializers[name]
@deferredDeserializers[name]()
delete @deferredDeserializers[name]
@deserializers[name]

View File

@@ -26,6 +26,7 @@ module.exports =
class EditorView extends View
@characterWidthCache: {}
@configDefaults:
fontFamily: ''
fontSize: 20
showInvisibles: false
showIndentGuide: false

View File

@@ -621,7 +621,7 @@ class Editor extends Model
largestFoldStartingAtScreenRow: (screenRow) ->
@displayBuffer.largestFoldStartingAtScreenRow(screenRow)
# Public: Moves the selected line up one row.
# Public: Moves the selected lines up one screen row.
moveLineUp: ->
selection = @getSelectedBufferRange()
return if selection.start.row is 0
@@ -633,29 +633,47 @@ class Editor extends Model
rows = [selection.start.row..selection.end.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly above the selection
precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
if fold = @largestFoldContainingBufferRow(precedingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow - 1)
endRow = bufferRange.end.row
foldedRows.push(startRow - insertDelta)
else
startRow = row
endRow = row
insertPosition = Point.fromObject([startRow - insertDelta])
endPosition = Point.min([endRow + 1], @buffer.getEofPosition())
lines = @buffer.getTextInRange([[startRow], endPosition])
if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
lines = "#{lines}\n"
@buffer.deleteRows(startRow, endRow)
@buffer.insert([startRow - 1], lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
@setSelectedBufferRange(selection.translate([-1]), preserveFolds: true)
@buffer.insert(insertPosition, lines)
# Public: Moves the selected line down one row.
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true)
# Public: Moves the selected lines down one screen row.
moveLineDown: ->
selection = @getSelectedBufferRange()
lastRow = @buffer.getLastRow()
@@ -667,13 +685,21 @@ class Editor extends Model
rows = [selection.end.row..selection.start.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly below the selection
followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
if fold = @largestFoldContainingBufferRow(followingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow + 1)
endRow = bufferRange.end.row
foldedRows.push(endRow + insertDelta)
else
startRow = row
endRow = row
@@ -684,14 +710,23 @@ class Editor extends Model
endPosition = [endRow + 1]
lines = @buffer.getTextInRange([[startRow], endPosition])
@buffer.deleteRows(startRow, endRow)
insertPosition = Point.min([startRow + 1], @buffer.getEofPosition())
insertPosition = Point.min([startRow + insertDelta], @buffer.getEofPosition())
if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
lines = "\n#{lines}"
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
@buffer.insert(insertPosition, lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([1]), preserveFolds: true)
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true)
# Public: Duplicates the current line.
#

View File

@@ -11,9 +11,8 @@ fs = require 'fs-plus'
# An instance of this class is always available as the `atom.menu` global.
module.exports =
class MenuManager
pendingUpdateOperation: null
constructor: ({@resourcePath}) ->
@pendingUpdateOperation = null
@template = []
atom.keymap.on 'bundled-keymaps-loaded', => @loadPlatformItems()
@@ -32,7 +31,8 @@ class MenuManager
# items - An {Array} of menu item {Object}s containing the keys:
# :label - The {String} menu label.
# :submenu - An optional {Array} of sub menu items.
# :command - An option {String} command to trigger when the item is clicked.
# :command - An optional {String} command to trigger when the item is
# clicked.
#
# Returns nothing.
add: (items) ->
@@ -48,14 +48,21 @@ class MenuManager
includeSelector: (selector) ->
return true if document.body.webkitMatchesSelector(selector)
# Simulate an .editor element attached to a body element that has the same
# classes as the current body element.
# Simulate an .editor element attached to a .workspace element attached to
# a body element that has the same classes as the current body element.
unless @testEditor?
testBody = document.createElement('body')
testBody.classList.add(@classesForElement(document.body)...)
testWorkspace = document.createElement('body')
workspaceClasses = @classesForElement(document.body.querySelector('.workspace')) ? ['.workspace']
testWorkspace.classList.add(workspaceClasses...)
testBody.appendChild(testWorkspace)
@testEditor = document.createElement('div')
@testEditor.classList.add('editor')
testBody = document.createElement('body')
testBody.classList.add(document.body.classList.toString().split(' ')...)
testBody.appendChild(@testEditor)
testWorkspace.appendChild(@testEditor)
@testEditor.webkitMatchesSelector(selector)
@@ -109,3 +116,7 @@ class MenuManager
label.replace(/\&/g, '')
else
label
# Get an {Array} of {String} classes for the given element.
classesForElement: (element) ->
element?.classList.toString().split(' ') ? []

View File

@@ -81,26 +81,15 @@ class PackageManager
@observeDisabledPackages()
# Activate a single package by name
activatePackage: (name, options={}) ->
if options.sync? or options.immediate?
return @activatePackageSync(name, options)
activatePackage: (name) ->
if pack = @getActivePackage(name)
Q(pack)
else
pack = @loadPackage(name)
pack.activate(options).then =>
pack.activate().then =>
@activePackages[pack.name] = pack
pack
# Deprecated
activatePackageSync: (name, options) ->
return pack if pack = @getActivePackage(name)
if pack = @loadPackage(name)
@activePackages[pack.name] = pack
pack.activateSync(options)
pack
# Deactivate all packages
deactivatePackages: ->
@deactivatePackage(pack.name) for pack in @getLoadedPackages()

View File

@@ -27,7 +27,7 @@ class PaneView extends View
'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems',
'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems',
'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive',
'activate', toProperty: 'model'
'activate', 'getActiveItem', toProperty: 'model'
previousActiveItem: null

View File

@@ -3,6 +3,7 @@
{Model, Sequence} = require 'theorist'
Serializable = require 'serializable'
PaneAxis = require './pane-axis'
Editor = require './editor'
PaneView = null
# Public: A container for multiple items, one of which is *active* at a given
@@ -85,6 +86,17 @@ class Pane extends Model
getItems: ->
@items.slice()
# Public: Get the active pane item in this pane.
#
# Returns a pane item.
getActiveItem: ->
@activeItem
# Public: Returns an {Editor} if the pane item is an {Editor}, or null
# otherwise.
getActiveEditor: ->
@activeItem if @activeItem instanceof Editor
# Public: Returns the item at the specified index.
itemAtIndex: (index) ->
@items[index]
@@ -105,15 +117,15 @@ class Pane extends Model
else
@activateItemAtIndex(@items.length - 1)
# Public: Returns the index of the current active item.
# Returns the index of the current active item.
getActiveItemIndex: ->
@items.indexOf(@activeItem)
# Public: Makes the item at the given index active.
# Makes the item at the given index active.
activateItemAtIndex: (index) ->
@activateItem(@itemAtIndex(index))
# Public: Makes the given item active, adding the item if necessary.
# Makes the given item active, adding the item if necessary.
activateItem: (item) ->
if item?
@addItem(item)

View File

@@ -137,9 +137,8 @@ class Project extends Model
#
# Returns a promise that resolves to an {Editor}.
open: (filePath, options={}) ->
filePath = @resolve(filePath)
resource = null
_.find @openers, (opener) -> resource = opener(filePath, options)
filePath = @resolve(filePath) ? ''
resource = opener(filePath, options) for opener in @openers when !resource
if resource
Q(resource)
@@ -149,11 +148,10 @@ class Project extends Model
# Only be used in specs
openSync: (filePath, options={}) ->
filePath = @resolve(filePath)
for opener in @openers
return resource if resource = opener(filePath, options)
filePath = @resolve(filePath) ? ''
resource = opener(filePath, options) for opener in @openers when !resource
@buildEditorForBuffer(@bufferForPathSync(filePath), options)
resource or @buildEditorForBuffer(@bufferForPathSync(filePath), options)
# Public: Retrieves all {Editor}s for all open files.
#

View File

@@ -165,14 +165,14 @@ class TextBuffer extends TextBufferCore
# Sets the path for the file.
#
# path - A {String} representing the new file path
setPath: (path) ->
return if path == @getPath()
# filePath - A {String} representing the new file path
setPath: (filePath) ->
return if filePath == @getPath()
@file?.off()
if path
@file = new File(path)
if filePath
@file = new File(filePath)
@subscribeToFile()
else
@file = null
@@ -188,14 +188,15 @@ class TextBuffer extends TextBufferCore
# Saves the buffer at a specific path.
#
# path - The path to save at.
saveAs: (path) ->
unless path then throw new Error("Can't save buffer with no file path")
# filePath - The path to save at.
saveAs: (filePath) ->
unless filePath then throw new Error("Can't save buffer with no file path")
@emit 'will-be-saved', this
@setPath(path)
@setPath(filePath)
@file.write(@getText())
@cachedDiskContents = @getText()
@conflict = false
@emitModifiedStatusChanged(false)
@emit 'saved', this
@@ -212,7 +213,10 @@ class TextBuffer extends TextBufferCore
else
not @isEmpty()
# Identifies if a buffer is in a git conflict with `HEAD`.
# Is the buffer's text in conflict with the text on disk?
#
# This occurs when the buffer's file changes on disk while the buffer has
# unsaved changes.
#
# Returns a {Boolean}.
isInConflict: -> @conflict

View File

@@ -117,6 +117,7 @@ class WorkspaceView extends View
@command 'window:run-package-specs', => ipc.sendChannel('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', => @focusNextPane()
@command 'window:focus-previous-pane', => @focusPreviousPane()
@@ -229,9 +230,13 @@ class WorkspaceView extends View
@horizontal.append(element)
# Public: Returns the currently focused {PaneView}.
getActivePane: ->
getActivePaneView: ->
@panes.getActivePane()
# Deprecated: Returns the currently focused {PaneView}.
getActivePane: ->
@getActivePaneView()
# Public: Returns the currently focused item from within the focused {PaneView}
getActivePaneItem: ->
@model.activePaneItem

View File

@@ -52,27 +52,43 @@ class Workspace extends Model
#
# filePath - A {String} file path.
# options - An options {Object} (default: {}).
# :initialLine - The buffer line number to open to.
# :initialLine - A {Number} indicating which line number to open to.
# :split - A {String} ('left' or 'right') that opens the filePath in a new
# pane or an existing one if it exists.
# :changeFocus - A {Boolean} that allows the filePath to be opened without
# changing focus.
# :searchAllPanes - A {Boolean} that will open existing editors from any pane
# if the filePath is already open (default: false)
#
# Returns a promise that resolves to the {Editor} for the file URI.
open: (filePath, options={}) ->
changeFocus = options.changeFocus ? true
filePath = atom.project.resolve(filePath)
initialLine = options.initialLine
activePane = @activePane
searchAllPanes = options.searchAllPanes
split = options.split
uri = atom.project.relativize(filePath)
editor = activePane.itemForUri(atom.project.relativize(filePath)) if activePane and filePath
promise = atom.project.open(filePath, {initialLine}) if not editor
pane = switch split
when 'left'
@activePane.findLeftmostSibling()
when 'right'
@activePane.findOrCreateRightmostSibling()
else
if searchAllPanes
@paneContainer.paneForUri(uri) ? @activePane
else
@activePane
Q(editor ? promise)
Q(pane.itemForUri(uri) ? atom.project.open(filePath, options))
.then (editor) =>
if not activePane
activePane = new Pane(items: [editor])
@paneContainer.root = activePane
if not pane
pane = new Pane(items: [editor])
@paneContainer.root = pane
@itemOpened(editor)
activePane.activateItem(editor)
activePane.activate() if changeFocus
pane.activateItem(editor)
pane.activate() if changeFocus
@emit "uri-opened"
editor
.catch (error) ->
@@ -95,8 +111,7 @@ class Workspace extends Model
@activePane.activate() if activatePane
editor
# Public: Synchronously open an editor for the given URI or activate an existing
# editor in any pane if one already exists.
# Deprecated
openSingletonSync: (uri, options={}) ->
{initialLine, split} = options
# TODO: Remove deprecated changeFocus option
@@ -140,6 +155,11 @@ class Workspace extends Model
destroyActivePane: ->
@activePane?.destroy()
# Public: Returns an {Editor} if the active pane item is an {Editor},
# or null otherwise.
getActiveEditor: ->
@activePane?.getActiveEditor()
increaseFontSize: ->
atom.config.set("editor.fontSize", atom.config.get("editor.fontSize") + 1)
@@ -147,6 +167,9 @@ class Workspace extends Model
fontSize = atom.config.get("editor.fontSize")
atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1
resetFontSize: ->
atom.config.restoreDefault("editor.fontSize")
# Removes the item's uri from the list of potential items to reopen.
itemOpened: (item) ->
if uri = item.getUri?()