Merge branch 'master' into absolute-paths-in-fuzzy-finder

Conflicts:
	src/packages/fuzzy-finder/lib/load-paths-handler.coffee
This commit is contained in:
Chris Wanstrath
2013-03-27 19:10:50 -07:00
340 changed files with 3200 additions and 38710 deletions

View File

@@ -1,7 +1,7 @@
$ = require 'jquery'
AutocompleteView = require 'autocomplete/lib/autocomplete-view'
Autocomplete = require 'autocomplete/lib/autocomplete'
Buffer = require 'buffer'
Buffer = require 'text-buffer'
Editor = require 'editor'
RootView = require 'root-view'
@@ -14,7 +14,7 @@ describe "Autocomplete", ->
describe "@activate()", ->
it "activates autocomplete on all existing and future editors (but not on autocomplete's own mini editor)", ->
spyOn(AutocompleteView.prototype, 'initialize').andCallThrough()
autocompletePackage = window.loadPackage("autocomplete")
autocompletePackage = atom.activatePackage("autocomplete")
expect(AutocompleteView.prototype.initialize).not.toHaveBeenCalled()
leftEditor = rootView.getActiveView()
@@ -41,7 +41,7 @@ describe "AutocompleteView", ->
beforeEach ->
window.rootView = new RootView
editor = new Editor(editSession: project.buildEditSession('sample.js'))
window.loadPackage('autocomplete')
atom.activatePackage('autocomplete')
autocomplete = new AutocompleteView(editor)
miniEditor = autocomplete.miniEditor

View File

@@ -6,7 +6,7 @@ describe "Autoflow package", ->
beforeEach ->
window.rootView = new RootView
rootView.open()
window.loadPackage 'autoflow'
atom.activatePackage('autoflow')
editor = rootView.getActiveView()
config.set('editor.preferredLineLength', 30)

View File

@@ -41,7 +41,7 @@ module.exports =
goToMatchingPair: (editor) ->
return unless @pairHighlighted
return unless underlayer = editor.pane()?.find('.underlayer')
return unless underlayer = editor.getPane()?.find('.underlayer')
position = editor.getCursorBufferPosition()
previousPosition = position.translate([0, -1])
@@ -127,7 +127,7 @@ module.exports =
startPairPosition
updateMatch: (editor) ->
return unless underlayer = editor.pane()?.find('.underlayer')
return unless underlayer = editor.getPane()?.find('.underlayer')
@hideHighlightViews(editor) if @pairHighlighted
@pairHighlighted = false

View File

@@ -6,7 +6,7 @@ describe "bracket matching", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
window.loadPackage('bracket-matcher')
atom.activatePackage('bracket-matcher')
rootView.attachToDom()
editor = rootView.getActiveView()
editSession = editor.activeEditSession

View File

@@ -96,7 +96,7 @@ class CommandLoggerView extends ScrollView
w = @treeMap.width()
h = @treeMap.height()
d3 = require 'd3.v3'
d3 = require 'd3'
x = d3.scale.linear().range([0, w])
y = d3.scale.linear().range([0, h])

View File

@@ -21,8 +21,8 @@ module.exports =
eventNameLog.lastRun = new Date().getTime()
trigger = $.fn.trigger
@originalTrigger = trigger
$.fn.trigger = (eventName) ->
eventName = eventName.type if eventName.type
$.fn.trigger = (event) ->
eventName = event.type ? event
registerTriggeredEvent(eventName) if $(this).events()[eventName]
trigger.apply(this, arguments)

View File

@@ -7,7 +7,7 @@ describe "CommandLogger", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
commandLogger = window.loadPackage('command-logger').mainModule
commandLogger = atom.activatePackage('command-logger').mainModule
commandLogger.eventLog = {}
editor = rootView.getActiveView()

View File

@@ -13,7 +13,6 @@ class CommandPaletteView extends SelectList
filterKey: 'eventDescription'
previouslyFocusedElement: null
keyBindings: null
initialize: ->
@@ -30,10 +29,14 @@ class CommandPaletteView extends SelectList
attach: ->
super
@keyBindings = _.losslessInvert(keymap.bindingsForElement(@previouslyFocusedElement))
if @previouslyFocusedElement[0]
@eventElement = @previouslyFocusedElement
else
@eventElement = rootView
@keyBindings = _.losslessInvert(keymap.bindingsForElement(@eventElement))
events = []
for eventName, eventDescription of _.extend($(window).events(), @previouslyFocusedElement.events())
for eventName, eventDescription of _.extend($(window).events(), @eventElement.events())
events.push({eventName, eventDescription}) if eventDescription
events = _.sortBy events, (e) -> e.eventDescription
@@ -53,4 +56,4 @@ class CommandPaletteView extends SelectList
confirmed: ({eventName}) ->
@cancel()
@previouslyFocusedElement.trigger(eventName)
@eventElement.trigger(eventName)

View File

@@ -9,7 +9,7 @@ describe "CommandPalette", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
window.loadPackage("command-palette")
atom.activatePackage("command-palette")
rootView.attachToDom().focus()
rootView.trigger 'command-palette:toggle'
palette = rootView.find('.command-palette').view()
@@ -81,3 +81,20 @@ describe "CommandPalette", ->
expect(activeEditor.isFocused).toBeTruthy()
expect(eventHandler).toHaveBeenCalled()
expect(palette.hasParent()).toBeFalsy()
describe "when no element has focus", ->
it "uses the root view as the element to display and trigger events for", ->
rootView.trigger 'command-palette:toggle'
$(':focus').blur()
rootView.trigger 'command-palette:toggle'
keyBindings = _.losslessInvert(keymap.bindingsForElement(rootView.getActiveView()))
for eventName, description of rootView.events()
eventLi = palette.list.children("[data-event-name='#{eventName}']")
if description
expect(eventLi).toExist()
expect(eventLi.find('.label')).toHaveText(description)
expect(eventLi.find('.label').attr('title')).toBe(eventName)
for binding in keyBindings[eventName] ? []
expect(eventLi.find(".key-binding:contains(#{binding})")).toExist()
else
expect(eventLi).not.toExist()

View File

@@ -1,4 +1,4 @@
fs = require 'fs'
fs = require 'fs-utils'
PEG = require 'pegjs'
module.exports =
@@ -11,8 +11,12 @@ class CommandInterpreter
@lastRelativeAddress = compositeCommand if compositeCommand.isRelativeAddress()
compositeCommand.execute(@project, activeEditSession)
repeatRelativeAddress: (activeEditSession) ->
@lastRelativeAddress?.execute(@project, activeEditSession)
repeatRelativeAddress: (activeEditSession, {reverse}={}) ->
return unless @lastRelativeAddress
reverse ?= false
previousSelectionRange = activeEditSession.getSelection().getBufferRange()
address = if reverse then @lastRelativeAddress.reverse() else @lastRelativeAddress
repeatRelativeAddressInReverse: (activeEditSession) ->
@lastRelativeAddress?.reverse().execute(@project, activeEditSession)
address.execute(@project, activeEditSession).done ->
currentSelectionRange = activeEditSession.getSelection().getBufferRange()
$native.beep() if previousSelectionRange.isEqual(currentSelectionRange)

View File

@@ -44,7 +44,7 @@ class CommandPanelView extends View
rootView.command 'command-panel:find-in-file', => @attach('/')
rootView.command 'command-panel:find-in-project', => @attach('Xx/')
rootView.command 'command-panel:repeat-relative-address', => @repeatRelativeAddress()
rootView.command 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddressInReverse()
rootView.command 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddress(reverse: true)
rootView.command 'command-panel:set-selection-as-regex-address', => @setSelectionAsLastRelativeAddress()
@on 'click', '.expand', @onExpandAll
@@ -65,6 +65,9 @@ class CommandPanelView extends View
destroy: ->
@previewList.destroy()
rootView.off "command-panel:toggle-preview command-panel:find-in-file command-panel:find-in-project \
command-panel:repeat-relative-address command-panel:repeat-relative-address-in-reverse command-panel:set-selection-as-regex-address"
@remove()
toggle: ->
if @miniEditor.isFocused
@@ -115,7 +118,7 @@ class CommandPanelView extends View
escapedCommand: ->
@miniEditor.getText()
execute: (command=@escapedCommand())->
execute: (command=@escapedCommand()) ->
@loadingMessage.show()
@errorMessages.empty()
@@ -138,6 +141,7 @@ class CommandPanelView extends View
else
@detach()
catch error
@loadingMessage.hide()
if error.name is "SyntaxError"
@flashError()
return
@@ -154,11 +158,8 @@ class CommandPanelView extends View
@historyIndex++
@miniEditor.setText(@history[@historyIndex] or '')
repeatRelativeAddress: ->
@commandInterpreter.repeatRelativeAddress(rootView.getActivePaneItem())
repeatRelativeAddressInReverse: ->
@commandInterpreter.repeatRelativeAddressInReverse(rootView.getActivePaneItem())
repeatRelativeAddress: (options) ->
@commandInterpreter.repeatRelativeAddress(rootView.getActivePaneItem(), options)
setSelectionAsLastRelativeAddress: ->
selection = rootView.getActiveView().getSelectedText()

View File

@@ -6,7 +6,7 @@ module.exports =
class Address extends Command
compile: (project, buffer, ranges) ->
deferred = $.Deferred()
deferred.resolve ranges.map (range) =>
operations = ranges.map (range) =>
newRange = @getRange(buffer, range)
new Operation
@@ -15,6 +15,7 @@ class Address extends Command
bufferRange: newRange
errorMessage: @errorMessage
deferred.resolve(operations)
deferred.promise()
isAddress: -> true

View File

@@ -16,7 +16,7 @@ class SelectAllMatchesInProject extends Command
promise = project.scan @regex, ({path, range}) ->
operations.push(new Operation(
project: project
buffer: project.bufferForPath(path)
path: path
bufferRange: range
))

View File

@@ -4,7 +4,7 @@ module.exports =
class OperationView extends View
@content: ({operation} = {}) ->
{prefix, suffix, match, range} = operation.preview()
@li 'data-index': operation.index, class: 'operation', =>
@li class: 'operation', =>
@span range.start.row + 1, class: 'line-number'
@span class: 'preview', =>
@span prefix

View File

@@ -1,22 +1,30 @@
module.exports =
class Operation
constructor: ({@project, @buffer, bufferRange, @newText, @preserveSelection, @errorMessage}) ->
@buffer.retain()
@marker = @buffer.markRange(bufferRange)
constructor: ({@project, @path, @buffer, @bufferRange, @newText, @preserveSelection, @errorMessage}) ->
if @buffer?
@buffer.retain()
@getMarker()
getMarker: ->
@marker ?= @getBuffer().markRange(@bufferRange)
getBuffer: ->
@buffer ?= @project.bufferForPath(@path).retain()
getPath: ->
@project.relativize(@buffer.getPath())
path = @path ? @getBuffer().getPath()
@project.relativize(path)
getBufferRange: ->
@buffer.getMarkerRange(@marker)
@getBuffer().getMarkerRange(@getMarker())
execute: (editSession) ->
@buffer.change(@getBufferRange(), @newText) if @newText
@getBuffer().change(@getBufferRange(), @newText) if @newText
@getBufferRange() unless @preserveSelection
preview: ->
range = @buffer.getMarkerRange(@marker)
line = @buffer.lineForRow(range.start.row)
range = @getBuffer().getMarkerRange(@getMarker())
line = @getBuffer().lineForRow(range.start.row)
prefix = line[0...range.start.column]
match = line[range.start.column...range.end.column]
suffix = line[range.end.column..]
@@ -24,5 +32,5 @@ class Operation
{prefix, suffix, match, range}
destroy: ->
@buffer.destroyMarker(@marker)
@buffer.release()
@buffer?.destroyMarker(@marker) if @marker?
@buffer?.release()

