diff --git a/spec/atom/cursor-spec.coffee b/spec/atom/cursor-spec.coffee index ea633b833..9bcd01089 100644 --- a/spec/atom/cursor-spec.coffee +++ b/spec/atom/cursor-spec.coffee @@ -9,7 +9,7 @@ describe "Cursor", -> beforeEach -> buffer = new Buffer(require.resolve('fixtures/sample.js')) - editor = Editor.build() + editor = new Editor editor.enableKeymap() editor.setBuffer(buffer) cursor = editor.cursor diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 0fbadd323..552d7734c 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -11,7 +11,7 @@ describe "Editor", -> beforeEach -> buffer = new Buffer(require.resolve('fixtures/sample.js')) - editor = Editor.build() + editor = new Editor editor.enableKeymap() editor.setBuffer(buffer) diff --git a/spec/atom/file-finder-spec.coffee b/spec/atom/file-finder-spec.coffee index 19610c9e0..3dbf14fc1 100644 --- a/spec/atom/file-finder-spec.coffee +++ b/spec/atom/file-finder-spec.coffee @@ -6,7 +6,7 @@ describe 'FileFinder', -> beforeEach -> urls = ['app.coffee', 'buffer.coffee', 'atom/app.coffee', 'atom/buffer.coffee'] - finder = FileFinder.build {urls} + finder = new FileFinder({urls}) describe "initialize", -> it "populates the ol with all urls and selects the first element", -> @@ -63,7 +63,7 @@ describe 'FileFinder', -> selectedCallback = jasmine.createSpy 'selected' beforeEach -> - finder = FileFinder.build {urls, selected: selectedCallback} + finder = new FileFinder({urls, selected: selectedCallback}) it "when a file is selected Editor.open is called", -> spyOn(finder, 'remove') diff --git a/spec/atom/root-view-spec.coffee b/spec/atom/root-view-spec.coffee index c4c3ff032..be3dd0e30 100644 --- a/spec/atom/root-view-spec.coffee +++ b/spec/atom/root-view-spec.coffee @@ -9,7 +9,7 @@ describe "RootView", -> beforeEach -> url = require.resolve 'fixtures/dir/a' - rootView = RootView.build {url} + rootView = new RootView({url}) rootView.enableKeymap() project = rootView.project @@ -22,14 +22,14 @@ describe "RootView", -> describe "when called with a url that references a directory", -> it "creates a project for the directory and opens an empty buffer", -> url = require.resolve 'fixtures/dir/' - rootView = RootView.build {url} + rootView = new RootView({url}) expect(rootView.project.url).toBe url expect(rootView.editor.buffer.url).toBeUndefined() describe "when not called with a url", -> it "opens an empty buffer", -> - rootView = RootView.build() + rootView = new RootView expect(rootView.editor.buffer.url).toBeUndefined() describe ".addPane(view)", -> @@ -70,7 +70,7 @@ describe "RootView", -> describe "when there is no project", -> beforeEach -> - rootView = RootView.build() + rootView = new RootView it "does not open the FileFinder", -> expect(rootView.editor.buffer.url).toBeUndefined() diff --git a/spec/atom/selection-spec.coffee b/spec/atom/selection-spec.coffee index dd6b78387..4ab6478c6 100644 --- a/spec/atom/selection-spec.coffee +++ b/spec/atom/selection-spec.coffee @@ -7,7 +7,7 @@ describe "Selection", -> beforeEach -> buffer = new Buffer(require.resolve('fixtures/sample.js')) - editor = Editor.build() + editor = new Editor editor.enableKeymap() editor.setBuffer(buffer) selection = editor.selection diff --git a/spec/atom/vim-mode-spec.coffee b/spec/atom/vim-mode-spec.coffee index 66359b934..3e0b014d4 100644 --- a/spec/atom/vim-mode-spec.coffee +++ b/spec/atom/vim-mode-spec.coffee @@ -5,7 +5,7 @@ describe "VimMode", -> editor = null beforeEach -> - editor = Editor.build() + editor = new Editor editor.enableKeymap() vimMode = new VimMode(editor) diff --git a/spec/stdlib/template-spec.coffee b/spec/stdlib/template-spec.coffee deleted file mode 100644 index 366894081..000000000 --- a/spec/stdlib/template-spec.coffee +++ /dev/null @@ -1,126 +0,0 @@ -$ = require 'jquery' -Template = require 'template' - -describe "Template", -> - describe "toView", -> - view = null - - beforeEach -> - subviewTemplate = class extends Template - content: (params) -> - @div => - @h2 { outlet: "header" }, params.title - @div "I am a subview" - - template = class extends Template - content: (attrs) -> - @div keydown: 'viewClicked', class: 'rootDiv', => - @h1 { outlet: 'header' }, attrs.title - @list() - @subview 'subview', subviewTemplate.build(title: "Subview") - - list: -> - @ol => - @li outlet: 'li1', click: 'li1Clicked', class: 'foo', "one" - @li outlet: 'li2', keypress:'li2Keypressed', class: 'bar', "two" - - viewProperties: - initialize: (attrs) -> - @initializeCalledWith = attrs - foo: "bar", - li1Clicked: ->, - li2Keypressed: -> - viewClicked: -> - - view = template.build(title: "Zebra") - - describe ".build(attributes)", -> - it "generates markup based on the content method", -> - expect(view).toMatchSelector "div" - expect(view.find("h1:contains(Zebra)")).toExist() - expect(view.find("ol > li.foo:contains(one)")).toExist() - expect(view.find("ol > li.bar:contains(two)")).toExist() - - it "extends the view with viewProperties, calling the 'constructor' property if present", -> - expect(view.constructor).toBeDefined() - expect(view.foo).toBe("bar") - expect(view.initializeCalledWith).toEqual title: "Zebra" - - it "wires references for elements with 'outlet' attributes", -> - expect(view.li1).toMatchSelector "li.foo:contains(one)" - expect(view.li2).toMatchSelector "li.bar:contains(two)" - - it "constructs and wires outlets for subviews", -> - expect(view.subview).toExist() - expect(view.subview.find('h2:contains(Subview)')).toExist() - expect(view.subview.parentView).toBe view - - it "does not overwrite outlets on the superview with outlets from the subviews", -> - expect(view.header).toMatchSelector "h1" - expect(view.subview.header).toMatchSelector "h2" - - it "binds events for elements with event name attributes", -> - spyOn(view, 'viewClicked').andCallFake (event, elt) -> - expect(event.type).toBe 'keydown' - expect(elt).toMatchSelector "div.rootDiv" - - spyOn(view, 'li1Clicked').andCallFake (event, elt) -> - expect(event.type).toBe 'click' - expect(elt).toMatchSelector 'li.foo:contains(one)' - - spyOn(view, 'li2Keypressed').andCallFake (event, elt) -> - expect(event.type).toBe 'keypress' - expect(elt).toMatchSelector "li.bar:contains(two)" - - view.keydown() - expect(view.viewClicked).toHaveBeenCalled() - - view.li1.click() - expect(view.li1Clicked).toHaveBeenCalled() - expect(view.li2Keypressed).not.toHaveBeenCalled() - - view.li1Clicked.reset() - - view.li2.keypress() - expect(view.li2Keypressed).toHaveBeenCalled() - expect(view.li1Clicked).not.toHaveBeenCalled() - - it "makes the original jquery wrapper accessible via the view method from any child element", -> - expect(view.view()).toBe view - expect(view.header.view()).toBe view - expect(view.subview.view()).toBe view.subview - expect(view.subview.header.view()).toBe view.subview - - describe "when a view is inserted within another element with jquery", -> - [attachHandler, subviewAttachHandler] = [] - - beforeEach -> - attachHandler = jasmine.createSpy 'attachHandler' - subviewAttachHandler = jasmine.createSpy 'subviewAttachHandler' - view.on 'attach', attachHandler - view.subview.on 'attach', subviewAttachHandler - - describe "when attached to an element that is on the DOM", -> - it "triggers an 'attach' event on the view and its subviews", -> - content = $('#jasmine-content') - content.append view - expect(attachHandler).toHaveBeenCalled() - expect(subviewAttachHandler).toHaveBeenCalled() - - view.detach() - content.empty() - attachHandler.reset() - subviewAttachHandler.reset() - - otherElt = $('
') - content.append(otherElt) - view.insertBefore(otherElt) - expect(attachHandler).toHaveBeenCalled() - expect(subviewAttachHandler).toHaveBeenCalled() - - describe "when attached to an element that is not on the DOM", -> - it "does not trigger an attach event", -> - fragment = $('
') - fragment.append view - expect(attachHandler).not.toHaveBeenCalled() - diff --git a/spec/stdlib/template/builder-spec.coffee b/spec/stdlib/template/builder-spec.coffee index c5ab6584f..5c4695255 100644 --- a/spec/stdlib/template/builder-spec.coffee +++ b/spec/stdlib/template/builder-spec.coffee @@ -1,5 +1,4 @@ Builder = require 'template/builder' -Template = require 'template' describe "Builder", -> builder = null @@ -74,28 +73,3 @@ describe "Builder", -> builder.raw ' ' expect(builder.toHtml()).toBe ' ' - describe ".subview(name, template, attrs)", -> - template = null - - beforeEach -> - template = class extends Template - content: (params) -> - @div => - @h2 params.title - @div "I am a subview" - - viewProperties: - foo: "bar" - - it "inserts a view built from the given template with the given params", -> - builder.tag 'div', -> - builder.tag 'h1', "Superview" - builder.subview 'sub', template.build(title: "Subview") - - fragment = builder.toFragment() - expect(fragment.find("h1:contains(Superview)")).toExist() - expect(fragment.find("h2:contains(Subview)")).toExist() - subview = fragment.sub - expect(subview).toMatchSelector ':has(h2):contains(I am a subview)' - expect(subview.foo).toBe 'bar' - diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index 108bd2d94..727186c6c 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -1,120 +1,119 @@ -Template = require 'template' +{View} = require 'space-pen' Point = require 'point' _ = require 'underscore' module.exports = -class Cursor extends Template - content: -> +class Cursor extends View + @content: -> @pre class: 'cursor idle', style: 'position: absolute;', => @raw ' ' - viewProperties: - editor: null + editor: null - initialize: (@editor) -> - @one 'attach', => @updateAppearance() + initialize: (@editor) -> + @one 'attach', => @updateAppearance() - bufferChanged: (e) -> - @setPosition(e.postRange.end) + bufferChanged: (e) -> + @setPosition(e.postRange.end) - setPosition: (point) -> - point = Point.fromObject(point) - @point = @editor.clipPosition(point) - @goalColumn = null - @updateAppearance() - @trigger 'cursor:position-changed' + setPosition: (point) -> + point = Point.fromObject(point) + @point = @editor.clipPosition(point) + @goalColumn = null + @updateAppearance() + @trigger 'cursor:position-changed' - @removeClass 'idle' - window.clearTimeout(@idleTimeout) if @idleTimeout - @idleTimeout = window.setTimeout (=> @addClass 'idle'), 200 + @removeClass 'idle' + window.clearTimeout(@idleTimeout) if @idleTimeout + @idleTimeout = window.setTimeout (=> @addClass 'idle'), 200 - getPosition: -> _.clone(@point) + getPosition: -> _.clone(@point) - setColumn: (column) -> - { row } = @getPosition() - @setPosition {row, column} + setColumn: (column) -> + { row } = @getPosition() + @setPosition {row, column} - getColumn: -> - @getPosition().column + getColumn: -> + @getPosition().column - getRow: -> - @getPosition().row + getRow: -> + @getPosition().row - moveUp: -> - { row, column } = @getPosition() - column = @goalColumn if @goalColumn? - if row > 0 - @setPosition({row: row - 1, column: column}) - else - @moveToLineStart() + moveUp: -> + { row, column } = @getPosition() + column = @goalColumn if @goalColumn? + if row > 0 + @setPosition({row: row - 1, column: column}) + else + @moveToLineStart() - @goalColumn = column + @goalColumn = column - moveDown: -> - { row, column } = @getPosition() - column = @goalColumn if @goalColumn? - if row < @editor.buffer.numLines() - 1 - @setPosition({row: row + 1, column: column}) - else - @moveToLineEnd() + moveDown: -> + { row, column } = @getPosition() + column = @goalColumn if @goalColumn? + if row < @editor.buffer.numLines() - 1 + @setPosition({row: row + 1, column: column}) + else + @moveToLineEnd() - @goalColumn = column + @goalColumn = column - moveToLineEnd: -> - { row } = @getPosition() - @setPosition({ row, column: @editor.buffer.getLine(row).length }) + moveToLineEnd: -> + { row } = @getPosition() + @setPosition({ row, column: @editor.buffer.getLine(row).length }) - moveToLineStart: -> - { row } = @getPosition() - @setPosition({ row, column: 0 }) + moveToLineStart: -> + { row } = @getPosition() + @setPosition({ row, column: 0 }) - moveRight: -> - { row, column } = @getPosition() - if column < @editor.buffer.getLine(row).length - column++ - else if row < @editor.buffer.numLines() - 1 - row++ - column = 0 - @setPosition({row, column}) + moveRight: -> + { row, column } = @getPosition() + if column < @editor.buffer.getLine(row).length + column++ + else if row < @editor.buffer.numLines() - 1 + row++ + column = 0 + @setPosition({row, column}) - moveLeft: -> - { row, column } = @getPosition() - if column > 0 - column-- - else if row > 0 - row-- - column = @editor.buffer.getLine(row).length + moveLeft: -> + { row, column } = @getPosition() + if column > 0 + column-- + else if row > 0 + row-- + column = @editor.buffer.getLine(row).length - @setPosition({row, column}) + @setPosition({row, column}) - updateAppearance: -> - position = @editor.pixelPositionFromPoint(@point) - @css(position) - @autoScrollVertically(position) - @autoScrollHorizontally(position) + updateAppearance: -> + position = @editor.pixelPositionFromPoint(@point) + @css(position) + @autoScrollVertically(position) + @autoScrollHorizontally(position) - autoScrollVertically: (position) -> - linesInView = @editor.height() / @height() - maxScrollMargin = Math.floor((linesInView - 1) / 2) - scrollMargin = Math.min(@editor.vScrollMargin, maxScrollMargin) - margin = scrollMargin * @height() - desiredTop = position.top - margin - desiredBottom = position.top + @height() + margin + autoScrollVertically: (position) -> + linesInView = @editor.height() / @height() + maxScrollMargin = Math.floor((linesInView - 1) / 2) + scrollMargin = Math.min(@editor.vScrollMargin, maxScrollMargin) + margin = scrollMargin * @height() + desiredTop = position.top - margin + desiredBottom = position.top + @height() + margin - if desiredBottom > @editor.scrollBottom() - @editor.scrollBottom(desiredBottom) - else if desiredTop < @editor.scrollTop() - @editor.scrollTop(desiredTop) + if desiredBottom > @editor.scrollBottom() + @editor.scrollBottom(desiredBottom) + else if desiredTop < @editor.scrollTop() + @editor.scrollTop(desiredTop) - autoScrollHorizontally: (position) -> - charsInView = @editor.width() / @width() - maxScrollMargin = Math.floor((charsInView - 1) / 2) - scrollMargin = Math.min(@editor.hScrollMargin, maxScrollMargin) - margin = scrollMargin * @width() - desiredRight = position.left + @width() + margin - desiredLeft = position.left - margin + autoScrollHorizontally: (position) -> + charsInView = @editor.width() / @width() + maxScrollMargin = Math.floor((charsInView - 1) / 2) + scrollMargin = Math.min(@editor.hScrollMargin, maxScrollMargin) + margin = scrollMargin * @width() + desiredRight = position.left + @width() + margin + desiredLeft = position.left - margin - if desiredRight > @editor.scrollRight() - @editor.scrollRight(desiredRight) - else if desiredLeft < @editor.scrollLeft() - @editor.scrollLeft(desiredLeft) + if desiredRight > @editor.scrollRight() + @editor.scrollRight(desiredRight) + else if desiredLeft < @editor.scrollLeft() + @editor.scrollLeft(desiredLeft) diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 0384eda2f..0e80de218 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -1,4 +1,4 @@ -Template = require 'template' +{View} = require 'space-pen' Buffer = require 'buffer' Point = require 'point' Cursor = require 'cursor' @@ -11,224 +11,223 @@ $$ = require 'template/builder' _ = require 'underscore' module.exports = -class Editor extends Template - content: -> +class Editor extends View + @content: -> @div class: 'editor', tabindex: -1, => @div outlet: 'lines' @input class: 'hidden-input', outlet: 'hiddenInput' - viewProperties: - vScrollMargin: 2 - hScrollMargin: 10 - cursor: null - buffer: null - selection: null - lineHeight: null - charWidth: null + vScrollMargin: 2 + hScrollMargin: 10 + cursor: null + buffer: null + selection: null + lineHeight: null + charWidth: null - initialize: () -> - requireStylesheet 'editor.css' - requireStylesheet 'theme/twilight.css' - @bindKeys() - @buildCursorAndSelection() - @handleEvents() - @setBuffer(new Buffer) + initialize: () -> + requireStylesheet 'editor.css' + requireStylesheet 'theme/twilight.css' + @bindKeys() + @buildCursorAndSelection() + @handleEvents() + @setBuffer(new Buffer) - bindKeys: -> - atom.bindKeys '*', - right: 'move-right' - left: 'move-left' - down: 'move-down' - up: 'move-up' - 'shift-right': 'select-right' - 'shift-left': 'select-left' - 'shift-up': 'select-up' - 'shift-down': 'select-down' - enter: 'newline' - backspace: 'delete-left' - delete: 'delete-right' - 'meta-x': 'cut' - 'meta-c': 'copy' - 'meta-v': 'paste' + bindKeys: -> + atom.bindKeys '*', + right: 'move-right' + left: 'move-left' + down: 'move-down' + up: 'move-up' + 'shift-right': 'select-right' + 'shift-left': 'select-left' + 'shift-up': 'select-up' + 'shift-down': 'select-down' + enter: 'newline' + backspace: 'delete-left' + delete: 'delete-right' + 'meta-x': 'cut' + 'meta-c': 'copy' + 'meta-v': 'paste' - @on 'move-right', => @moveCursorRight() - @on 'move-left', => @moveCursorLeft() - @on 'move-down', => @moveCursorDown() - @on 'move-up', => @moveCursorUp() - @on 'select-right', => @selectRight() - @on 'select-left', => @selectLeft() - @on 'select-up', => @selectUp() - @on 'select-down', => @selectDown() - @on 'newline', => @insertNewline() - @on 'delete-left', => @deleteLeft() - @on 'delete-right', => @deleteRight() - @on 'cut', => @cutSelection() - @on 'copy', => @copySelection() - @on 'paste', => @paste() + @on 'move-right', => @moveCursorRight() + @on 'move-left', => @moveCursorLeft() + @on 'move-down', => @moveCursorDown() + @on 'move-up', => @moveCursorUp() + @on 'select-right', => @selectRight() + @on 'select-left', => @selectLeft() + @on 'select-up', => @selectUp() + @on 'select-down', => @selectDown() + @on 'newline', => @insertNewline() + @on 'delete-left', => @deleteLeft() + @on 'delete-right', => @deleteRight() + @on 'cut', => @cutSelection() + @on 'copy', => @copySelection() + @on 'paste', => @paste() - buildCursorAndSelection: -> - @cursor = Cursor.build(this) - @append(@cursor) + buildCursorAndSelection: -> + @cursor = new Cursor(this) + @append(@cursor) - @selection = Selection.build(this) - @append(@selection) + @selection = new Selection(this) + @append(@selection) - handleEvents: -> - @on 'focus', => - @hiddenInput.focus() - false + handleEvents: -> + @on 'focus', => + @hiddenInput.focus() + false - @on 'mousedown', (e) => - clickCount = e.originalEvent.detail + @on 'mousedown', (e) => + clickCount = e.originalEvent.detail - if clickCount == 1 - @setCursorPosition @pointFromMouseEvent(e) - @selectTextOnMouseMovement() - else if clickCount == 2 - @selection.selectWord() - @selectTextOnMouseMovement() + if clickCount == 1 + @setCursorPosition @pointFromMouseEvent(e) + @selectTextOnMouseMovement() + else if clickCount == 2 + @selection.selectWord() + @selectTextOnMouseMovement() - @hiddenInput.on "textInput", (e) => - @insertText(e.originalEvent.data) + @hiddenInput.on "textInput", (e) => + @insertText(e.originalEvent.data) - @on 'cursor:position-changed', => - @hiddenInput.css(@pixelPositionFromPoint(@cursor.getPosition())) + @on 'cursor:position-changed', => + @hiddenInput.css(@pixelPositionFromPoint(@cursor.getPosition())) - @one 'attach', => - @calculateDimensions() - @hiddenInput.width(@charWidth) - @focus() + @one 'attach', => + @calculateDimensions() + @hiddenInput.width(@charWidth) + @focus() - selectTextOnMouseMovement: -> - moveHandler = (e) => @selectToPosition(@pointFromMouseEvent(e)) - @on 'mousemove', moveHandler - $(document).one 'mouseup', => @off 'mousemove', moveHandler + selectTextOnMouseMovement: -> + moveHandler = (e) => @selectToPosition(@pointFromMouseEvent(e)) + @on 'mousemove', moveHandler + $(document).one 'mouseup', => @off 'mousemove', moveHandler - buildLineElement: (row) -> - tokens = @highlighter.tokensForRow(row) - $$.pre class: 'line', -> - if tokens.length - for token in tokens - classes = token.type.split('.').map((c) -> "ace_#{c}").join(' ') - @span { class: token.type.replace('.', ' ') }, token.value - else - @raw ' ' - - setBuffer: (@buffer) -> - @highlighter = new Highlighter(@buffer) - - @lines.empty() - for row in [0..@buffer.lastRow()] - line = @buildLineElement(row) - @lines.append line - - @setCursorPosition(row: 0, column: 0) - - @buffer.on 'change', (e) => - @cursor.bufferChanged(e) - - @highlighter.on 'change', (e) => - { preRange, postRange } = e - - if postRange.end.row > preRange.end.row - # update, then insert elements - for row in [preRange.start.row..postRange.end.row] - if row <= preRange.end.row - @updateLineElement(row) - else - @insertLineElement(row) - else - # traverse in reverse... remove, then update elements - for row in [preRange.end.row..preRange.start.row] - if row > postRange.end.row - @removeLineElement(row) - else - @updateLineElement(row) - - updateLineElement: (row) -> - @getLineElement(row).replaceWith(@buildLineElement(row)) - - insertLineElement: (row) -> - @getLineElement(row).before(@buildLineElement(row)) - - removeLineElement: (row) -> - @getLineElement(row).remove() - - getLineElement: (row) -> - @lines.find("pre.line:eq(#{row})") - - clipPosition: ({row, column}) -> - row = Math.min(Math.max(0, row), @buffer.numLines() - 1) - column = Math.min(Math.max(0, column), @buffer.getLine(row).length) - new Point(row, column) - - pixelPositionFromPoint: ({row, column}) -> - { top: row * @lineHeight, left: column * @charWidth } - - pointFromPixelPosition: ({top, left}) -> - { row: Math.floor(top / @lineHeight), column: Math.floor(left / @charWidth) } - - pointFromMouseEvent: (e) -> - { pageX, pageY } = e - @pointFromPixelPosition - top: pageY - @lines.offset().top - left: pageX - @lines.offset().left - - calculateDimensions: -> - fragment = $('
x
') - @lines.append(fragment) - @charWidth = fragment.width() - @lineHeight = fragment.outerHeight() - fragment.remove() - - scrollBottom: (newValue) -> - if newValue? - @scrollTop(newValue - @height()) + buildLineElement: (row) -> + tokens = @highlighter.tokensForRow(row) + $$.pre class: 'line', -> + if tokens.length + for token in tokens + classes = token.type.split('.').map((c) -> "ace_#{c}").join(' ') + @span { class: token.type.replace('.', ' ') }, token.value else - @scrollTop() + @height() + @raw ' ' - scrollRight: (newValue) -> - if newValue? - @scrollLeft(newValue - @width()) + setBuffer: (@buffer) -> + @highlighter = new Highlighter(@buffer) + + @lines.empty() + for row in [0..@buffer.lastRow()] + line = @buildLineElement(row) + @lines.append line + + @setCursorPosition(row: 0, column: 0) + + @buffer.on 'change', (e) => + @cursor.bufferChanged(e) + + @highlighter.on 'change', (e) => + { preRange, postRange } = e + + if postRange.end.row > preRange.end.row + # update, then insert elements + for row in [preRange.start.row..postRange.end.row] + if row <= preRange.end.row + @updateLineElement(row) + else + @insertLineElement(row) else - @scrollLeft() + @width() + # traverse in reverse... remove, then update elements + for row in [preRange.end.row..preRange.start.row] + if row > postRange.end.row + @removeLineElement(row) + else + @updateLineElement(row) - getCursor: -> @cursor - getSelection: -> @selection + updateLineElement: (row) -> + @getLineElement(row).replaceWith(@buildLineElement(row)) - getCurrentLine: -> @buffer.getLine(@getCursorRow()) - getSelectedText: -> @selection.getText() - moveCursorUp: -> @cursor.moveUp() - moveCursorDown: -> @cursor.moveDown() - moveCursorRight: -> @cursor.moveRight() - moveCursorLeft: -> @cursor.moveLeft() - setCursorPosition: (point) -> @cursor.setPosition(point) - getCursorPosition: -> @cursor.getPosition() - setCursorRow: (row) -> @cursor.setRow(row) - getCursorRow: -> @cursor.getRow() - setCursorColumn: (column) -> @cursor.setColumn(column) - getCursorColumn: -> @cursor.getColumn() + insertLineElement: (row) -> + @getLineElement(row).before(@buildLineElement(row)) - selectRight: -> @selection.selectRight() - selectLeft: -> @selection.selectLeft() - selectUp: -> @selection.selectUp() - selectDown: -> @selection.selectDown() - selectToPosition: (position) -> - @selection.selectToPosition(position) + removeLineElement: (row) -> + @getLineElement(row).remove() - insertText: (text) -> @selection.insertText(text) - insertNewline: -> @selection.insertNewline() + getLineElement: (row) -> + @lines.find("pre.line:eq(#{row})") - cutSelection: -> @selection.cut() - copySelection: -> @selection.copy() - paste: -> @selection.insertText(atom.native.readFromPasteboard()) + clipPosition: ({row, column}) -> + row = Math.min(Math.max(0, row), @buffer.numLines() - 1) + column = Math.min(Math.max(0, column), @buffer.getLine(row).length) + new Point(row, column) - deleteLeft: -> - @selectLeft() if @selection.isEmpty() - @selection.delete() + pixelPositionFromPoint: ({row, column}) -> + { top: row * @lineHeight, left: column * @charWidth } - deleteRight: -> - @selectRight() if @selection.isEmpty() - @selection.delete() + pointFromPixelPosition: ({top, left}) -> + { row: Math.floor(top / @lineHeight), column: Math.floor(left / @charWidth) } + + pointFromMouseEvent: (e) -> + { pageX, pageY } = e + @pointFromPixelPosition + top: pageY - @lines.offset().top + left: pageX - @lines.offset().left + + calculateDimensions: -> + fragment = $('
x
') + @lines.append(fragment) + @charWidth = fragment.width() + @lineHeight = fragment.outerHeight() + fragment.remove() + + scrollBottom: (newValue) -> + if newValue? + @scrollTop(newValue - @height()) + else + @scrollTop() + @height() + + scrollRight: (newValue) -> + if newValue? + @scrollLeft(newValue - @width()) + else + @scrollLeft() + @width() + + getCursor: -> @cursor + getSelection: -> @selection + + getCurrentLine: -> @buffer.getLine(@getCursorRow()) + getSelectedText: -> @selection.getText() + moveCursorUp: -> @cursor.moveUp() + moveCursorDown: -> @cursor.moveDown() + moveCursorRight: -> @cursor.moveRight() + moveCursorLeft: -> @cursor.moveLeft() + setCursorPosition: (point) -> @cursor.setPosition(point) + getCursorPosition: -> @cursor.getPosition() + setCursorRow: (row) -> @cursor.setRow(row) + getCursorRow: -> @cursor.getRow() + setCursorColumn: (column) -> @cursor.setColumn(column) + getCursorColumn: -> @cursor.getColumn() + + selectRight: -> @selection.selectRight() + selectLeft: -> @selection.selectLeft() + selectUp: -> @selection.selectUp() + selectDown: -> @selection.selectDown() + selectToPosition: (position) -> + @selection.selectToPosition(position) + + insertText: (text) -> @selection.insertText(text) + insertNewline: -> @selection.insertNewline() + + cutSelection: -> @selection.cut() + copySelection: -> @selection.copy() + paste: -> @selection.insertText(atom.native.readFromPasteboard()) + + deleteLeft: -> + @selectLeft() if @selection.isEmpty() + @selection.delete() + + deleteRight: -> + @selectRight() if @selection.isEmpty() + @selection.delete() diff --git a/src/atom/file-finder.coffee b/src/atom/file-finder.coffee index fbb181d37..15a98c4b1 100644 --- a/src/atom/file-finder.coffee +++ b/src/atom/file-finder.coffee @@ -1,67 +1,66 @@ $ = require 'jquery' -Template = require 'template' +{View} = require 'space-pen' stringScore = require 'stringscore' module.exports = -class FileFinder extends Template - content: -> +class FileFinder extends View + @content: -> @div class: 'file-finder', => @link rel: 'stylesheet', href: "#{require.resolve('file-finder.css')}?#{(new Date).getTime()}" @ol outlet: 'urlList' @input outlet: 'input', input: 'populateUrlList' - viewProperties: - urls: null - maxResults: null + urls: null + maxResults: null - initialize: ({@urls, @selected}) -> - @maxResults = 10 + initialize: ({@urls, @selected}) -> + @maxResults = 10 - @populateUrlList() - atom.bindKeys ".file-finder", - 'up': 'move-up' - 'down': 'move-down' - 'enter': 'select' + @populateUrlList() + atom.bindKeys ".file-finder", + 'up': 'move-up' + 'down': 'move-down' + 'enter': 'select' - @on 'move-up', => @moveUp() - @on 'move-down', => @moveDown() - @on 'select', => @select() + @on 'move-up', => @moveUp() + @on 'move-down', => @moveDown() + @on 'select', => @select() - populateUrlList: -> - @urlList.empty() - for url in @findMatches(@input.val()) - @urlList.append $("
  • #{url}
  • ") + populateUrlList: -> + @urlList.empty() + for url in @findMatches(@input.val()) + @urlList.append $("
  • #{url}
  • ") - @urlList.children('li:first').addClass 'selected' + @urlList.children('li:first').addClass 'selected' - findSelectedLi: -> - @urlList.children('li.selected') + findSelectedLi: -> + @urlList.children('li.selected') - select: -> - filePath = @findSelectedLi().text() - @selected(filePath) if filePath and @selected - @remove() + select: -> + filePath = @findSelectedLi().text() + @selected(filePath) if filePath and @selected + @remove() - moveUp: -> - @findSelectedLi() - .filter(':not(:first-child)') - .removeClass('selected') - .prev() - .addClass('selected') + moveUp: -> + @findSelectedLi() + .filter(':not(:first-child)') + .removeClass('selected') + .prev() + .addClass('selected') - moveDown: -> - @findSelectedLi() - .filter(':not(:last-child)') - .removeClass('selected') - .next() - .addClass('selected') + moveDown: -> + @findSelectedLi() + .filter(':not(:last-child)') + .removeClass('selected') + .next() + .addClass('selected') - findMatches: (query) -> - if not query - urls = @urls - else - scoredUrls = ({url, score: stringScore(url, query)} for url in @urls) - scoredUrls.sort (a, b) -> a.score > b.score - urls = (urlAndScore.url for urlAndScore in scoredUrls when urlAndScore.score > 0) + findMatches: (query) -> + if not query + urls = @urls + else + scoredUrls = ({url, score: stringScore(url, query)} for url in @urls) + scoredUrls.sort (a, b) -> a.score > b.score + urls = (urlAndScore.url for urlAndScore in scoredUrls when urlAndScore.score > 0) - urls.slice 0, @maxResults + urls.slice 0, @maxResults diff --git a/src/atom/root-view.coffee b/src/atom/root-view.coffee index 24855b14d..fb7b90fd7 100644 --- a/src/atom/root-view.coffee +++ b/src/atom/root-view.coffee @@ -2,7 +2,7 @@ $ = require 'jquery' fs = require 'fs' _ = require 'underscore' -Template = require 'template' +{View} = require 'space-pen' Buffer = require 'buffer' Editor = require 'editor' FileFinder = require 'file-finder' @@ -11,58 +11,57 @@ GlobalKeymap = require 'global-keymap' VimMode = require 'vim-mode' module.exports = -class RootView extends Template - content: -> +class RootView extends View + @content: -> @div id: 'app-horizontal', => @div id: 'app-vertical', outlet: 'vertical', => @div id: 'main', outlet: 'main', => - @subview 'editor', Editor.build() + @subview 'editor', new Editor - viewProperties: - globalKeymap: null + globalKeymap: null - initialize: ({url}) -> - @editor.keyEventHandler = atom.globalKeymap - @createProject(url) + initialize: ({url}) -> + @editor.keyEventHandler = atom.globalKeymap + @createProject(url) - atom.bindKeys '*' - 'meta-s': 'save' - 'meta-w': 'close' - 'meta-t': 'toggle-file-finder' - 'alt-meta-i': 'show-console' + atom.bindKeys '*' + 'meta-s': 'save' + 'meta-w': 'close' + 'meta-t': 'toggle-file-finder' + 'alt-meta-i': 'show-console' - @on 'toggle-file-finder', => @toggleFileFinder() - @on 'show-console', -> window.showConsole() + @on 'toggle-file-finder', => @toggleFileFinder() + @on 'show-console', -> window.showConsole() - @on 'focusout', (e) => - # if anything but the editor and its input loses focus, restore focus to the editor - unless $(e.target).closest('.editor').length - @editor.focus() + @on 'focusout', (e) => + # if anything but the editor and its input loses focus, restore focus to the editor + unless $(e.target).closest('.editor').length + @editor.focus() - createProject: (url) -> - if url - @project = new Project(fs.directory(url)) - @editor.setBuffer(@project.open(url)) if fs.isFile(url) + createProject: (url) -> + if url + @project = new Project(fs.directory(url)) + @editor.setBuffer(@project.open(url)) if fs.isFile(url) - bindKeys: (selector, bindings) -> - @globalKeymap.bindKeys(selector, bindings) + bindKeys: (selector, bindings) -> + @globalKeymap.bindKeys(selector, bindings) - addPane: (view) -> - pane = $('
    ') - pane.append(view) - @main.after(pane) + addPane: (view) -> + pane = $('
    ') + pane.append(view) + @main.after(pane) - toggleFileFinder: -> - return unless @project + toggleFileFinder: -> + return unless @project - if @fileFinder and @fileFinder.parent()[0] - @fileFinder.remove() - @fileFinder = null - else - @project.getFilePaths().done (paths) => - relativePaths = (path.replace(@project.url, "") for path in paths) - @fileFinder = FileFinder.build - urls: relativePaths - selected: (relativePath) => @editor.setBuffer(@project.open(relativePath)) - @addPane @fileFinder - @fileFinder.input.focus() + if @fileFinder and @fileFinder.parent()[0] + @fileFinder.remove() + @fileFinder = null + else + @project.getFilePaths().done (paths) => + relativePaths = (path.replace(@project.url, "") for path in paths) + @fileFinder = new FileFinder + urls: relativePaths + selected: (relativePath) => @editor.setBuffer(@project.open(relativePath)) + @addPane @fileFinder + @fileFinder.input.focus() diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index 52c12c731..5a8767f35 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -1,150 +1,149 @@ Cursor = require 'cursor' Range = require 'range' -Template = require 'template' +{View} = require 'space-pen' $$ = require 'template/builder' module.exports = -class Selection extends Template - content: -> +class Selection extends View + @content: -> @div() - viewProperties: - anchor: null - modifyingSelection: null - regions: null + anchor: null + modifyingSelection: null + regions: null - initialize: (@editor) -> - @regions = [] - @cursor = @editor.cursor - @cursor.on 'cursor:position-changed', => - if @modifyingSelection - @updateAppearance() - else - @clearSelection() - - clearSelection: -> - @anchor = null - @updateAppearance() - - updateAppearance: -> - @clearRegions() - - range = @getRange() - return if range.isEmpty() - - rowSpan = range.end.row - range.start.row - - if rowSpan == 0 - @appendRegion(1, range.start, range.end) + initialize: (@editor) -> + @regions = [] + @cursor = @editor.cursor + @cursor.on 'cursor:position-changed', => + if @modifyingSelection + @updateAppearance() else - @appendRegion(1, range.start, null) - if rowSpan > 1 - @appendRegion(rowSpan - 1, { row: range.start.row + 1, column: 0}, null) - @appendRegion(1, { row: range.end.row, column: 0 }, range.end) + @clearSelection() - appendRegion: (rows, start, end) -> - { lineHeight, charWidth } = @editor - css = {} - css.top = start.row * lineHeight - css.left = start.column * charWidth - css.height = lineHeight * rows - if end - css.width = end.column * charWidth - css.left - else - css.right = 0 + clearSelection: -> + @anchor = null + @updateAppearance() - region = $$.div(class: 'selection').css(css) - @append(region) - @regions.push(region) + updateAppearance: -> + @clearRegions() - clearRegions: -> - region.remove() for region in @regions - @regions = [] + range = @getRange() + return if range.isEmpty() - getRange: -> - if @anchor - new Range(@anchor.getPosition(), @cursor.getPosition()) - else - new Range(@cursor.getPosition(), @cursor.getPosition()) + rowSpan = range.end.row - range.start.row - setRange: (range) -> - @cursor.setPosition(range.start) - @modifySelection => - @cursor.setPosition(range.end) + if rowSpan == 0 + @appendRegion(1, range.start, range.end) + else + @appendRegion(1, range.start, null) + if rowSpan > 1 + @appendRegion(rowSpan - 1, { row: range.start.row + 1, column: 0}, null) + @appendRegion(1, { row: range.end.row, column: 0 }, range.end) - getText: -> - @editor.buffer.getTextInRange @getRange() + appendRegion: (rows, start, end) -> + { lineHeight, charWidth } = @editor + css = {} + css.top = start.row * lineHeight + css.left = start.column * charWidth + css.height = lineHeight * rows + if end + css.width = end.column * charWidth - css.left + else + css.right = 0 - insertText: (text) -> - @editor.buffer.change(@getRange(), text) + region = $$.div(class: 'selection').css(css) + @append(region) + @regions.push(region) - insertNewline: -> - @insertText('\n') + clearRegions: -> + region.remove() for region in @regions + @regions = [] - delete: -> - range = @getRange() - @editor.buffer.change(range, '') unless range.isEmpty() + getRange: -> + if @anchor + new Range(@anchor.getPosition(), @cursor.getPosition()) + else + new Range(@cursor.getPosition(), @cursor.getPosition()) - isEmpty: -> - @getRange().isEmpty() + setRange: (range) -> + @cursor.setPosition(range.start) + @modifySelection => + @cursor.setPosition(range.end) - modifySelection: (fn) -> - @placeAnchor() - @modifyingSelection = true - fn() - @modifyingSelection = false + getText: -> + @editor.buffer.getTextInRange @getRange() - placeAnchor: -> - return if @anchor - cursorPosition = @cursor.getPosition() - @anchor = { getPosition: -> cursorPosition } + insertText: (text) -> + @editor.buffer.change(@getRange(), text) - selectWord: -> - row = @cursor.getRow() - column = @cursor.getColumn() + insertNewline: -> + @insertText('\n') - line = @editor.buffer.getLine(row) - leftSide = line[0...column].split('').reverse().join('') # reverse left side - rightSide = line[column..] + delete: -> + range = @getRange() + @editor.buffer.change(range, '') unless range.isEmpty() - regex = /^\w*/ - startOffset = -regex.exec(leftSide)?[0]?.length or 0 - endOffset = regex.exec(rightSide)?[0]?.length or 0 + isEmpty: -> + @getRange().isEmpty() - range = new Range([row, column + startOffset], [row, column + endOffset]) - @setRange range + modifySelection: (fn) -> + @placeAnchor() + @modifyingSelection = true + fn() + @modifyingSelection = false - selectRight: -> - @modifySelection => - @cursor.moveRight() + placeAnchor: -> + return if @anchor + cursorPosition = @cursor.getPosition() + @anchor = { getPosition: -> cursorPosition } - selectLeft: -> - @modifySelection => - @cursor.moveLeft() + selectWord: -> + row = @cursor.getRow() + column = @cursor.getColumn() - selectUp: -> - @modifySelection => - @cursor.moveUp() + line = @editor.buffer.getLine(row) + leftSide = line[0...column].split('').reverse().join('') # reverse left side + rightSide = line[column..] - selectDown: -> - @modifySelection => - @cursor.moveDown() + regex = /^\w*/ + startOffset = -regex.exec(leftSide)?[0]?.length or 0 + endOffset = regex.exec(rightSide)?[0]?.length or 0 - selectToPosition: (position) -> - @modifySelection => - @cursor.setPosition(position) + range = new Range([row, column + startOffset], [row, column + endOffset]) + @setRange range - moveCursorToLineEnd: -> - @cursor.moveToLineEnd() + selectRight: -> + @modifySelection => + @cursor.moveRight() - moveCursorToLineStart: -> - @cursor.moveToLineStart() + selectLeft: -> + @modifySelection => + @cursor.moveLeft() - cut: -> - @copy() - @delete() + selectUp: -> + @modifySelection => + @cursor.moveUp() - copy: -> - return if @isEmpty() - text = @editor.buffer.getTextInRange @getRange() - atom.native.writeToPasteboard text + selectDown: -> + @modifySelection => + @cursor.moveDown() + + selectToPosition: (position) -> + @modifySelection => + @cursor.setPosition(position) + + moveCursorToLineEnd: -> + @cursor.moveToLineEnd() + + moveCursorToLineStart: -> + @cursor.moveToLineStart() + + cut: -> + @copy() + @delete() + + copy: -> + return if @isEmpty() + text = @editor.buffer.getTextInRange @getRange() + atom.native.writeToPasteboard text diff --git a/src/atom/window.coffee b/src/atom/window.coffee index 5cf069031..0f74dbe8e 100644 --- a/src/atom/window.coffee +++ b/src/atom/window.coffee @@ -14,7 +14,7 @@ windowAdditions = startup: -> @menuItemActions = {} - @rootView = RootView.build(url: $atomController.url?.toString()) + @rootView = new RootView(url: $atomController.url?.toString()) $('body').append @rootView @registerEventHandlers() @bindMenuItems() diff --git a/src/stdlib/template.coffee b/src/stdlib/template.coffee deleted file mode 100644 index e2587e606..000000000 --- a/src/stdlib/template.coffee +++ /dev/null @@ -1,76 +0,0 @@ -$ = require 'jquery' -_ = require 'underscore' -Builder = require 'template/builder' - -module.exports = -class Template - @events: 'blur change click dblclick error focus input keydown - keypress keyup load mousedown mousemove mouseout mouseover - mouseup resize scroll select submit unload'.split /\s+/ - - @buildTagMethod: (name) -> - this.prototype[name] = (args...) -> @builder.tag(name, args...) - - @buildTagMethod(name) for name in Builder.elements.normal - @buildTagMethod(name) for name in Builder.elements.void - - @build: (attributes) -> - (new this).build(attributes) - - @toHtml: (attributes) -> - (new this).toHtml(attributes) - - build: (attributes={}) -> - @builder = new Builder - @content(attributes) - view = @builder.toFragment() - @bindEvents(view) - if @viewProperties - $.extend(view, @viewProperties) - view.attr('triggerAttachEvents', true) - view.initialize?(attributes) - view - - toHtml: (attributes) -> - @builder = new Builder - @content(attributes) - @builder.toHtml() - - subview: (args...) -> - @builder.subview.apply(@builder, args) - - raw: (text) -> - @builder.raw(text) - - bindEvents: (view) -> - for eventName in this.constructor.events - selector = "[#{eventName}]" - elements = view.find(selector).add(view.filter(selector)) - - elements.each -> - elt = $(this) - methodName = elt.attr(eventName) - elt.on eventName, (event) -> view[methodName](event, elt) - -$.fn.view = -> - this.data('view') - -# Trigger attach event when views are added to the DOM -triggerAttachEvent = (elt) -> - if elt.attr?('triggerAttachEvents') and elt.parents('html').length - elt.find('[triggerAttachEvents]').add(elt).trigger('attach') - -_.each ['append', 'prepend', 'after', 'before'], (methodName) -> - originalMethod = $.fn[methodName] - $.fn[methodName] = (args...) -> - result = originalMethod.apply(this, args) - triggerAttachEvent(args[0]) - result - -_.each ['prependTo', 'appendTo', 'insertAfter', 'insertBefore'], (methodName) -> - originalMethod = $.fn[methodName] - $.fn[methodName] = (args...) -> - result = originalMethod.apply(this, args) - triggerAttachEvent(this) - result - diff --git a/vendor/space-pen.coffee b/vendor/space-pen.coffee new file mode 100644 index 000000000..648f8f5ef --- /dev/null +++ b/vendor/space-pen.coffee @@ -0,0 +1,161 @@ +# Modified from 26fca5374e546fd8cc2f12d1140f915185611bdc +# Add require 'jquery' +$ = jQuery = require('jquery') + +elements = + 'a abbr address article aside audio b bdi bdo blockquote body button + canvas caption cite code colgroup datalist dd del details dfn div dl dt em + fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup + html i iframe ins kbd label legend li map mark menu meter nav noscript object + ol optgroup option output p pre progress q rp rt ruby s samp script section + select small span strong style sub summary sup table tbody td textarea tfoot + th thead time title tr u ul video area base br col command embed hr img input + keygen link meta param source track wbrk'.split /\s+/ + +voidElements = + 'area base br col command embed hr img input keygen link meta param + source track wbr'.split /\s+/ + +events = + 'blur change click dblclick error focus input keydown + keypress keyup load mousedown mousemove mouseout mouseover + mouseup resize scroll select submit unload'.split /\s+/ + +idCounter = 0 + +class View extends jQuery + elements.forEach (tagName) -> + View[tagName] = (args...) -> @builder.tag(tagName, args...) + + @subview: (name, view) -> @builder.subview(name, view) + @text: (string) -> @builder.text(string) + @raw: (string) -> @builder.raw(string) + + constructor: (params={}) -> + postProcessingSteps = @buildHtml(params) + @constructor = jQuery # sadly, jQuery assumes this.constructor == jQuery in pushStack + @wireOutlets(this) + @bindEventHandlers(this) + @find('*').andSelf().data('view', this) + @attr('triggerAttachEvents', true) + step(this) for step in postProcessingSteps + @initialize?(params) + + buildHtml: (params) -> + @constructor.builder = new Builder + @constructor.content(params) + [html, postProcessingSteps] = @constructor.builder.buildHtml() + @constructor.builder = null + jQuery.fn.init.call(this, html) + postProcessingSteps + + wireOutlets: (view) -> + @find('[outlet]').each -> + element = $(this) + view[element.attr('outlet')] = element + + bindEventHandlers: (view) -> + for eventName in events + selector = "[#{eventName}]" + elements = view.find(selector).add(view.filter(selector)) + elements.each -> + element = $(this) + methodName = element.attr(eventName) + element.on eventName, (event) -> view[methodName](event, element) + +class Builder + constructor: -> + @document = [] + @postProcessingSteps = [] + + buildHtml: -> + [@document.join(''), @postProcessingSteps] + + tag: (name, args...) -> + options = @extractOptions(args) + + @openTag(name, options.attributes) + + if name in voidElements + if (options.text? or options.content?) + throw new Error("Self-closing tag #{name} cannot have text or content") + else + options.content?() + @text(options.text) if options.text + @closeTag(name) + + openTag: (name, attributes) -> + attributePairs = + for attributeName, value of attributes + "#{attributeName}=\"#{value}\"" + + attributesString = + if attributePairs.length + " " + attributePairs.join(" ") + else + "" + + @document.push "<#{name}#{attributesString}>" + + closeTag: (name) -> + @document.push "" + + text: (string) -> + escapedString = string + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>') + + @document.push escapedString + + raw: (string) -> + @document.push string + + subview: (outletName, subview) -> + subviewId = "subview-#{++idCounter}" + @tag 'div', id: subviewId + @postProcessingSteps.push (view) -> + view[outletName] = subview + subview.parentView = view + view.find("div##{subviewId}").replaceWith(subview) + + extractOptions: (args) -> + options = {} + for arg in args + type = typeof(arg) + if type is "function" + options.content = arg + else if type is "string" or type is "number" + options.text = arg.toString() + else + options.attributes = arg + options + +jQuery.fn.view = -> this.data('view') + +# Trigger attach event when views are added to the DOM +triggerAttachEvent = (element) -> + if element.attr?('triggerAttachEvents') and element.parents('html').length + element.find('[triggerAttachEvents]').add(element).trigger('attach') + +for methodName in ['append', 'prepend', 'after', 'before'] + do (methodName) -> + originalMethod = $.fn[methodName] + jQuery.fn[methodName] = (args...) -> + result = originalMethod.apply(this, args) + triggerAttachEvent(args[0]) + result + +for methodName in ['prependTo', 'appendTo', 'insertAfter', 'insertBefore'] + do (methodName) -> + originalMethod = $.fn[methodName] + jQuery.fn[methodName] = (args...) -> + result = originalMethod.apply(this, args) + triggerAttachEvent(this) + result + +(exports ? this).View = View + +