mirror of
https://github.com/atom/atom.git
synced 2026-01-23 22:08:08 -05:00
Use SpacePen for all views
SpacePen is better because its objects inherit directly from the jQuery prototype, meaning you can create them with `new`.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@ describe "VimMode", ->
|
||||
editor = null
|
||||
|
||||
beforeEach ->
|
||||
editor = Editor.build()
|
||||
editor = new Editor
|
||||
editor.enableKeymap()
|
||||
vimMode = new VimMode(editor)
|
||||
|
||||
|
||||
@@ -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 = $('<div>')
|
||||
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 = $('<div>')
|
||||
fragment.append view
|
||||
expect(attachHandler).not.toHaveBeenCalled()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = $('<pre style="position: absolute; visibility: hidden;">x</pre>')
|
||||
@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 = $('<pre style="position: absolute; visibility: hidden;">x</pre>')
|
||||
@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()
|
||||
|
||||
|
||||
@@ -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 $("<li>#{url}</li>")
|
||||
populateUrlList: ->
|
||||
@urlList.empty()
|
||||
for url in @findMatches(@input.val())
|
||||
@urlList.append $("<li>#{url}</li>")
|
||||
|
||||
@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
|
||||
|
||||
@@ -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 = $('<div class="pane">')
|
||||
pane.append(view)
|
||||
@main.after(pane)
|
||||
addPane: (view) ->
|
||||
pane = $('<div class="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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
161
vendor/space-pen.coffee
vendored
Normal file
161
vendor/space-pen.coffee
vendored
Normal file
@@ -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 "</#{name}>"
|
||||
|
||||
text: (string) ->
|
||||
escapedString = string
|
||||
.replace(/&/g, '&')
|
||||
.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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user