View File

@@ -1,20 +1,18 @@
{View} = require 'space-pen'
fs = require 'fs'
fs = require 'fs-utils'
OperationView = require './operation-view'
$ = require 'jquery'
module.exports =
class PathView extends View
@content: ({path, operations, previewList} = {}) ->
@content: ({path, previewList} = {}) ->
classes = ['path']
classes.push('readme') if fs.isReadmePath(path)
@li class: classes.join(' '), =>
@div outlet: 'pathDetails', class: 'path-details', =>
@span class: 'path-name', path
@span "(#{operations.length})", class: 'path-match-number'
@span outlet: 'description', class: 'path-match-number'
@ul outlet: 'matches', class: 'matches', =>
for operation in operations
@subview "operation#{operation.index}", new OperationView({operation, previewList})
initialize: ({@previewList}) ->
@pathDetails.on 'mousedown', => @toggle(true)
@@ -27,6 +25,10 @@ class PathView extends View
@toggle(true)
false
addOperation: (operation) ->
@matches.append new OperationView({operation, @previewList})
@description.text("(#{@matches.find('li').length})")
isSelected: ->
@hasClass('selected') or @find('.selected').length

View File

@@ -1,7 +1,7 @@
$ = require 'jquery'
ScrollView = require 'scroll-view'
_ = require 'underscore'
fs = require 'fs'
fs = require 'fs-utils'
PathView = require './path-view'
OperationView = require './operation-view'
@@ -11,12 +11,17 @@ class PreviewList extends ScrollView
@ol class: 'preview-list', tabindex: -1
operations: null
viewsForPath: null
pixelOverdraw: 100
lastRenderedOperationIndex: null
initialize: ->
super
@on 'core:move-down', => @selectNextOperation(); false
@on 'core:move-up', => @selectPreviousOperation(); false
@on 'scroll', =>
@renderOperations() if @scrollBottom() >= (@prop('scrollHeight'))
@command 'command-panel:collapse-all', => @collapseAllPaths()
@command 'command-panel:expand-all', => @expandAllPaths()
@@ -25,6 +30,7 @@ class PreviewList extends ScrollView
@children().each (index, element) -> $(element).view().expand()
collapseAllPaths: ->
@renderOperations(renderAll: true)
@children().each (index, element) -> $(element).view().collapse()
destroy: ->
@@ -35,23 +41,31 @@ class PreviewList extends ScrollView
populate: (operations) ->
@destroyOperations() if @operations
@operations = operations
@lastRenderedOperationIndex = 0
@empty()
operation.index = index for operation, index in operations
operationsByPath = _.groupBy(operations, (operation) -> operation.getPath())
for path, operations of operationsByPath
@append new PathView({path, operations, previewList: this})
@viewsForPath = {}
@show()
@find('.operation:first').addClass('selected')
@setLineNumberWidth()
@renderOperations()
setLineNumberWidth: ->
lineNumbers = @find('.line-number')
maxWidth = 0
lineNumbers.each (index, element) ->
maxWidth = Math.max($(element).outerWidth(), maxWidth)
lineNumbers.width(maxWidth)
@find('.operation:first').addClass('selected')
renderOperations: ({renderAll}={}) ->
renderAll ?= false
startingScrollHeight = @prop('scrollHeight')
for operation in @operations[@lastRenderedOperationIndex..]
pathView = @pathViewForPath(operation.getPath())
pathView.addOperation(operation)
@lastRenderedOperationIndex++
break if not renderAll and @prop('scrollHeight') >= startingScrollHeight + @pixelOverdraw and @prop('scrollHeight') > @height() + @pixelOverdraw
pathViewForPath: (path) ->
pathView = @viewsForPath[path]
if not pathView
pathView = new PathView({path: path, previewList: this})
@viewsForPath[path] = pathView
@append(pathView)
pathView
selectNextOperation: ->
selectedView = @find('.selected').view()

View File

@@ -1,6 +1,6 @@
CommandInterpreter = require 'command-panel/lib/command-interpreter'
Project = require 'project'
Buffer = require 'buffer'
Buffer = require 'text-buffer'
EditSession = require 'edit-session'
_ = require 'underscore'

View File

@@ -11,7 +11,7 @@ describe "CommandPanel", ->
rootView.enableKeymap()
editSession = rootView.getActivePaneItem()
buffer = editSession.buffer
commandPanelMain = window.loadPackage('command-panel', activateImmediately: true).mainModule
commandPanelMain = atom.activatePackage('command-panel', immediate: true).mainModule
commandPanel = commandPanelMain.commandPanelView
commandPanel.history = []
commandPanel.historyIndex = 0
@@ -28,11 +28,8 @@ describe "CommandPanel", ->
rootView.trigger 'command-panel:toggle'
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
rootViewState = rootView.serialize()
rootView.deactivate()
window.rootView = RootView.deserialize(rootViewState)
rootView.attachToDom()
window.loadPackage('command-panel')
atom.deactivatePackage('command-panel')
atom.activatePackage('command-panel')
expect(rootView.find('.command-panel')).not.toExist()
rootView.trigger 'command-panel:toggle'
@@ -55,13 +52,12 @@ describe "CommandPanel", ->
expect(commandPanel.history[2]).toBe('/test3')
expect(commandPanel.historyIndex).toBe(3)
rootViewState = rootView.serialize()
rootView.deactivate()
RootView.deserialize(rootViewState).attachToDom()
window.loadPackage('command-panel')
rootView.trigger 'command-panel:toggle'
atom.deactivatePackage('command-panel')
atom.activatePackage('command-panel')
rootView.trigger 'command-panel:toggle'
commandPanel = rootView.find('.command-panel').view()
expect(commandPanel.history.length).toBe(2)
expect(commandPanel.history[0]).toBe('/test2')
expect(commandPanel.history[1]).toBe('/test3')
@@ -216,39 +212,69 @@ describe "CommandPanel", ->
expect(commandPanel.miniEditor.hiddenInput).not.toMatchSelector ':focus'
describe "when command-panel:repeat-relative-address is triggered on the root view", ->
it "repeats the last search command if there is one", ->
rootView.trigger 'command-panel:repeat-relative-address'
describe "when there is more than one match", ->
it "repeats the last search command if there is one", ->
rootView.trigger 'command-panel:repeat-relative-address'
editSession.setCursorScreenPosition([4, 0])
editSession.setCursorScreenPosition([4, 0])
commandPanel.execute("/current")
expect(editSession.getSelectedBufferRange()).toEqual [[5,6], [5,13]]
commandPanel.execute("/current")
expect(editSession.getSelectedBufferRange()).toEqual [[5,6], [5,13]]
rootView.trigger 'command-panel:repeat-relative-address'
expect(editSession.getSelectedBufferRange()).toEqual [[6,6], [6,13]]
rootView.trigger 'command-panel:repeat-relative-address'
expect(editSession.getSelectedBufferRange()).toEqual [[6,6], [6,13]]
commandPanel.execute('s/r/R/g')
commandPanel.execute('s/r/R/g')
rootView.trigger 'command-panel:repeat-relative-address'
expect(editSession.getSelectedBufferRange()).toEqual [[6,34], [6,41]]
rootView.trigger 'command-panel:repeat-relative-address'
expect(editSession.getSelectedBufferRange()).toEqual [[6,34], [6,41]]
commandPanel.execute('0')
commandPanel.execute('/sort/ s/r/R/') # this contains a substitution... won't be repeated
commandPanel.execute('0')
commandPanel.execute('/sort/ s/r/R/') # this contains a substitution... won't be repeated
rootView.trigger 'command-panel:repeat-relative-address'
expect(editSession.getSelectedBufferRange()).toEqual [[3,31], [3,38]]
rootView.trigger 'command-panel:repeat-relative-address'
expect(editSession.getSelectedBufferRange()).toEqual [[3,31], [3,38]]
describe "when there is only one match and it is selected", ->
it "maintains the current selection and plays a beep", ->
editSession.setCursorScreenPosition([0, 0])
waitsForPromise ->
commandPanel.execute("/Array")
runs ->
expect(editSession.getSelectedBufferRange()).toEqual [[11,14], [11,19]]
spyOn($native, 'beep')
rootView.trigger 'command-panel:repeat-relative-address'
waitsFor ->
$native.beep.callCount > 0
runs ->
expect(editSession.getSelectedBufferRange()).toEqual [[11,14], [11,19]]
describe "when command-panel:repeat-relative-address-in-reverse is triggered on the root view", ->
it "it repeats the last relative address in the reverse direction", ->
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
describe "when there is more than one match", ->
it "it repeats the last relative address in the reverse direction", ->
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
editSession.setCursorScreenPosition([6, 0])
editSession.setCursorScreenPosition([6, 0])
commandPanel.execute("/current")
expect(editSession.getSelectedBufferRange()).toEqual [[6,6], [6,13]]
commandPanel.execute("/current")
expect(editSession.getSelectedBufferRange()).toEqual [[6,6], [6,13]]
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
expect(editSession.getSelectedBufferRange()).toEqual [[5,6], [5,13]]
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
expect(editSession.getSelectedBufferRange()).toEqual [[5,6], [5,13]]
describe "when there is only one match and it is selected", ->
it "maintains the current selection and plays a beep", ->
editSession.setCursorScreenPosition([0, 0])
waitsForPromise ->
commandPanel.execute("/Array")
runs ->
expect(editSession.getSelectedBufferRange()).toEqual [[11,14], [11,19]]
spyOn($native, 'beep')
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
waitsFor ->
$native.beep.callCount > 0
runs ->
expect(editSession.getSelectedBufferRange()).toEqual [[11,14], [11,19]]
describe "when command-panel:set-selection-as-regex-address is triggered on the root view", ->
it "sets the @lastRelativeAddress to a RegexAddress of the current selection", ->
@@ -319,13 +345,15 @@ describe "CommandPanel", ->
# there shouldn't be any dangling operations after this
describe "if the command is malformed", ->
it "adds and removes an error class to the command panel and does not close it", ->
it "adds and removes an error class to the command panel and does not close it or display a loading message", ->
rootView.attachToDom()
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.insertText 'garbage-command!!'
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
expect(commandPanel.parent()).toExist()
expect(commandPanel).toHaveClass 'error'
expect(commandPanel.loadingMessage).toBeHidden()
advanceClock 400

View File

