Files
atom/src/editor.coffee
2011-11-07 08:53:07 -08:00

238 lines
5.8 KiB
CoffeeScript

$ = require 'jquery'
_ = require 'underscore'
fs = require 'fs'
ace = require 'ace/ace'
Event = require 'event'
KeyBinder = require 'key-binder'
Native = require 'native'
Storage = require 'storage'
Browser = require 'browser'
Pane = require 'pane'
{EditSession} = require 'ace/edit_session'
{UndoManager} = require 'ace/undomanager'
module.exports =
class Editor
activePath: null
buffers: {}
openPathsKey: "editor.openPaths.#{window.path}"
focusedPathKey: "editor.focusedPath.#{window.path}"
constructor: (path) ->
KeyBinder.register "editor", @
@ace = ace.edit "ace-editor"
# This stuff should all be grabbed from the .atomicity dir
@ace.setTheme require "ace/theme/twilight"
@ace.getSession().setUseSoftTabs true
@ace.getSession().setTabSize 2
@ace.setShowInvisibles(true)
@ace.setPrintMarginColumn 78
Event.on 'window:open', (e) =>
path = e.details
@addBuffer e.details if fs.isFile path
Event.on 'window:close', (e) => @removeBuffer e.details
Event.on 'editor:bufferFocus', (e) => @resize()
@addBuffer path if path
# Resize editor when panes are added/removed
el = document.body
el.addEventListener 'DOMNodeInsertedIntoDocument', => @resize()
el.addEventListener 'DOMNodeRemovedFromDocument', => @resize()
modeMap:
js: 'javascript'
c: 'c_cpp'
cpp: 'c_cpp'
h: 'c_cpp'
m: 'c_cpp'
md: 'markdown'
cs: 'csharp'
rb: 'ruby'
ru: 'ruby'
gemspec: 'ruby'
modeFileMap:
Gemfile: 'ruby'
Rakefile: 'ruby'
modeForPath: (path) ->
return null if not path
if not modeName = @modeFileMap[ _.last path.split '/' ]
language = _.last path.split '.'
language = language.toLowerCase()
modeName = @modeMap[language] or language
try
require("ace/mode/#{modeName}").Mode
catch e
null
restoreOpenBuffers: ->
openPaths = Storage.get @openPathsKey, []
focusedPath = Storage.get(@focusedPathKey)
@addBuffer path for path in openPaths
@focusBuffer focusedPath if focusedPath
addBuffer: (path) ->
throw "#{@constructor.name}: Cannot create buffer from a directory `#{path}`" if fs.isDirectory path
buffer = @buffers[path]
if not buffer
if fs.isFile path
code = if path then fs.read path else ''
buffer = new EditSession code
buffer.setUndoManager new UndoManager
buffer.setUseSoftTabs useSoftTabs = @usesSoftTabs code
buffer.setTabSize if useSoftTabs then @guessTabSize code else 8
mode = @modeForPath path
buffer.setMode new mode if mode
else if /^https?:\/\//.test path
buffer = new Browser path
else
throw "#{@constructor.name}: I don't know what to do with `#{path}`"
@buffers[path] = buffer
openPaths = Storage.get @openPathsKey, []
unless path in openPaths
openPaths.push path
Storage.set @openPathsKey, openPaths
buffer.on 'change', -> buffer.$atom_dirty = true
Event.trigger "editor:bufferAdd", path
@focusBuffer path
removeBuffer: (path) ->
path ?= @activePath
return if not path
buffer = @buffers[path]
return if not buffer
if buffer.$atom_dirty
# This should be thrown into it's own method, but I can't think of a good
# name.
detailedMessage = if @activePath
"#{@activePath} has changes."
else
"An untitled file has changes."
canceled = Native.alert "Do you want to save the changes you made?", detailedMessage,
"Save": =>
path = @save()
not path # if save modal fails/cancels, consider it canceled
"Cancel": => true
"Don't Save": => false
return if canceled
delete @buffers[path]
openPaths = Storage.get @openPathsKey, []
Storage.set @openPathsKey, _.without openPaths, path
Event.trigger "editor:bufferRemove", path
if path is @activePath
newActivePath = Object.keys(@buffers)[0]
if newActivePath
@focusBuffer newActivePath
else
@ace.setSession new EditSession ''
focusBuffer: (path) ->
return if not path or @activePath == path
@activePath = path
buffer = @buffers[path] or @addBuffer path
if buffer.constructor is EditSession
$('#ace-editor').show()
@ace.setSession buffer
else
$('#ace-editor').hide()
buffer.show()
Storage.set @focusedPathKey, path
Event.trigger "editor:bufferFocus", path
save: (path) ->
path ?= @activePath
return @saveAs() if not path
@removeTrailingWhitespace()
fs.write path, @code()
if @buffers[path]
@buffers[path].$atom_dirty = false
path
saveAs: ->
path = Native.savePanel()?.toString()
if path
@save path
@addBuffer path
path
code: ->
@ace.getSession().getValue()
removeTrailingWhitespace: ->
return
@ace.replaceAll "",
needle: "[ \t]+$"
regExp: true
wrap: true
usesSoftTabs: (code) ->
not /^\t/m.test code or @code()
guessTabSize: (code) ->
# * ignores indentation of css/js block comments
match = /^( +)[^*]/im.exec code || @code()
match?[1].length or 2
resize: (timeout=1) ->
setTimeout =>
@ace.focus()
@ace.resize()
, timeout
copy: ->
text = @ace.getSession().doc.getTextRange @ace.getSelectionRange()
Native.writeToPasteboard text
cut: ->
text = @ace.getSession().doc.getTextRange @ace.getSelectionRange()
Native.writeToPasteboard text
@ace.session.remove @ace.getSelectionRange()
eval: -> eval @code()
toggleComment: -> @ace.toggleCommentLines()
outdent: -> @ace.blockOutdent()
indent: -> @ace.indent()
forwardWord: -> @ace.navigateWordRight()
backWord: -> @ace.navigateWordLeft()
deleteWord: -> @ace.removeWordRight()
home: -> @ace.navigateFileStart()
end: -> @ace.navigateFileEnd()
consolelog: ->
@ace.insert 'console.log ""'
@ace.navigateLeft()