mirror of
https://github.com/atom/atom.git
synced 2026-02-12 23:55:10 -05:00
WIP: Use rake to start compiling resources (like require.coffee)
This commit is contained in:
38
src/extensions/tree-view/dialog.coffee
Normal file
38
src/extensions/tree-view/dialog.coffee
Normal file
@@ -0,0 +1,38 @@
|
||||
{View, $$} = require 'space-pen'
|
||||
Editor = require 'editor'
|
||||
fs = require 'fs'
|
||||
$ = require 'jquery'
|
||||
|
||||
module.exports =
|
||||
class Dialog extends View
|
||||
@content: ({prompt} = {}) ->
|
||||
@div class: 'tree-view-dialog', =>
|
||||
@div prompt, outlet: 'prompt'
|
||||
@subview 'miniEditor', new Editor(mini: true)
|
||||
|
||||
initialize: ({path, @onConfirm, select} = {}) ->
|
||||
@miniEditor.focus()
|
||||
@on 'tree-view:confirm', => @confirm()
|
||||
@on 'tree-view:cancel', => @cancel()
|
||||
@miniEditor.on 'focusout', => @remove()
|
||||
|
||||
@miniEditor.setText(path)
|
||||
|
||||
if select
|
||||
extension = fs.extension(path)
|
||||
baseName = fs.base(path)
|
||||
range = [[0, path.length - baseName.length], [0, path.length - extension.length]]
|
||||
@miniEditor.setSelectedBufferRange(range)
|
||||
|
||||
confirm: ->
|
||||
return if @onConfirm(@miniEditor.getText()) is false
|
||||
@remove()
|
||||
$('#root-view').focus()
|
||||
|
||||
cancel: ->
|
||||
@remove()
|
||||
$('.tree-view').focus()
|
||||
|
||||
showError: (message) ->
|
||||
@prompt.text(message)
|
||||
@prompt.flashError()
|
||||
84
src/extensions/tree-view/directory-view.coffee
Normal file
84
src/extensions/tree-view/directory-view.coffee
Normal file
@@ -0,0 +1,84 @@
|
||||
{View, $$} = require 'space-pen'
|
||||
FileView = require 'tree-view/file-view'
|
||||
Directory = require 'directory'
|
||||
$ = require 'jquery'
|
||||
|
||||
module.exports =
|
||||
class DirectoryView extends View
|
||||
@content: ({directory, isExpanded} = {}) ->
|
||||
@li class: 'directory entry', =>
|
||||
@div outlet: 'header', class: 'header', =>
|
||||
@span '▸', class: 'disclosure-arrow', outlet: 'disclosureArrow'
|
||||
@span directory.getBaseName(), class: 'name'
|
||||
|
||||
directory: null
|
||||
entries: null
|
||||
header: null
|
||||
|
||||
initialize: ({@directory, isExpanded} = {}) ->
|
||||
@expand() if isExpanded
|
||||
@disclosureArrow.on 'click', => @toggleExpansion()
|
||||
|
||||
getPath: ->
|
||||
@directory.path
|
||||
|
||||
buildEntries: ->
|
||||
@unwatchDescendantEntries()
|
||||
@entries?.remove()
|
||||
@entries = $$ -> @ol class: 'entries'
|
||||
for entry in @directory.getEntries()
|
||||
if entry instanceof Directory
|
||||
@entries.append(new DirectoryView(directory: entry, isExpanded: false))
|
||||
else
|
||||
@entries.append(new FileView(entry))
|
||||
@append(@entries)
|
||||
|
||||
toggleExpansion: ->
|
||||
if @isExpanded then @collapse() else @expand()
|
||||
|
||||
expand: ->
|
||||
return if @isExpanded
|
||||
@addClass('expanded')
|
||||
@disclosureArrow.text('▾')
|
||||
@buildEntries()
|
||||
@watchEntries()
|
||||
@deserializeEntryExpansionStates(@entryStates) if @entryStates?
|
||||
@isExpanded = true
|
||||
false
|
||||
|
||||
collapse: ->
|
||||
@entryStates = @serializeEntryExpansionStates()
|
||||
@removeClass('expanded')
|
||||
@disclosureArrow.text('▸')
|
||||
@unwatchEntries()
|
||||
@entries.remove()
|
||||
@entries = null
|
||||
@isExpanded = false
|
||||
|
||||
watchEntries: ->
|
||||
@directory.on "contents-change.#{@directory.path}", =>
|
||||
@buildEntries()
|
||||
@trigger "tree-view:directory-modified"
|
||||
|
||||
unwatchEntries: ->
|
||||
@unwatchDescendantEntries()
|
||||
@directory.off ".#{@directory.path}"
|
||||
|
||||
unwatchDescendantEntries: ->
|
||||
@find('.expanded.directory').each ->
|
||||
$(this).view().unwatchEntries()
|
||||
|
||||
serializeEntryExpansionStates: ->
|
||||
entryStates = {}
|
||||
@entries?.find('> .directory.expanded').each ->
|
||||
view = $(this).view()
|
||||
entryStates[view.directory.getBaseName()] = view.serializeEntryExpansionStates()
|
||||
entryStates
|
||||
|
||||
deserializeEntryExpansionStates: (entryStates) ->
|
||||
for directoryName, childEntryStates of entryStates
|
||||
@entries.find("> .directory:contains('#{directoryName}')").each ->
|
||||
view = $(this).view()
|
||||
view.entryStates = childEntryStates
|
||||
view.expand()
|
||||
|
||||
14
src/extensions/tree-view/file-view.coffee
Normal file
14
src/extensions/tree-view/file-view.coffee
Normal file
@@ -0,0 +1,14 @@
|
||||
{View, $$} = require 'space-pen'
|
||||
$ = require 'jquery'
|
||||
|
||||
module.exports =
|
||||
class FileView extends View
|
||||
@content: (file) ->
|
||||
@li file.getBaseName(), class: 'file entry'
|
||||
|
||||
file: null
|
||||
|
||||
initialize: (@file) ->
|
||||
|
||||
getPath: ->
|
||||
@file.path
|
||||
1
src/extensions/tree-view/index.coffee
Normal file
1
src/extensions/tree-view/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require 'tree-view/tree-view'
|
||||
18
src/extensions/tree-view/keymap.coffee
Normal file
18
src/extensions/tree-view/keymap.coffee
Normal file
@@ -0,0 +1,18 @@
|
||||
window.keymap.bindKeys '#root-view'
|
||||
'ctrl-1': 'tree-view:toggle'
|
||||
'ctrl-meta-1': 'tree-view:reveal-active-file'
|
||||
|
||||
window.keymap.bindKeys '.tree-view'
|
||||
'escape': 'tree-view:unfocus'
|
||||
'meta-w': 'tree-view:toggle'
|
||||
'right': 'tree-view:expand-directory'
|
||||
'left': 'tree-view:collapse-directory'
|
||||
'enter': 'tree-view:open-selected-entry'
|
||||
'm': 'tree-view:move'
|
||||
'a': 'tree-view:add'
|
||||
'delete': 'tree-view:remove'
|
||||
'backspace': 'tree-view:remove'
|
||||
|
||||
window.keymap.bindKeys '.tree-view-dialog .mini.editor'
|
||||
'enter': 'tree-view:confirm'
|
||||
'escape': 'tree-view:cancel'
|
||||
265
src/extensions/tree-view/tree-view.coffee
Normal file
265
src/extensions/tree-view/tree-view.coffee
Normal file
@@ -0,0 +1,265 @@
|
||||
{View, $$} = require 'space-pen'
|
||||
Directory = require 'directory'
|
||||
DirectoryView = require 'tree-view/directory-view'
|
||||
FileView = require 'tree-view/file-view'
|
||||
Dialog = require 'tree-view/dialog'
|
||||
Native = require 'native'
|
||||
fs = require 'fs'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class TreeView extends View
|
||||
@activate: (rootView, state) ->
|
||||
requireStylesheet 'tree-view.css'
|
||||
|
||||
if state
|
||||
@instance = TreeView.deserialize(state, rootView)
|
||||
else
|
||||
@instance = new TreeView(rootView)
|
||||
@instance.attach()
|
||||
|
||||
@deactivate: () ->
|
||||
@instance.deactivate()
|
||||
|
||||
@serialize: ->
|
||||
@instance.serialize()
|
||||
|
||||
@content: (rootView) ->
|
||||
@div class: 'tree-view', tabindex: -1, =>
|
||||
if rootView.project.getRootDirectory()
|
||||
@subview 'root', new DirectoryView(directory: rootView.project.getRootDirectory(), isExpanded: true)
|
||||
|
||||
@deserialize: (state, rootView) ->
|
||||
treeView = new TreeView(rootView)
|
||||
treeView.root.deserializeEntryExpansionStates(state.directoryExpansionStates)
|
||||
treeView.selectEntryForPath(state.selectedPath)
|
||||
treeView.focusAfterAttach = state.hasFocus
|
||||
treeView.attach() if state.attached
|
||||
treeView
|
||||
|
||||
root: null
|
||||
focusAfterAttach: false
|
||||
|
||||
initialize: (@rootView) ->
|
||||
@on 'click', '.entry', (e) => @entryClicked(e)
|
||||
@on 'move-up', => @moveUp()
|
||||
@on 'move-down', => @moveDown()
|
||||
@on 'tree-view:expand-directory', => @expandDirectory()
|
||||
@on 'tree-view:collapse-directory', => @collapseDirectory()
|
||||
@on 'tree-view:open-selected-entry', => @openSelectedEntry()
|
||||
@on 'tree-view:move', => @moveSelectedEntry()
|
||||
@on 'tree-view:add', => @add()
|
||||
@on 'tree-view:remove', => @removeSelectedEntry()
|
||||
@on 'tree-view:directory-modified', => @selectActiveFile()
|
||||
@on 'tree-view:unfocus', => @rootView.focus()
|
||||
@rootView.on 'tree-view:toggle', => @toggle()
|
||||
@rootView.on 'tree-view:reveal-active-file', => @revealActiveFile()
|
||||
@rootView.on 'active-editor-path-change', => @selectActiveFile()
|
||||
@rootView.project.on 'path-change', => @updateRoot()
|
||||
|
||||
@selectEntry(@root) if @root
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if @focusAfterAttach
|
||||
|
||||
serialize: ->
|
||||
directoryExpansionStates: @root?.serializeEntryExpansionStates()
|
||||
selectedPath: @selectedEntry()?.getPath()
|
||||
hasFocus: @is(':focus')
|
||||
attached: @hasParent()
|
||||
|
||||
deactivate: ->
|
||||
@root?.unwatchEntries()
|
||||
|
||||
toggle: ->
|
||||
if @is(':focus')
|
||||
@detach()
|
||||
@rootView.focus()
|
||||
else
|
||||
@attach() unless @hasParent()
|
||||
@focus()
|
||||
|
||||
attach: ->
|
||||
@rootView.horizontal.prepend(this)
|
||||
|
||||
entryClicked: (e) ->
|
||||
entry = $(e.currentTarget).view()
|
||||
switch e.originalEvent?.detail ? 1
|
||||
when 1
|
||||
@selectEntry(entry)
|
||||
@openSelectedEntry() if (entry instanceof FileView)
|
||||
when 2
|
||||
if entry.is('.selected.file')
|
||||
@rootView.getActiveEditor().focus()
|
||||
else if entry.is('.selected.directory')
|
||||
entry.toggleExpansion()
|
||||
|
||||
false
|
||||
|
||||
updateRoot: ->
|
||||
@root?.remove()
|
||||
@root = new DirectoryView(directory: @rootView.project.getRootDirectory(), isExpanded: true)
|
||||
@append(@root)
|
||||
|
||||
selectActiveFile: ->
|
||||
activeFilePath = @rootView.getActiveEditor()?.getPath()
|
||||
@selectEntryForPath(activeFilePath)
|
||||
|
||||
revealActiveFile: ->
|
||||
@attach()
|
||||
@focus()
|
||||
|
||||
return unless activeFilePath = @rootView.getActiveEditor()?.getPath()
|
||||
|
||||
project = @rootView.project
|
||||
activePathComponents = project.relativize(activeFilePath).split('/')
|
||||
currentPath = project.getPath().replace(/\/$/, '')
|
||||
for pathComponent in activePathComponents
|
||||
currentPath += '/' + pathComponent
|
||||
entry = @entryForPath(currentPath)
|
||||
if entry.hasClass('directory')
|
||||
entry.expand()
|
||||
else
|
||||
@selectEntry(entry)
|
||||
|
||||
entryForPath: (path) ->
|
||||
fn = (bestMatchEntry, element) ->
|
||||
entry = $(element).view()
|
||||
regex = new RegExp("^" + _.escapeRegExp(entry.getPath()))
|
||||
if regex.test(path) and entry.getPath().length > bestMatchEntry.getPath().length
|
||||
entry
|
||||
else
|
||||
bestMatchEntry
|
||||
|
||||
@find(".entry").toArray().reduce(fn, @root)
|
||||
|
||||
selectEntryForPath: (path) ->
|
||||
@selectEntry(@entryForPath(path))
|
||||
|
||||
moveDown: ->
|
||||
selectedEntry = @selectedEntry()
|
||||
if selectedEntry
|
||||
if selectedEntry.is('.expanded.directory')
|
||||
return if @selectEntry(selectedEntry.find('.entry:first'))
|
||||
until @selectEntry(selectedEntry.next())
|
||||
selectedEntry = selectedEntry.parents('.entry:first')
|
||||
break unless selectedEntry.length
|
||||
else
|
||||
@selectEntry(@root)
|
||||
|
||||
@scrollToEntry(@selectedEntry())
|
||||
|
||||
moveUp: ->
|
||||
selectedEntry = @selectedEntry()
|
||||
if selectedEntry
|
||||
if previousEntry = @selectEntry(selectedEntry.prev())
|
||||
if previousEntry.is('.expanded.directory')
|
||||
@selectEntry(previousEntry.find('.entry:last'))
|
||||
else
|
||||
@selectEntry(selectedEntry.parents('.directory').first())
|
||||
else
|
||||
@selectEntry(@find('.entry').last())
|
||||
|
||||
@scrollToEntry(@selectedEntry())
|
||||
|
||||
expandDirectory: ->
|
||||
selectedEntry = @selectedEntry()
|
||||
selectedEntry.view().expand() if (selectedEntry instanceof DirectoryView)
|
||||
|
||||
collapseDirectory: ->
|
||||
selectedEntry = @selectedEntry()
|
||||
if directory = selectedEntry.closest('.expanded.directory').view()
|
||||
directory.collapse()
|
||||
@selectEntry(directory)
|
||||
|
||||
openSelectedEntry: ->
|
||||
selectedEntry = @selectedEntry()
|
||||
if (selectedEntry instanceof DirectoryView)
|
||||
selectedEntry.view().toggleExpansion()
|
||||
else if (selectedEntry instanceof FileView)
|
||||
@rootView.open(selectedEntry.getPath(), changeFocus: false)
|
||||
|
||||
moveSelectedEntry: ->
|
||||
entry = @selectedEntry()
|
||||
return unless entry
|
||||
oldPath = @selectedEntry().getPath()
|
||||
|
||||
dialog = new Dialog
|
||||
prompt: "Enter the new path for the file:"
|
||||
path: @rootView.project.relativize(oldPath)
|
||||
select: true
|
||||
onConfirm: (newPath) =>
|
||||
newPath = @rootView.project.resolve(newPath)
|
||||
directoryPath = fs.directory(newPath)
|
||||
try
|
||||
fs.makeTree(directoryPath) unless fs.exists(directoryPath)
|
||||
fs.move(oldPath, newPath)
|
||||
catch e
|
||||
dialog.showError("Error: " + e.message + " Try a different path:")
|
||||
return false
|
||||
|
||||
@rootView.append(dialog)
|
||||
|
||||
removeSelectedEntry: ->
|
||||
entry = @selectedEntry()
|
||||
return unless entry
|
||||
|
||||
entryType = if entry instanceof DirectoryView then "directory" else "file"
|
||||
message = "Are you sure you would like to delete the selected #{entryType}?"
|
||||
detailedMessage = "You are deleting #{entry.getPath()}"
|
||||
buttons = [
|
||||
["Move to Trash", => Native.moveToTrash(entry.getPath())]
|
||||
["Cancel", => ] # Do Nothing
|
||||
["Delete", => fs.remove(entry.getPath())]
|
||||
]
|
||||
|
||||
Native.alert message, detailedMessage, buttons
|
||||
|
||||
add: ->
|
||||
selectedPath = @selectedEntry().getPath()
|
||||
directoryPath = if fs.isFile(selectedPath) then fs.directory(selectedPath) else selectedPath
|
||||
relativeDirectoryPath = @rootView.project.relativize(directoryPath)
|
||||
relativeDirectoryPath += '/' if relativeDirectoryPath.length > 0
|
||||
|
||||
dialog = new Dialog
|
||||
prompt: "Enter the path for the new file/directory. Directories end with '/':"
|
||||
path: relativeDirectoryPath
|
||||
select: false
|
||||
onConfirm: (relativePath) =>
|
||||
endsWithDirectorySeperator = /\/$/.test(relativePath)
|
||||
path = @rootView.project.resolve(relativePath)
|
||||
try
|
||||
if fs.exists(path)
|
||||
pathType = if fs.isFile(path) then "file" else "directory"
|
||||
dialog.showError("Error: A #{pathType} already exists at path '#{path}'. Try a different path:")
|
||||
false
|
||||
else if endsWithDirectorySeperator
|
||||
fs.makeTree(path)
|
||||
else
|
||||
fs.write(path, "")
|
||||
@rootView.open(path)
|
||||
catch e
|
||||
dialog.showError("Error: " + e.message + " Try a different path:")
|
||||
return false
|
||||
|
||||
@rootView.append(dialog)
|
||||
|
||||
selectedEntry: ->
|
||||
@find('.selected')?.view()
|
||||
|
||||
selectEntry: (entry) ->
|
||||
return false unless entry.get(0)
|
||||
@find('.selected').removeClass('selected')
|
||||
entry.addClass('selected')
|
||||
|
||||
scrollToEntry: (entry) ->
|
||||
displayElement = if (entry instanceof DirectoryView) then entry.header else entry
|
||||
|
||||
top = @scrollTop() + displayElement.position().top
|
||||
bottom = top + displayElement.outerHeight()
|
||||
|
||||
if bottom > @scrollBottom()
|
||||
@scrollBottom(bottom)
|
||||
if top < @scrollTop()
|
||||
@scrollTop(top)
|
||||
Reference in New Issue
Block a user