@@ -0,0 +1,45 @@
RootView = require 'root-view'
CommandPanelView = require 'command-panel/lib/command-panel-view'
_ = require 'underscore'
describe "Preview List", ->
[previewList, commandPanelMain, commandPanelView] = []
beforeEach ->
window.rootView = new RootView()
rootView.attachToDom()
commandPanelMain = atom.activatePackage('command-panel', immediate: true).mainModule
commandPanelView = commandPanelMain.commandPanelView
previewList = commandPanelView.previewList
rootView.trigger 'command-panel:toggle'
describe "when the list is scrollable", ->
it "adds more operations to the DOM when `scrollBottom` nears the `pixelOverdraw`", ->
waitsForPromise ->
commandPanelView.execute('X x/so/')
runs ->
expect(previewList.prop('scrollHeight')).toBeGreaterThan previewList.height()
previousScrollHeight = previewList.prop('scrollHeight')
previousOperationCount = previewList.find("li").length
previewList.scrollTop(previewList.pixelOverdraw / 2)
previewList.trigger('scroll') # Not sure why scroll event isn't being triggered on it's own
expect(previewList.prop('scrollHeight')).toBe previousScrollHeight
expect(previewList.find("li").length).toBe previousOperationCount
previewList.scrollToBottom()
previewList.trigger('scroll') # Not sure why scroll event isn't being triggered on it's own
expect(previewList.prop('scrollHeight')).toBeGreaterThan previousScrollHeight
expect(previewList.find("li").length).toBeGreaterThan previousOperationCount
it "renders all operations if the preview items are collapsed", ->
waitsForPromise ->
commandPanelView.execute('X x/so/')
runs ->
expect(previewList.prop('scrollHeight')).toBeGreaterThan previewList.height()
previousScrollHeight = previewList.prop('scrollHeight')
previousOperationCount = previewList.find("li").length
previewList.collapseAllPaths()
expect(previewList.find("li").length).toBeGreaterThan previousOperationCount

View File

@@ -0,0 +1,132 @@
.command-panel {
position: relative;
padding: 0;
.is-loading {
display: block;
margin: 0 auto 10px auto;
width: 100px;
background-color: #111111;
background-size: auto;
background-position: 5px 5px;
padding: 5px 5px 10px 30px;
border-radius: 3px;
border: 1px solid rgba(255,255,255,0.1);
border-top: 1px solid rgba(0,0,0,1);
border-left: 1px solid rgba(0,0,0,1);
}
.preview-count {
display: inline-block;
margin-top: 4px;
font-size: 11px;
-webkit-user-select: none;
}
.preview-list {
max-height: 300px;
overflow: auto;
margin: 0 0 10px 0;
position: relative;
cursor: default;
.path {
position: relative;
-webkit-user-select: none;
}
.path-details:before {
font-family: 'Octicons Regular';
font-size: 12px;
width: 12px;
height: 12px;
margin-right: 5px;
margin-left: 5px;
-webkit-font-smoothing: antialiased;
content: "\f05b";
position: relative;
top: 0;
}
.is-collapsed .path-details:before {
content: "\f05a";
}
.path-name:before {
font-family: 'Octicons Regular';
font-size: 16px;
width: 16px;
height: 16px;
margin-right: 5px;
-webkit-font-smoothing: antialiased;
content: "\f011";
position: relative;
top: 1px;
}
.path.readme .path-name:before {
content: "\f007";
}
.operation {
padding-top: 2px;
padding-bottom: 2px;
padding-left: 10px;
}
.line-number {
margin-right: 1ex;
text-align: right;
display: inline-block;
}
.path-match-number {
padding-left: 8px;
}
.preview {
word-break: break-all;
.match {
-webkit-border-radius: 2px;
padding: 1px;
}
}
}
.header:after {
content: ".";
display: block;
visibility: hidden;
clear: both;
height: 0;
}
.expand-collapse {
float: right;
-webkit-user-select: none;
li {
display: inline-block;
cursor: pointer;
font-size: 11px;
margin-left: 5px;
padding: 5px 10px;
border-radius: 3px;
}
}
.prompt-and-editor {
display: -webkit-flex;
.editor {
position: relative;
-webkit-flex: 1;
}
}
}
.error-messages {
padding: 5px 1em;
color: white;
}

View File

@@ -1,5 +1,5 @@
ScrollView = require 'scroll-view'
d3 = require 'd3.v3'
d3 = require 'd3'
_ = require 'underscore'
$ = require 'jquery'

View File

@@ -17,7 +17,7 @@ describe "EditorStats", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
editorStats = window.loadPackage('editor-stats').mainModule.stats
editorStats = atom.activatePackage('editor-stats').mainModule.stats
describe "when a keyup event is triggered", ->
beforeEach ->

View File

@@ -2,7 +2,6 @@
padding: 5px;
box-sizing: border-box;
border-top: 1px solid rgba(255, 255, 255, 0.05);
z-index: 9999;
}
.editor-stats {

View File

@@ -2,7 +2,7 @@
SelectList = require 'select-list'
_ = require 'underscore'
$ = require 'jquery'
fs = require 'fs'
fs = require 'fs-utils'
LoadPathsTask = require './load-paths-task'
module.exports =
@@ -139,7 +139,7 @@ class FuzzyFinderView extends SelectList
@setArray(paths)
populateProjectPaths: (options = {}) ->
if @projectPaths?.length > 0
if @projectPaths?
listedItems =
if options.filter?
@projectPaths.filter (path) ->
@@ -156,21 +156,15 @@ class FuzzyFinderView extends SelectList
callback = (paths) =>
@projectPaths = paths
@reloadProjectPaths = false
listedItems =
if options.filter?
@projectPaths.filter (path) ->
path.indexOf(options.filter) >= 0
else
@projectPaths
@setArray(listedItems)
options.done(listedItems) if options.done?
@populateProjectPaths(options)
@loadPathsTask = new LoadPathsTask(callback)
@loadPathsTask.start()
populateOpenBufferPaths: ->
editSessions = project.getEditSessions().filter (editSession)->
editSessions = project.getEditSessions().filter (editSession) ->
editSession.getPath()?
editSessions = _.uniq editSessions, (editSession) ->
editSession.getPath()
editSessions = _.sortBy editSessions, (editSession) =>
if editSession is rootView.getActivePaneItem()

View File

@@ -1,24 +0,0 @@
fs = require 'fs'
_ = require 'underscore'
module.exports =
loadPaths: (rootPath, ignoredNames, excludeGitIgnoredPaths) ->
if excludeGitIgnoredPaths
Git = require 'git'
repo = Git.open(rootPath, refreshOnWindowFocus: false)
paths = []
isIgnored = (path) ->
path = path.substring(rootPath.length + 1)
for segment in path.split('/')
return true if _.contains(ignoredNames, segment)
repo?.isPathIgnored(fs.join(rootPath, path))
onFile = (path) ->
paths.push(path) unless isIgnored(path)
onDirectory = (path) ->
not isIgnored(path)
fs.traverseTree(rootPath, onFile, onDirectory)
repo?.destroy()
callTaskMethod('pathsLoaded', paths)

View File

@@ -1,17 +1,33 @@
Task = require 'task'
_ = require 'underscore'
fs = require 'fs-utils'
module.exports =
class LoadPathsTask extends Task
constructor: (@callback) ->
super('fuzzy-finder/lib/load-paths-handler')
class LoadPathsTask
aborted: false
started: ->
constructor: (@callback) ->
start: ->
rootPath = project.getPath()
ignoredNames = config.get('fuzzyFinder.ignoredNames') ? []
ignoredNames = ignoredNames.concat(config.get('core.ignoredNames') ? [])
excludeGitIgnoredPaths = config.get('core.hideGitIgnoredFiles')
rootPath = project.getPath()
@callWorkerMethod('loadPaths', rootPath, ignoredNames, excludeGitIgnoredPaths)
ignoreGitIgnoredFiles = config.get('core.hideGitIgnoredFiles')
pathsLoaded: (paths) ->
@done()
@callback(paths)
paths = []
isIgnored = (path) ->
for segment in path.split('/')
return true if _.contains(ignoredNames, segment)
ignoreGitIgnoredFiles and git?.isPathIgnored(fs.join(rootPath, path))
onFile = (path) ->
return if @aborted
path = path.substring(rootPath.length + 1)
paths.push(path) unless isIgnored(path)
onDirectory = (path) =>
not @aborted and not isIgnored(path.substring(rootPath.length + 1))
onDone = =>
@callback(paths) unless @aborted
fs.traverseTree(rootPath, onFile, onDirectory, onDone)
abort: ->
@aborted = true

View File

@@ -4,7 +4,7 @@ LoadPathsTask = require 'fuzzy-finder/lib/load-paths-task'
_ = require 'underscore'
$ = require 'jquery'
{$$} = require 'space-pen'
fs = require 'fs'
fs = require 'fs-utils'
describe 'FuzzyFinder', ->
[finderView] = []
@@ -13,7 +13,7 @@ describe 'FuzzyFinder', ->
window.rootView = new RootView
rootView.open('sample.js')
rootView.enableKeymap()
finderView = window.loadPackage("fuzzy-finder").mainModule.createView()
finderView = atom.activatePackage("fuzzy-finder").mainModule.createView()
describe "file-finder behavior", ->
describe "toggling", ->
@@ -43,13 +43,14 @@ describe 'FuzzyFinder', ->
it "shows all relative file paths for the current project and selects the first", ->
rootView.attachToDom()
finderView.maxItems = Infinity
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:toggle-file-finder'
paths = null
expect(finderView.find(".loading")).toBeVisible()
expect(finderView.find(".loading")).toHaveText "Indexing..."
waitsFor "all project paths to load", 5000, ->
if finderView.projectPaths?.length > 0
unless finderView.reloadProjectPaths
paths = finderView.projectPaths
true
@@ -142,8 +143,8 @@ describe 'FuzzyFinder', ->
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
rootView.open()
states = rootView.serialize().packages
states = _.map states['fuzzy-finder'], (path, time) -> [ path, time ]
atom.deactivatePackage('fuzzy-finder')
states = _.map atom.getPackageState('fuzzy-finder'), (path, time) -> [ path, time ]
states = _.sortBy states, (path, time) -> -time
paths = [ 'sample-with-tabs.coffee', 'sample.txt', 'sample.js' ]
@@ -165,6 +166,13 @@ describe 'FuzzyFinder', ->
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
expect(rootView.find('.fuzzy-finder')).not.toExist()
describe "when multiple sessions are opened on the same path", ->
it "does not display duplicates for that path in the list", ->
rootView.open 'sample.js'
rootView.getActivePane().splitRight()
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
expect(_.pluck(finderView.list.children('li'), 'outerText')).toEqual ['sample.js']
describe "when a path selection is confirmed", ->
[editor1, editor2] = []
@@ -271,6 +279,7 @@ describe 'FuzzyFinder', ->
describe "cached file paths", ->
it "caches file paths after first time", ->
spyOn(LoadPathsTask.prototype, "start").andCallThrough()
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:toggle-file-finder'
waitsFor ->
@@ -290,6 +299,7 @@ describe 'FuzzyFinder', ->
it "doesn't cache buffer paths", ->
spyOn(project, "getEditSessions").andCallThrough()
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
waitsFor ->
@@ -309,6 +319,7 @@ describe 'FuzzyFinder', ->
it "busts the cache when the window gains focus", ->
spyOn(LoadPathsTask.prototype, "start").andCallThrough()
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:toggle-file-finder'
waitsFor ->
@@ -325,6 +336,7 @@ describe 'FuzzyFinder', ->
describe "path ignoring", ->
it "ignores paths that match entries in config.fuzzyFinder.ignoredNames", ->
config.set("fuzzyFinder.ignoredNames", ["tree-view.js"])
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:toggle-file-finder'
finderView.maxItems = Infinity
@@ -343,6 +355,7 @@ describe 'FuzzyFinder', ->
it "opens the fuzzy finder window when there are multiple matches", ->
editor.setText("sample")
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:find-under-cursor'
waitsFor ->
@@ -354,6 +367,7 @@ describe 'FuzzyFinder', ->
it "opens a file directly when there is a single match", ->
editor.setText("sample.txt")
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:find-under-cursor'
openedPath = null
@@ -367,10 +381,11 @@ describe 'FuzzyFinder', ->
expect(finderView).not.toBeVisible()
expect(openedPath).toBe "sample.txt"
it "displays error when the word under the cursor doesn't match any files", ->
it "displays an error when the word under the cursor doesn't match any files", ->
editor.setText("moogoogaipan")
editor.setCursorBufferPosition([0,5])
jasmine.unspy(window, "setTimeout")
rootView.trigger 'fuzzy-finder:find-under-cursor'
waitsFor ->

View File

@@ -0,0 +1,64 @@
.fuzzy-finder .directory {
color: rgba(0, 0, 0, 0.5);
word-break: break-word;
margin-left: 5px;
line-height: 150%;
}
.fuzzy-finder .file:before {
font-family: 'Octicons Regular';
font-size: 16px;
width: 16px;
height: 16px;
margin-right: 5px;
margin-left: 5px;
-webkit-font-smoothing: antialiased;
color: #9d9d9d;
}
.fuzzy-finder .status {
font-family: 'Octicons Regular';
font-size: 16px;
width: 16px;
height: 16px;
margin-left: 5px;
-webkit-font-smoothing: antialiased;
color: #9d9d9d;
float: right;
}
.fuzzy-finder .status.new:before {
position: relative;
top: 3px;
content: "\f06b";
}
.fuzzy-finder .status.modified:before {
position: relative;
top: 3px;
content: "\f06d";
}
.fuzzy-finder .file.text-name:before {
content: "\f011";
}
.fuzzy-finder .file.image-name:before {
content: "\f012";
}
.fuzzy-finder .file.compressed-name:before {
content: "\f013";
}
.fuzzy-finder .file.pdf-name:before {
content: "\f014";
}
.fuzzy-finder .file.readme-name:before {
content: "\f007";
}
.fuzzy-finder .file.binary-name:before {
content: "\f094";
}

View File

@@ -1,4 +0,0 @@
'name': 'Indent'
'scope': 'source.gfm'
'settings':
'increaseIndentPattern': '^\\s*([\\*\\+-])[ \\t]+'

View File

@@ -1,4 +0,0 @@
'name': 'bold text'
'scope': 'source.gfm'
'tabTrigger': 'b'
'content': '**$1**$0'

View File

@@ -1,4 +0,0 @@
'name': 'code block'
'scope': 'source.gfm'
'tabTrigger': 'code'
'content': '```$1\n$2\n```$0'

View File

@@ -1,4 +0,0 @@
'name': 'embedded image'
'scope': 'source.gfm'
'tabTrigger': 'img'
'content': '![$1]($2)$0'

View File

@@ -1,4 +0,0 @@
'name': 'italic text'
'scope': 'source.gfm'
'tabTrigger': 'i'
'content': '*$1*$0'

View File

@@ -1,4 +0,0 @@
'name': 'link'
'scope': 'source.gfm'
'tabTrigger': 'l'
'content': '[$1]($2)$0'

View File

@@ -7,6 +7,7 @@
'mkdown'
'ron'
]
'patterns': [
{
'match': '(?:^|\\s)(\\*\\*[^\\*]+\\*\\*)'

View File

@@ -0,0 +1,3 @@
'.source.gfm':
'editor':
'increaseIndentPattern': '^\\s*([\\*\\+-])[ \\t]+'

View File

@@ -0,0 +1,16 @@
'.source.gfm':
'bold text':
prefix: 'b'
body: '**$1**$0'
'embedded image':
prefix: 'img'
body: '![$1]($2)$0'
'italic text':
prefix: 'i'
body: '*$1*$0'
'link':
prefix: 'l'
body: '[$1]($2)$0'
'code':
prefix: 'code'
body: '```$1\n$2\n```$0'

View File

@@ -4,13 +4,13 @@ describe "GitHub Flavored Markdown grammar", ->
grammar = null
beforeEach ->
spyOn(syntax, "addGrammar")
pack = new TextMatePackage(require.resolve("gfm.tmbundle"))
pack.load()
grammar = pack.grammars[0]
spyOn(syntax, "addGrammar").andCallThrough()
atom.activatePackage("gfm")
expect(syntax.addGrammar).toHaveBeenCalled()
grammar = syntax.addGrammar.argsForCall[0][0]
it "parses the grammar", ->
expect(grammar).toBeTruthy()
expect(grammar).toBeDefined()
expect(grammar.scopeName).toBe "source.gfm"
it "tokenizes horizontal rules", ->

View File

@@ -8,7 +8,7 @@ class Gists
constructor: ->
rootView.command 'gist:create', '.editor', => @createGist()
createGist: (editor) ->
createGist: ->
editor = rootView.getActiveView()
return unless editor?

View File

@@ -7,7 +7,7 @@ describe "Gists package", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
window.loadPackage('gists')
atom.activatePackage('gists')
editor = rootView.getActiveView()
spyOn($, 'ajax')

View File

@@ -0,0 +1,3 @@
'.editor':
'meta-L': 'grammar-selector:show'

View File

@@ -0,0 +1,64 @@
SelectList = require 'select-list'
Editor = require 'editor'
{$$} = require 'space-pen'
module.exports =
class GrammarSelector extends SelectList
@viewClass: -> "#{super} grammar-selector from-top overlay mini"
@activate: ->
rootView.command 'grammar-selector:show', '.editor', => new GrammarSelector()
filterKey: 'name'
initialize: ->
@editor = rootView.getActiveView()
return unless @editor instanceof Editor
@currentGrammar = @editor.getGrammar()
@path = @editor.getPath()
@autoDetect = name: 'Auto Detect'
@command 'grammar-selector:show', =>
@cancel()
false
super
@populate()
@attach()
itemForElement: (grammar) ->
if grammar is @currentGrammar
grammarClass = 'active-item'
else
grammarClass = 'inactive-item'
$$ ->
@li grammar.name, class: grammarClass
populate: ->
grammars = new Array(syntax.grammars...)
grammars.sort (grammarA, grammarB) ->
if grammarA.scopeName is 'text.plain'
-1
else if grammarB.scopeName is 'text.plain'
1
else if grammarA.name < grammarB.name
-1
else if grammarA.name > grammarB.name
1
else
0
grammars.unshift(@autoDetect)
@setArray(grammars)
confirmed: (grammar) ->
@cancel()
if grammar is @autoDetect
syntax.clearGrammarOverrideForPath(@path)
else
syntax.setGrammarOverrideForPath(@path, grammar.scopeName)
@editor.reloadGrammar()
attach: ->
super
rootView.append(this)
@miniEditor.focus()

View File

@@ -0,0 +1,2 @@
'main': 'lib/grammar-selector'
'activationEvents': ['grammar-selector:show']

View File

@@ -0,0 +1,47 @@
GrammarSelector = require '../lib/grammar-selector'
RootView = require 'root-view'
_ = require 'underscore'
describe "GrammarSelector", ->
[editor, textGrammar, jsGrammar] = []
beforeEach ->
window.rootView = new RootView
atom.activatePackage('grammar-selector')
atom.activatePackage('text.tmbundle', sync: true)
atom.activatePackage('javascript.tmbundle', sync: true)
rootView.open('sample.js')
editor = rootView.getActiveView()
textGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'Plain Text'
expect(textGrammar).toBeTruthy()
jsGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'JavaScript'
expect(jsGrammar).toBeTruthy()
expect(editor.getGrammar()).toBe jsGrammar
describe "when grammar-selector:show is triggered", ->
it "displays a list of all the available grammars", ->
editor.trigger 'grammar-selector:show'
grammarView = rootView.find('.grammar-selector').view()
expect(grammarView).toExist()
grammars = syntax.grammars
expect(grammarView.list.children('li').length).toBe grammars.length + 1
expect(grammarView.list.children('li:first').text()).toBe 'Auto Detect'
describe "when a grammar is selected", ->
it "sets the new grammar on the editor", ->
editor.trigger 'grammar-selector:show'
grammarView = rootView.find('.grammar-selector').view()
grammarView.confirmed(textGrammar)
expect(editor.getGrammar()).toBe textGrammar
describe "when auto-detect is selected", ->
it "restores the auto-detected grammar on the editor", ->
editor.trigger 'grammar-selector:show'
grammarView = rootView.find('.grammar-selector').view()
grammarView.confirmed(textGrammar)
expect(editor.getGrammar()).toBe textGrammar
editor.trigger 'grammar-selector:show'
grammarView = rootView.find('.grammar-selector').view()
grammarView.confirmed(grammarView.array[0])
expect(editor.getGrammar()).toBe jsGrammar

View File

@@ -1,4 +1,4 @@
fs = require 'fs'
fs = require 'fs-utils'
$ = require 'jquery'
ScrollView = require 'scroll-view'
{$$$} = require 'space-pen'
@@ -29,6 +29,9 @@ class MarkdownPreviewView extends ScrollView
getUri: ->
"markdown-preview:#{@buffer.getPath()}"
getPath: ->
@buffer.getPath()
setErrorHtml: ->
@html $$$ ->
@h2 'Previewing Markdown Failed'

View File

@@ -22,4 +22,4 @@ module.exports =
nextPane.showItem(new MarkdownPreviewView(editSession.buffer))
else
activePane.splitRight(new MarkdownPreviewView(editSession.buffer))
activePane.focus()
activePane.focus()

View File

@@ -6,7 +6,7 @@ describe "MarkdownPreview package", ->
beforeEach ->
project.setPath(project.resolve('markdown'))
window.rootView = new RootView
window.loadPackage("markdown-preview", activateImmediately: true)
atom.activatePackage("markdown-preview", immediate: true)
spyOn(MarkdownPreviewView.prototype, 'fetchRenderedMarkdown')
describe "markdown-preview:show", ->

View File

@@ -2,7 +2,7 @@
Editor = require 'editor'
$ = require 'jquery'
_ = require 'underscore'
fs = require 'fs'
fs = require 'fs-utils'
module.exports =
class PackageGeneratorView extends View
@@ -56,7 +56,7 @@ class PackageGeneratorView extends View
true
createPackageFiles: ->
templatePath = require.resolve(fs.join("package-generator", "template"))
templatePath = fs.resolveOnLoadPath(fs.join("package-generator", "template"))
packageName = fs.base(@getPackagePath())
for path in fs.listTree(templatePath)

View File

@@ -1,5 +1,5 @@
RootView = require 'root-view'
fs = require 'fs'
fs = require 'fs-utils'
describe 'Package Generator', ->
[packageGenerator] = []
@@ -7,7 +7,7 @@ describe 'Package Generator', ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
window.loadPackage("package-generator")
atom.activatePackage("package-generator")
describe "when package-generator:generate is triggered", ->
it "displays a miniEditor", ->

View File

@@ -10,7 +10,7 @@ fdescribe "__PackageName__View", ->
beforeEach ->
window.rootView = new RootView
__packageName__ = window.loadPackage('__packageName__', activateImmediately: true)
__packageName__ = atom.activatePackage('__packageName__', immediate: true)
describe "when the __package-name__:toggle event is triggered", ->
it "attaches and then detaches the view", ->

View File

@@ -1,55 +0,0 @@
fs = require 'fs'
TextMatePackage = require 'text-mate-package'
SnippetBodyParser = require './snippet-body-parser'
module.exports =
snippetsLoaded: (snippets) ->
for snippet in snippets
for selector, snippetsByName of snippet
for name, attributes of snippetsByName
attributes.bodyTree = SnippetBodyParser.parse(attributes.body)
callTaskMethod('snippetsLoaded', snippets)
loadTextMateSnippets: (path) ->
snippetsDirPath = fs.join(path, 'Snippets')
snippets = []
for snippetsPath in fs.list(snippetsDirPath)
logWarning = ->
console.warn "Error reading TextMate snippets file '#{snippetsPath}'"
continue if fs.base(snippetsPath).indexOf('.') is 0
try
if fs.isObjectPath(snippetsPath) and object = fs.readObject(snippetsPath)
snippets.push(object)
else if object = fs.readPlist(snippetsPath)
snippets.push(object)
else
logWarning()
catch e
logWarning()
@snippetsLoaded(@translateTextmateSnippets(snippets))
loadAtomSnippets: (path) ->
snippetsDirPath = fs.join(path, 'snippets')
snippets = []
for snippetsPath in fs.list(snippetsDirPath)
continue if fs.base(snippetsPath).indexOf('.') is 0
try
snippets.push(fs.readObject(snippetsPath))
catch e
console.warn "Error reading snippets file '#{snippetsPath}'"
@snippetsLoaded(snippets)
translateTextmateSnippets: (tmSnippets) ->
atomSnippets = {}
for { scope, name, content, tabTrigger } in tmSnippets
if scope
scope = TextMatePackage.cssSelectorFromScopeSelector(scope)
else
scope = '*'
snippetsForScope = (atomSnippets[scope] ?= {})
snippetsForScope[name] = { prefix: tabTrigger, body: content }
[atomSnippets]

View File

@@ -1,34 +0,0 @@
Task = require 'task'
TextMatePackage = require 'text-mate-package'
module.exports =
class LoadSnippetsTask extends Task
constructor: (@snippets) ->
super('snippets/lib/load-snippets-handler')
@packages = atom.getLoadedPackages()
@packages.push(path: config.configDirPath)
started: ->
@loadNextPackageSnippets()
loadNextPackageSnippets: ->
unless @packages.length
@done()
@snippets.loaded = true
return
@packageBeingLoaded = @packages.shift()
if @packageBeingLoaded instanceof TextMatePackage
@loadTextMateSnippets(@packageBeingLoaded.path)
else
@loadAtomSnippets(@packageBeingLoaded.path)
loadAtomSnippets: (path) ->
@callWorkerMethod('loadAtomSnippets', path)
loadTextMateSnippets: (path) ->
@callWorkerMethod('loadTextMateSnippets', path)
snippetsLoaded: (snippets) ->
@snippets.add(snippet) for snippet in snippets
@loadNextPackageSnippets()

View File

@@ -1,4 +1,4 @@
PEG = require 'pegjs'
fs = require 'fs'
fs = require 'fs-utils'
grammarSrc = fs.read(require.resolve('./snippet-body.pegjs'))
module.exports = PEG.buildParser(grammarSrc, trackLineAndColumn: true)

View File

@@ -27,7 +27,7 @@ class SnippetExpansion
return if @settingTabStop or bufferChanged
oldTabStops = @tabStopsForBufferPosition(oldBufferPosition)
newTabStops = @tabStopsForBufferPosition(newBufferPosition)
@destroy() unless _.intersect(oldTabStops, newTabStops).length
@destroy() unless _.intersection(oldTabStops, newTabStops).length
placeTabStopMarkers: (startPosition, tabStopRanges) ->
@tabStopMarkers = tabStopRanges.map ({start, end}) =>

View File

@@ -1,9 +1,12 @@
AtomPackage = require 'atom-package'
fs = require 'fs'
fsUtils = require 'fs-utils'
_ = require 'underscore'
SnippetExpansion = require './snippet-expansion'
Snippet = require './snippet'
LoadSnippetsTask = require './load-snippets-task'
TextMatePackage = require 'text-mate-package'
CSON = require 'cson'
async = require 'async'
module.exports =
snippetsByExtension: {}
@@ -16,22 +19,85 @@ module.exports =
@enableSnippetsInEditor(editor) if editor.attached
deactivate: ->
@loadSnippetsTask?.abort()
loadAll: ->
@loadSnippetsTask = new LoadSnippetsTask(this)
@loadSnippetsTask.start()
packages = atom.getLoadedPackages()
packages.push(path: config.configDirPath)
async.eachSeries packages, @loadSnippetsFromPackage.bind(this), @doneLoading.bind(this)
loadDirectory: (snippetsDirPath) ->
for snippetsPath in fs.list(snippetsDirPath) when fs.base(snippetsPath).indexOf('.') isnt 0
snippets.loadFile(snippetsPath)
doneLoading: ->
@loaded = true
loadFile: (snippetsPath) ->
try
snippets = fs.readObject(snippetsPath)
catch e
console.warn "Error reading snippets file '#{snippetsPath}'"
@add(snippets)
loadSnippetsFromPackage: (pack, done) ->
if pack instanceof TextMatePackage
@loadTextMateSnippets(pack.path, done)
else
@loadAtomSnippets(pack.path, done)
loadAtomSnippets: (path, done) ->
snippetsDirPath = fsUtils.join(path, 'snippets')
return done() unless fsUtils.isDirectory(snippetsDirPath)
loadSnippetFile = (filename, done) =>
return done() if filename.indexOf('.') is 0
filepath = fsUtils.join(snippetsDirPath, filename)
CSON.readObjectAsync filepath, (err, object) =>
if err
console.warn "Error reading snippets file '#{filepath}': #{err.stack}"
else
@add(object)
done()
fs.readdir snippetsDirPath, (err, paths) ->
async.eachSeries(paths, loadSnippetFile, done)
loadTextMateSnippets: (path, done) ->
snippetsDirPath = fsUtils.join(path, 'Snippets')
return done() unless fsUtils.isDirectory(snippetsDirPath)
loadSnippetFile = (filename, done) =>
return done() if filename.indexOf('.') is 0
filepath = fsUtils.join(snippetsDirPath, filename)
logError = (err) ->
console.warn "Error reading snippets file '#{filepath}': #{err.stack ? err}"
try
readObject =
if CSON.isObjectPath(filepath)
CSON.readObjectAsync.bind(CSON)
else
fsUtils.readPlistAsync.bind(fsUtils)
readObject filepath, (err, object) =>
try
if err
logError(err)
else
@add(@translateTextmateSnippet(object))
catch err
logError(err)
finally
done()
catch err
logError(err)
done()
fs.readdir snippetsDirPath, (err, paths) ->
if err
console.warn err
return done()
async.eachSeries(paths, loadSnippetFile, done)
translateTextmateSnippet: ({ scope, name, content, tabTrigger }) ->
scope = syntax.cssSelectorFromScopeSelector(scope) if scope
scope ?= '*'
snippetsByScope = {}
snippetsByName = {}
snippetsByScope[scope] = snippetsByName
snippetsByName[name] = { prefix: tabTrigger, body: content }
snippetsByScope
add: (snippetsBySelector) ->
for selector, snippetsByName of snippetsBySelector

View File

@@ -1,25 +1,22 @@
Snippet = require 'snippets/lib/snippet'
LoadSnippetsTask = require 'snippets/lib/load-snippets-task'
RootView = require 'root-view'
Buffer = require 'buffer'
Buffer = require 'text-buffer'
Editor = require 'editor'
_ = require 'underscore'
fs = require 'fs'
fs = require 'fs-utils'
Package = require 'package'
describe "Snippets extension", ->
[buffer, editor, editSession] = []
beforeEach ->
atom.activatePackage('javascript.tmbundle', sync: true)
window.rootView = new RootView
rootView.open('sample.js')
spyOn(LoadSnippetsTask.prototype, 'start')
packageWithSnippets = window.loadPackage("package-with-snippets")
packageWithSnippets = atom.loadPackage("package-with-snippets")
spyOn(atom, "getLoadedPackages").andCallFake ->
window.textMatePackages.concat([packageWithSnippets])
window.loadPackage("snippets")
spyOn(require("snippets/lib/snippets"), 'loadAll')
atom.activatePackage("snippets")
editor = rootView.getActiveView()
editSession = rootView.getActivePaneItem()
@@ -239,12 +236,16 @@ describe "Snippets extension", ->
describe "snippet loading", ->
beforeEach ->
jasmine.unspy(LoadSnippetsTask.prototype, 'start')
spyOn(LoadSnippetsTask.prototype, 'loadAtomSnippets').andCallFake -> @snippetsLoaded({})
spyOn(LoadSnippetsTask.prototype, 'loadTextMateSnippets').andCallFake -> @snippetsLoaded({})
atom.loadPackage('package-with-broken-snippets.tmbundle', sync: true)
atom.loadPackage('package-with-snippets')
jasmine.unspy(window, "setTimeout")
jasmine.unspy(snippets, 'loadAll')
spyOn(snippets, 'loadAtomSnippets').andCallFake (path, done) -> done()
spyOn(snippets, 'loadTextMateSnippets').andCallFake (path, done) -> done()
it "loads non-hidden snippet files from all atom packages with snippets directories, logging a warning if a file can't be parsed", ->
jasmine.unspy(LoadSnippetsTask.prototype, 'loadAtomSnippets')
jasmine.unspy(snippets, 'loadAtomSnippets')
spyOn(console, 'warn')
snippets.loaded = false
snippets.loadAll()
@@ -259,7 +260,7 @@ describe "Snippets extension", ->
expect(console.warn.calls.length).toBe 1
it "loads snippets from all TextMate packages with snippets", ->
jasmine.unspy(LoadSnippetsTask.prototype, 'loadTextMateSnippets')
jasmine.unspy(snippets, 'loadTextMateSnippets')
spyOn(console, 'warn')
snippets.loaded = false
snippets.loadAll()
@@ -281,37 +282,6 @@ describe "Snippets extension", ->
expect(console.warn).toHaveBeenCalled()
expect(console.warn.calls.length).toBe 1
it "terminates the worker when loading completes", ->
jasmine.unspy(LoadSnippetsTask.prototype, 'loadAtomSnippets')
spyOn(console, "warn")
spyOn(Worker.prototype, 'terminate').andCallThrough()
snippets.loaded = false
snippets.loadAll()
waitsFor "all snippets to load", 5000, -> snippets.loaded
runs ->
expect(console.warn).toHaveBeenCalled()
expect(console.warn.argsForCall[0]).toMatch /Error reading snippets file '.*?\/spec\/fixtures\/packages\/package-with-snippets\/snippets\/junk-file'/
expect(Worker.prototype.terminate).toHaveBeenCalled()
expect(Worker.prototype.terminate.calls.length).toBe 1
it "loads CSON snippets from TextMate packages", ->
jasmine.unspy(LoadSnippetsTask.prototype, 'loadTextMateSnippets')
snippets.loaded = false
task = new LoadSnippetsTask(snippets)
task.packages = [Package.build(project.resolve('packages/package-with-a-cson-grammar.tmbundle'))]
task.start()
waitsFor "CSON snippets to load", 5000, -> snippets.loaded
runs ->
snippet = syntax.getProperty(['.source.alot'], 'snippets.really')
expect(snippet).toBeTruthy()
expect(snippet.prefix).toBe 'really'
expect(snippet.name).toBe 'Really'
expect(snippet.body).toBe "I really like alot"
describe "snippet body parser", ->
it "breaks a snippet body into lines, with each line containing tab stops at the appropriate position", ->
bodyTree = snippets.getBodyParser().parse """

View File

@@ -1,6 +1,7 @@
{View} = require 'space-pen'
Range = require 'range'
CorrectionsView = require './corrections-view'
SpellChecker = require 'spellchecker'
module.exports =
class MisspellingView extends View
@@ -30,7 +31,7 @@ class MisspellingView extends View
screenRange = @getScreenRange()
misspelling = @editor.getTextInRange(@editor.bufferRangeForScreenRange(screenRange))
corrections = $native.getCorrectionsForMisspelling(misspelling)
corrections = SpellChecker.getCorrectionsForMisspelling(misspelling)
@correctionsView?.remove()
@correctionsView = new CorrectionsView(@editor, corrections, screenRange)

View File

@@ -1,3 +1,5 @@
SpellChecker = require 'spellchecker'
module.exports =
findMisspellings: (text) ->
wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]]|$)/g
@@ -6,7 +8,7 @@ module.exports =
for line in text.split('\n')
while matches = wordRegex.exec(line)
word = matches[1]
continue unless $native.isMisspelled(word)
continue unless SpellChecker.isMisspelled(word)
startColumn = matches.index + matches[0].length - word.length
endColumn = startColumn + word.length
misspellings.push([[row, startColumn], [row, endColumn]])

View File

@@ -9,10 +9,10 @@ module.exports =
]
activate: ->
if syntax.grammars.length > 1
@subscribeToEditors()
else
syntax.on 'grammars-loaded', => @subscribeToEditors()
syntax.on 'grammars-loaded.spell-check', => @subscribeToEditors()
deactivate: ->
syntax.off '.spell-check'
subscribeToEditors: ->
rootView.eachEditor (editor) ->

View File

@@ -4,10 +4,13 @@ describe "Spell check", ->
[editor] = []
beforeEach ->
atom.activatePackage('text.tmbundle', sync: true)
atom.activatePackage('javascript.tmbundle', sync: true)
window.rootView = new RootView
rootView.open('sample.js')
config.set('spell-check.grammars', [])
window.loadPackage('spell-check', activateImmediately: true)
atom.activatePackage('spell-check', immediate: true)
syntax.trigger 'grammars-loaded'
rootView.attachToDom()
editor = rootView.getActiveView()

View File

@@ -9,7 +9,7 @@ class StatusBarView extends View
@appendToEditorPane(rootView, editor) if editor.attached
@appendToEditorPane: (rootView, editor) ->
if pane = editor.pane()
if pane = editor.getPane()
pane.append(new StatusBarView(rootView, editor))
@content: ->
@@ -34,7 +34,7 @@ class StatusBarView extends View
@updateCursorPositionText()
@subscribe @editor, 'cursor:moved', => @updateCursorPositionText()
@subscribe @grammarName, 'click', => @editor.trigger 'editor:select-grammar'
@subscribe @grammarName, 'click', => @editor.trigger 'grammar-selector:show'
@subscribe @editor, 'editor:grammar-changed', => @updateGrammarText()
if git?
@subscribe git, 'status-changed', (path, status) =>
@@ -58,7 +58,11 @@ class StatusBarView extends View
@updateStatusText()
updateGrammarText: ->
@grammarName.text(@editor.getGrammar().name)
grammar = @editor.getGrammar()
if grammar is syntax.nullGrammar
@grammarName.text('').hide()
else
@grammarName.text(grammar.name).show()
updateBufferHasModifiedText: (isModified)->
if isModified
@@ -110,6 +114,9 @@ class StatusBarView extends View
else if git.isStatusNew(status)
@gitStatusIcon.addClass('new-status-icon')
@gitStatusIcon.text("+#{@buffer.getLineCount()}")
else if git.isPathIgnored(path)
@gitStatusIcon.addClass('ignored-status-icon')
@gitStatusIcon.text('')
updatePathText: ->
if path = @editor.getPath()

View File

@@ -2,7 +2,7 @@ $ = require 'jquery'
_ = require 'underscore'
RootView = require 'root-view'
StatusBar = require 'status-bar/lib/status-bar-view'
fs = require 'fs'
fs = require 'fs-utils'
describe "StatusBar", ->
[editor, statusBar, buffer] = []
@@ -32,7 +32,7 @@ describe "StatusBar", ->
describe "when associated with an unsaved buffer", ->
it "displays 'untitled' instead of the buffer's path, but still displays the buffer position", ->
rootView.deactivate()
rootView.remove()
window.rootView = new RootView
rootView.open()
rootView.simulateDomAttachment()
@@ -112,7 +112,7 @@ describe "StatusBar", ->
it "displays the current branch for files in repositories", ->
path = require.resolve('fixtures/git/master.git/HEAD')
project.setPath(require.resolve('fixtures/git/master.git'))
project.setPath(fs.resolveOnLoadPath('fixtures/git/master.git'))
rootView.open(path)
expect(statusBar.branchArea).toBeVisible()
expect(statusBar.branchLabel.text()).toBe 'master'
@@ -124,12 +124,14 @@ describe "StatusBar", ->
expect(statusBar.branchLabel.text()).toBe ''
describe "git status label", ->
[repo, path, originalPathText, newPath] = []
[repo, path, originalPathText, newPath, ignoredPath] = []
beforeEach ->
path = require.resolve('fixtures/git/working-dir/file.txt')
newPath = fs.join(require.resolve('fixtures/git/working-dir'), 'new.txt')
newPath = fs.join(fs.resolveOnLoadPath('fixtures/git/working-dir'), 'new.txt')
fs.write(newPath, "I'm new here")
ignoredPath = fs.join(fs.resolveOnLoadPath('fixtures/git/working-dir'), 'ignored.txt')
fs.write(ignoredPath, 'ignored.txt')
git.getPathStatus(path)
git.getPathStatus(newPath)
originalPathText = fs.read(path)
@@ -138,6 +140,7 @@ describe "StatusBar", ->
afterEach ->
fs.write(path, originalPathText)
fs.remove(newPath) if fs.exists(newPath)
fs.remove(ignoredPath) if fs.exists(ignoredPath)
it "displays the modified icon for a changed file", ->
fs.write(path, "i've changed for the worse")
@@ -153,6 +156,10 @@ describe "StatusBar", ->
rootView.open(newPath)
expect(statusBar.gitStatusIcon).toHaveClass('new-status-icon')
it "displays the ignored icon for an ignored file", ->
rootView.open(ignoredPath)
expect(statusBar.gitStatusIcon).toHaveClass('ignored-status-icon')
it "updates when a status-changed event occurs", ->
fs.write(path, "i've changed for the worse")
git.getPathStatus(path)
@@ -173,19 +180,33 @@ describe "StatusBar", ->
expect(statusBar.gitStatusIcon).toHaveText('+1')
describe "grammar label", ->
beforeEach ->
atom.activatePackage('text.tmbundle', sync: true)
atom.activatePackage('javascript.tmbundle', sync: true)
syntax.trigger 'grammars-loaded'
it "displays the name of the current grammar", ->
expect(statusBar.find('.grammar-name').text()).toBe 'JavaScript'
it "hides the label when the current grammar is the null grammar", ->
rootView.attachToDom()
editor.activeEditSession.languageMode.grammar = syntax.nullGrammar
editor.activeEditSession.trigger 'grammar-changed'
expect(statusBar.find('.grammar-name')).toBeHidden()
expect(statusBar.find('.grammar-name').text()).toBe ''
editor.reloadGrammar()
expect(statusBar.find('.grammar-name')).toBeVisible()
expect(statusBar.find('.grammar-name').text()).toBe 'JavaScript'
describe "when the editor's grammar changes", ->
it "displays the new grammar of the editor", ->
textGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'Plain Text'
project.addGrammarOverrideForPath(editor.getPath(), textGrammar)
syntax.setGrammarOverrideForPath(editor.getPath(), 'text.plain')
editor.reloadGrammar()
expect(statusBar.find('.grammar-name').text()).toBe textGrammar.name
expect(statusBar.find('.grammar-name').text()).toBe 'Plain Text'
describe "when clicked", ->
it "toggles the editor:select-grammar event", ->
it "toggles the grammar-selector:show event", ->
eventHandler = jasmine.createSpy('eventHandler')
editor.on 'editor:select-grammar', eventHandler
editor.on 'grammar-selector:show', eventHandler
statusBar.find('.grammar-name').click()
expect(eventHandler).toHaveBeenCalled()

View File

@@ -0,0 +1,77 @@
.status-bar {
padding: 5px 10px;
font-size: 11px;
line-height: 14px;
position: relative;
-webkit-user-select: none;
cursor: default;
overflow: hidden;
}
.status-bar .git-branch {
float: right;
}
.status-bar .cursor-position,
.status-bar .grammar-name {
margin-left: 10px;
}
.status-bar .grammar-name {
cursor: pointer;
border: 1px solid transparent;
padding: 1px 2px;
}
.status-bar .branch-label {
vertical-align: baseline;
}
.status-bar .git-status.octicons {
display: none;
padding-left: 10px;
margin-top:-2px;
}
.status-bar .octicons:before {
font-family: 'Octicons Regular';
font-size: 14px;
width: 14px;
height: 14px;
line-height: 14px;
-webkit-font-smoothing: antialiased;
display: inline-block;
vertical-align: middle;
margin-right: 5px;
}
.status-bar .branch-icon:before {
content: "\f020";
}
.status-bar .modified-status-icon:before {
content: "\f26d";
}
.status-bar .new-status-icon:before {
content: "\f26b";
}
.status-bar .ignored-status-icon:before {
content: "\f099";
margin-right: 0px;
}
.status-bar .commits-behind-label:before {
margin-top: -3px;
margin-left: 3px;
margin-right: 1px;
content: "\f03f";
}
.status-bar .commits-ahead-label:before {
margin-top: -3px;
margin-left: 3px;
margin-right: 1px;
content: "\f03d";
}

View File

@@ -1,5 +1,5 @@
RootView = require 'root-view'
fs = require 'fs'
fs = require 'fs-utils'
describe "StripTrailingWhitespace", ->
[editor, path] = []
@@ -10,7 +10,7 @@ describe "StripTrailingWhitespace", ->
window.rootView = new RootView
rootView.open(path)
window.loadPackage('strip-trailing-whitespace')
atom.activatePackage('strip-trailing-whitespace')
rootView.focus()
editor = rootView.getActiveView()

View File

@@ -0,0 +1,17 @@
ctags = require 'ctags'
fs = require 'fs-utils'
module.exports =
getTagsFile: (path) ->
tagsFile = fs.join(path, "tags")
return tagsFile if fs.isFile(tagsFile)
tagsFile = fs.join(path, "TAGS")
return tagsFile if fs.isFile(tagsFile)
loadTags: (path) ->
tagsFile = @getTagsFile(path)
if tagsFile
callTaskMethod("tagsLoaded", ctags.getTags(tagsFile))
else
callTaskMethod("tagsLoaded", [])

View File

@@ -0,0 +1,13 @@
Task = require 'task'
module.exports =
class LoadTagsTask extends Task
constructor: (@callback) ->
super('symbols-view/lib/load-tags-handler')
started: ->
@callWorkerMethod('loadTags', project.getPath())
tagsLoaded: (tags) ->
@done()
@callback(tags)

View File

@@ -3,7 +3,7 @@ SelectList = require 'select-list'
TagGenerator = require './tag-generator'
TagReader = require './tag-reader'
Point = require 'point'
fs = require 'fs'
fs = require 'fs-utils'
$ = require 'jquery'
module.exports =
@@ -41,12 +41,10 @@ class SymbolsView extends SelectList
@attach()
populateFileSymbols: ->
tags = []
callback = (tag) -> tags.push tag
path = rootView.getActiveView().getPath()
@list.empty()
@setLoading("Generating symbols...")
new TagGenerator(path, callback).generate().done =>
new TagGenerator(path).generate().done (tags) =>
if tags.length > 0
@miniEditor.show()
@maxItem = Infinity

View File

@@ -1,10 +1,11 @@
Point = require 'point'
ChildProcess = require 'child-process'
$ = require 'jquery'
BufferedProcess = require 'buffered-process'
fs = require 'fs-utils'
module.exports =
class TagGenerator
constructor: (@path, @callback) ->
constructor: (@path) ->
parseTagLine: (line) ->
sections = line.split('\t')
@@ -15,12 +16,15 @@ class TagGenerator
null
generate: ->
options =
bufferLines: true
stdout: (data) =>
lines = data.split('\n')
for line in lines
tag = @parseTagLine(line)
@callback(tag) if tag
command = "#{require.resolve('ctags')} --fields=+KS -nf - #{@path}"
ChildProcess.exec(command, options)
deferred = $.Deferred()
tags = []
command = fs.resolveOnLoadPath('ctags')
args = ['--fields=+KS', '-nf', '-', @path]
stdout = (lines) =>
for line in lines.split('\n')
tag = @parseTagLine(line)
tags.push(tag) if tag
exit = ->
deferred.resolve(tags)
new BufferedProcess({command, args, stdout, exit})
deferred

View File

@@ -1,5 +1,7 @@
fs = require 'fs'
fs = require 'fs-utils'
$ = require 'jquery'
LoadTagsTask = require './load-tags-task'
ctags = require 'ctags'
module.exports =
@@ -14,14 +16,11 @@ find: (editor) ->
tagsFile = @getTagsFile(project)
return [] unless tagsFile
$tags.find(tagsFile, word) or []
ctags.findTags(tagsFile, word)
getAllTags: (project, callback) ->
deferred = $.Deferred()
tagsFile = @getTagsFile(project)
if tagsFile
$tags.getAllTagsAsync tagsFile, (tags) =>
deferred.resolve(tags)
else
deferred.resolve([])
callback = (tags=[]) =>
deferred.resolve(tags)
new LoadTagsTask(callback).start()
deferred.promise()

View File

@@ -1,14 +1,14 @@
RootView = require 'root-view'
SymbolsView = require 'symbols-view/lib/symbols-view'
TagGenerator = require 'symbols-view/lib/tag-generator'
fs = require 'fs'
fs = require 'fs-utils'
describe "SymbolsView", ->
[symbolsView, setArraySpy] = []
beforeEach ->
window.rootView = new RootView
window.loadPackage("symbols-view")
atom.activatePackage("symbols-view")
rootView.attachToDom()
setArraySpy = spyOn(SymbolsView.prototype, 'setArray').andCallThrough()
@@ -83,13 +83,12 @@ describe "SymbolsView", ->
it "moves the cursor to the selected function", ->
tags = []
waitsForPromise ->
tags = []
path = require.resolve('fixtures/sample.js')
callback = (tag) ->
tags.push tag
generator = new TagGenerator(path, callback)
generator.generate()
generator = new TagGenerator(path)
generator.generate().done (generatedTags) ->
tags = generatedTags
runs ->
rootView.open('sample.js')
@@ -108,10 +107,9 @@ describe "SymbolsView", ->
waitsForPromise ->
path = require.resolve('fixtures/sample.js')
callback = (tag) ->
tags.push tag
generator = new TagGenerator(path, callback)
generator.generate()
generator = new TagGenerator(path)
generator.generate().done (generatedTags) ->
tags = generatedTags
runs ->
expect(tags.length).toBe 2
@@ -125,10 +123,9 @@ describe "SymbolsView", ->
waitsForPromise ->
path = require.resolve('fixtures/sample.txt')
callback = (tag) ->
tags.push tag
generator = new TagGenerator(path, callback)
generator.generate()
generator = new TagGenerator(path)
generator.generate().done (generatedTags) ->
tags = generatedTags
runs ->
expect(tags.length).toBe 0

View File

@@ -47,9 +47,11 @@ class TabBarView extends SortableList
tab.insertBefore(followingTab)
else
@append(tab)
tab.updateTitle()
removeTabForItem: (item) ->
@tabForItem(item).remove()
tab.updateTitle() for tab in @getTabs()
getTabs: ->
@children('.tab').toArray().map (elt) -> $(elt).view()

View File

@@ -1,6 +1,6 @@
$ = require 'jquery'
{View} = require 'space-pen'
fs = require 'fs'
fs = require 'fs-utils'
module.exports =
class TabView extends View

View File

@@ -4,14 +4,14 @@ RootView = require 'root-view'
Pane = require 'pane'
PaneContainer = require 'pane-container'
TabBarView = require 'tabs/lib/tab-bar-view'
fs = require 'fs'
fs = require 'fs-utils'
{View} = require 'space-pen'
describe "Tabs package main", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
window.loadPackage("tabs")
atom.activatePackage("tabs")
describe ".activate()", ->
it "appends a tab bar all existing and new panes", ->
@@ -88,6 +88,17 @@ describe "TabBarView", ->
expect(tabBar.getTabs().length).toBe 2
expect(tabBar.find('.tab:contains(Item 2)')).not.toExist()
it "updates the titles of the remaining tabs", ->
expect(tabBar.tabForItem(item2)).toHaveText 'Item 2'
item2.longTitle = '2'
item2a = new TestView('Item 2')
item2a.longTitle = '2a'
pane.showItem(item2a)
expect(tabBar.tabForItem(item2)).toHaveText '2'
expect(tabBar.tabForItem(item2a)).toHaveText '2a'
pane.removeItem(item2a)
expect(tabBar.tabForItem(item2)).toHaveText 'Item 2'
describe "when a tab is clicked", ->
it "shows the associated item on the pane and focuses the pane", ->
spyOn(pane, 'focus')

View File

@@ -0,0 +1,110 @@
.tabs {
font: caption;
display: -webkit-flex;
-webkit-box-align: center;
}
.tab {
-webkit-user-select: none;
-webkit-user-drag: element;
cursor: default;
-webkit-flex: 1;
width: 175px;
max-width: 175px;
min-width: 40px;
box-sizing: border-box;
text-shadow: -1px -1px 0 #000;
font-size: 11px;
padding: 5px 10px;
position: relative;
}
.tab.active {
-webkit-flex: 2;
}
.tab .title {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 3px 10px 3px 0;
}
.tab .close-icon {
font-family: 'Octicons Regular';
font-size: 12px;
width: 12px;
height: 12px;
cursor: pointer;
position: absolute;
color: rgba(255, 255, 255, 0.5);
right: 8px;
top: 5px;
-webkit-font-smoothing: antialiased;
}
.tab .close-icon:before {
content: "\f081";
}
.tab .close-icon:hover {
color: #fff;
}
.tab.modified .close-icon {
top: 11px;
width: 5px;
height: 5px;
right: 9px;
border: 2px solid #66a6ff;
border-radius: 12px;
}
.tab.modified .close-icon:before {
content: "";
}
.tab.modified:hover .close-icon {
border: none;
width: 12px;
height: 12px;
right: 8px;
top: 5px;
}
.tab.modified:hover .close-icon:before {
content: "\f081";
color: #66a6ff;
}
/* Drag and Drop */
.tab.is-dragging {
}
.tab.is-drop-target:after {
position: absolute;
top: 0;
right: -2px;
content: "";
z-index: 999;
display: inline-block;
width: 2px;
height: 30px;
display: inline-block;
background: #0098ff;
}
.tab.is-drop-target:before {
content: "";
position: absolute;
width: 4px;
height: 4px;
background: #0098ff;
right: -4px;
top: 30px;
border-radius: 4px;
z-index: 9999;
border: 1px solid transparent;
}

View File

@@ -5,9 +5,9 @@ describe "TOML grammar", ->
beforeEach ->
spyOn(syntax, "addGrammar")
pack = new TextMatePackage(require.resolve("toml.tmbundle"))
pack.load()
grammar = pack.grammars[0]
atom.activatePackage("toml")
expect(syntax.addGrammar).toHaveBeenCalled()
grammar = syntax.addGrammar.argsForCall[0][0]
it "parses the grammar", ->
expect(grammar).toBeTruthy()

View File

@@ -1,6 +1,6 @@
{View} = require 'space-pen'
Editor = require 'editor'
fs = require 'fs'
fs = require 'fs-utils'
$ = require 'jquery'
module.exports =

View File

@@ -1,7 +1,7 @@
{View} = require 'space-pen'
$ = require 'jquery'
Git = require 'git'
fs = require 'fs'
fs = require 'fs-utils'
module.exports =
class FileView extends View
@@ -14,19 +14,22 @@ class FileView extends View
file: null
initialize: ({@file, @project} = {}) ->
extension = fs.extension(@getPath())
if fs.isReadmePath(@getPath())
@fileName.addClass('readme-icon')
else if fs.isCompressedExtension(extension)
@fileName.addClass('compressed-icon')
else if fs.isImageExtension(extension)
@fileName.addClass('image-icon')
else if fs.isPdfExtension(extension)
@fileName.addClass('pdf-icon')
else if fs.isBinaryExtension(extension)
@fileName.addClass('binary-icon')
if @file.symlink
@fileName.addClass('symlink-icon')
else
@fileName.addClass('text-icon')
extension = fs.extension(@getPath())
if fs.isReadmePath(@getPath())
@fileName.addClass('readme-icon')
else if fs.isCompressedExtension(extension)
@fileName.addClass('compressed-icon')
else if fs.isImageExtension(extension)
@fileName.addClass('image-icon')
else if fs.isPdfExtension(extension)
@fileName.addClass('pdf-icon')
else if fs.isBinaryExtension(extension)
@fileName.addClass('binary-icon')
else
@fileName.addClass('text-icon')
if git?
@subscribe git, 'status-changed', (path, status) =>

View File

@@ -4,7 +4,7 @@ Directory = require 'directory'
DirectoryView = require './directory-view'
FileView = require './file-view'
Dialog = require './dialog'
fs = require 'fs'
fs = require 'fs-utils'
$ = require 'jquery'
_ = require 'underscore'
@@ -40,8 +40,8 @@ class TreeView extends ScrollView
else
@selectActiveFile()
rootView.on 'pane:active-item-changed pane:became-active', => @selectActiveFile()
project.on 'path-changed', => @updateRoot()
rootView.on 'pane:active-item-changed.tree-view pane:became-active.tree-view', => @selectActiveFile()
project.on 'path-changed.tree-view', => @updateRoot()
@observeConfig 'core.hideGitIgnoredFiles', => @updateRoot()
if @root
@@ -67,6 +67,9 @@ class TreeView extends ScrollView
deactivate: ->
@root?.unwatchEntries()
rootView.off('.tree-view')
project.off('.tree-view')
@remove()
toggle: ->
if @hasFocus()
@@ -222,6 +225,14 @@ class TreeView extends ScrollView
iconClass: 'move'
onConfirm: (newPath) =>
newPath = project.resolve(newPath)
if oldPath is newPath
dialog.close()
return
if fs.exists(newPath)
dialog.showError("Error: #{newPath} already exists. Try a different path.")
return
directoryPath = fs.directory(newPath)
try
fs.makeTree(directoryPath) unless fs.exists(directoryPath)

View File

@@ -4,7 +4,7 @@ _ = require 'underscore'
TreeView = require 'tree-view/lib/tree-view'
RootView = require 'root-view'
Directory = require 'directory'
fs = require 'fs'
fs = require 'fs-utils'
describe "TreeView", ->
[treeView, sampleJs, sampleTxt] = []
@@ -13,7 +13,7 @@ describe "TreeView", ->
project.setPath(project.resolve('tree-view'))
window.rootView = new RootView
window.loadPackage("tree-view")
atom.activatePackage("tree-view")
rootView.trigger 'tree-view:toggle'
treeView = rootView.find(".tree-view").view()
treeView.root = treeView.find('ol > li:first').view()
@@ -47,10 +47,8 @@ describe "TreeView", ->
describe "when the project has no path", ->
beforeEach ->
project.setPath(undefined)
rootView.deactivate()
window.rootView = new RootView()
rootView.open()
treeView = window.loadPackage("tree-view").mainModule.createView()
atom.deactivatePackage("tree-view")
treeView = atom.activatePackage("tree-view").mainModule.createView()
it "does not attach to the root view or create a root node when initialized", ->
expect(treeView.hasParent()).toBeFalsy()
@@ -66,23 +64,24 @@ describe "TreeView", ->
describe "when the project is assigned a path because a new buffer is saved", ->
it "creates a root directory view but does not attach to the root view", ->
rootView.open()
rootView.getActivePaneItem().saveAs("/tmp/test.txt")
expect(treeView.hasParent()).toBeFalsy()
expect(treeView.root.getPath()).toBe require.resolve('/tmp')
expect(treeView.root.getPath()).toBe '/tmp'
expect(treeView.root.parent()).toMatchSelector(".tree-view")
describe "when the root view is opened to a file path", ->
it "does not attach to the root view but does create a root node when initialized", ->
rootView.deactivate()
window.rootView = new RootView
atom.deactivatePackage("tree-view")
atom.packageStates = {}
rootView.open('tree-view.js')
treeView = window.loadPackage("tree-view").mainModule.createView()
treeView = atom.activatePackage("tree-view").mainModule.createView()
expect(treeView.hasParent()).toBeFalsy()
expect(treeView.root).toExist()
describe "when the root view is opened to a directory", ->
it "attaches to the root view", ->
treeView = window.loadPackage("tree-view").mainModule.createView()
treeView = atom.activatePackage("tree-view").mainModule.createView()
expect(treeView.hasParent()).toBeTruthy()
expect(treeView.root).toExist()
@@ -91,10 +90,8 @@ describe "TreeView", ->
treeView.find('.directory:contains(dir1)').click()
sampleJs.click()
rootViewState = rootView.serialize()
rootView.deactivate()
window.rootView = RootView.deserialize(rootViewState)
window.loadPackage("tree-view")
atom.deactivatePackage("tree-view")
atom.activatePackage("tree-view")
treeView = rootView.find(".tree-view").view()
expect(treeView).toExist()
@@ -105,13 +102,8 @@ describe "TreeView", ->
rootView.attachToDom()
treeView.focus()
expect(treeView.find(".tree-view")).toMatchSelector ':focus'
rootViewState = rootView.serialize()
rootView.deactivate()
window.rootView = RootView.deserialize(rootViewState)
rootView.attachToDom()
window.loadPackage("tree-view")
atom.deactivatePackage("tree-view")
atom.activatePackage("tree-view")
treeView = rootView.find(".tree-view").view()
expect(treeView.find(".tree-view")).toMatchSelector ':focus'
@@ -267,20 +259,20 @@ describe "TreeView", ->
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveView().getPath()).toBe fs.resolveOnLoadPath('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveView().isFocused).toBeFalsy()
sampleTxt.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleTxt).toHaveClass 'selected'
expect(treeView.find('.selected').length).toBe 1
expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.txt')
expect(rootView.getActiveView().getPath()).toBe fs.resolveOnLoadPath('fixtures/tree-view/tree-view.txt')
expect(rootView.getActiveView().isFocused).toBeFalsy()
describe "when a file is double-clicked", ->
it "selects the file and opens it in the active editor on the first click, then changes focus to the active editor on the second", ->
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveView().getPath()).toBe fs.resolveOnLoadPath('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveView().isFocused).toBeFalsy()
sampleJs.trigger clickEvent(originalEvent: { detail: 2 })
@@ -576,7 +568,7 @@ describe "TreeView", ->
it "opens the file in the editor and focuses it", ->
treeView.root.find('.file:contains(tree-view.js)').click()
treeView.root.trigger 'tree-view:open-selected-entry'
expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveView().getPath()).toBe fs.resolveOnLoadPath('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when a directory is selected", ->
@@ -599,9 +591,9 @@ describe "TreeView", ->
[dirView, fileView, rootDirPath, dirPath, filePath] = []
beforeEach ->
rootView.deactivate()
atom.deactivatePackage('tree-view')
rootDirPath = "/tmp/atom-tests"
rootDirPath = fs.join(fs.absolute("/tmp"), "atom-tests")
fs.remove(rootDirPath) if fs.exists(rootDirPath)
dirPath = fs.join(rootDirPath, "test-dir")
@@ -611,8 +603,8 @@ describe "TreeView", ->
fs.write(filePath, "doesn't matter")
project.setPath(rootDirPath)
window.rootView = new RootView(rootDirPath)
window.loadPackage('tree-view')
atom.activatePackage('tree-view')
rootView.trigger 'tree-view:toggle'
treeView = rootView.find(".tree-view").view()
dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
@@ -883,7 +875,7 @@ describe "TreeView", ->
temporaryFilePath = null
beforeEach ->
temporaryFilePath = fs.join(require.resolve('fixtures/tree-view'), 'temporary')
temporaryFilePath = fs.join(fs.resolveOnLoadPath('fixtures/tree-view'), 'temporary')
if fs.exists(temporaryFilePath)
fs.remove(temporaryFilePath)
waits(20)
@@ -915,7 +907,7 @@ describe "TreeView", ->
[ignoreFile] = []
beforeEach ->
ignoreFile = fs.join(require.resolve('fixtures/tree-view'), '.gitignore')
ignoreFile = fs.join(fs.resolveOnLoadPath('fixtures/tree-view'), '.gitignore')
fs.write(ignoreFile, 'tree-view.js')
config.set "core.hideGitIgnoredFiles", false
@@ -938,15 +930,15 @@ describe "TreeView", ->
beforeEach ->
config.set "core.hideGitIgnoredFiles", false
ignoreFile = fs.join(require.resolve('fixtures/tree-view'), '.gitignore')
ignoreFile = fs.join(fs.resolveOnLoadPath('fixtures/tree-view'), '.gitignore')
fs.write(ignoreFile, 'tree-view.js')
git.getPathStatus(ignoreFile)
newFile = fs.join(require.resolve('fixtures/tree-view/dir2'), 'new2')
newFile = fs.join(fs.resolveOnLoadPath('fixtures/tree-view/dir2'), 'new2')
fs.write(newFile, '')
git.getPathStatus(newFile)
modifiedFile = fs.join(require.resolve('fixtures/tree-view/dir1'), 'file1')
modifiedFile = fs.join(fs.resolveOnLoadPath('fixtures/tree-view/dir1'), 'file1')
originalFileContent = fs.read(modifiedFile)
fs.write modifiedFile, 'ch ch changes'
git.getPathStatus(modifiedFile)

View File

@@ -1,3 +1,144 @@
.tree-view .entries {
margin-left: 12px;
}
.tree-view .entries .file .name {
margin-left: 20px;
}
.tree-view .file .name,
.tree-view .directory .header {
padding-top: 4px;
padding-bottom: 4px;
padding-right: 10px;
}
.tree-view .directory .header {
padding-left: 5px;
}
.tree-view-dialog {
padding: 5px;
}
.tree-view-dialog .prompt {
padding-bottom: 3px;
font-size: 12px;
line-height: 16px;
}
.tree-view-dialog .prompt span {
position: relative;
top: -1px;
}
.tree-view-dialog .prompt:before {
font-family: 'Octicons Regular';
font-size: 16px;
width: 16px;
height: 16px;
margin-right: 3px;
-webkit-font-smoothing: antialiased;
}
.tree-view-dialog .prompt.add-file:before {
content: "\f086";
}
.tree-view-dialog .prompt.add-directory:before {
content: "\f095";
}
.tree-view-dialog .prompt.move:before {
content: "\f03e";
}
.tree-view .directory .header .name,
.tree-view .file .name {
position: relative;
padding-left: 21px;
}
.tree-view .directory .header .name:before,
.tree-view .file .name:before {
font-family: 'Octicons Regular';
font-size: 16px;
width: 16px;
height: 16px;
margin-right: 5px;
-webkit-font-smoothing: antialiased;
position: absolute;
left: 0;
}
.tree-view .disclosure-arrow:before {
font-family: 'Octicons Regular';
font-size: 12px;
width: 12px;
height: 12px;
line-height: 16px;
margin-right: 3px;
-webkit-font-smoothing: antialiased;
}
.tree-view .directory .header .directory-icon:before {
content: "\f016";
top: -5px;
}
.tree-view .directory .header .repository-icon:before {
content: "\f001";
top: -4px;
}
.tree-view .directory .header .submodule-icon:before {
content: "\f017";
top: -5px;
}
.tree-view .file .text-icon:before {
content: "\f011";
top: -2px;
}
.tree-view .file .image-icon:before {
content: "\f012";
top: -2px;
}
.tree-view .file .compressed-icon:before {
content: "\f013";
top: -2px;
}
.tree-view .file .pdf-icon:before {
content: "\f014";
top: -2px;
}
.tree-view .file .readme-icon:before {
content: "\f007";
top: -2px;
}
.tree-view .file .binary-icon:before {
content: "\f094";
top: -2px;
}
.tree-view .file .symlink-icon:before {
content: "\f09b";
top: -2px;
}
.tree-view .directory > .header .disclosure-arrow:before {
content: "\f05a";
}
.tree-view .directory.expanded > .header .disclosure-arrow:before {
content: "\f05b";
}
.tree-view-wrapper {
position: relative;
height: 100%;

View File

@@ -6,7 +6,7 @@ describe "WrapGuide", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
window.loadPackage('wrap-guide')
atom.activatePackage('wrap-guide')
rootView.attachToDom()
editor = rootView.getActiveView()
wrapGuide = rootView.find('.wrap-guide').view()