Merge branch 'dev' into web-workers

This commit is contained in:
Nathan Sobo
2013-01-27 18:53:59 -07:00
43 changed files with 635 additions and 92 deletions

View File

@@ -833,3 +833,51 @@ describe 'Buffer', ->
expect(buffer.getText()).toBe "a"
buffer.append("b\nc");
expect(buffer.getText()).toBe "ab\nc"
describe "line ending support", ->
describe ".lineEndingForRow(line)", ->
it "return the line ending for each buffer line", ->
buffer.setText("a\r\nb\nc")
expect(buffer.lineEndingForRow(0)).toBe '\r\n'
expect(buffer.lineEndingForRow(1)).toBe '\n'
expect(buffer.lineEndingForRow(2)).toBeUndefined()
describe ".lineForRow(line)", ->
it "returns the line text without the line ending for both lf and crlf lines", ->
buffer.setText("a\r\nb\nc")
expect(buffer.lineForRow(0)).toBe 'a'
expect(buffer.lineForRow(1)).toBe 'b'
expect(buffer.lineForRow(2)).toBe 'c'
describe ".getText()", ->
it "returns the text with the corrent line endings for each row", ->
buffer.setText("a\r\nb\nc")
expect(buffer.getText()).toBe "a\r\nb\nc"
buffer.setText("a\r\nb\nc\n")
expect(buffer.getText()).toBe "a\r\nb\nc\n"
describe "when editing a line", ->
it "preserves the existing line ending", ->
buffer.setText("a\r\nb\nc")
buffer.insert([0, 1], "1")
expect(buffer.getText()).toBe "a1\r\nb\nc"
describe "when inserting text with multiple lines", ->
describe "when the current line has a line ending", ->
it "uses the same line ending as the line where the text is inserted", ->
buffer.setText("a\r\n")
buffer.insert([0,1], "hello\n1\n\n2")
expect(buffer.getText()).toBe "ahello\r\n1\r\n\r\n2\r\n"
describe "when the current line has no line ending (because it's the last line of the buffer)", ->
describe "when the buffer contains only a single line", ->
it "honors the line endings in the inserted text", ->
buffer.setText("initialtext")
buffer.append("hello\n1\r\n2\n")
expect(buffer.getText()).toBe "initialtexthello\n1\r\n2\n"
describe "when the buffer contains a preceding line", ->
it "uses the line ending of the preceding line", ->
buffer.setText("\ninitialtext")
buffer.append("hello\n1\r\n2\n")
expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n"

View File

@@ -1604,6 +1604,20 @@ describe "Editor", ->
expect(rightEditor.find(".line:first").text()).toBe "_tab _;"
expect(leftEditor.find(".line:first").text()).toBe "_tab _;"
it "displays trailing carriage return using a visible non-empty value", ->
editor.setText "a line that ends with a carriage return\r\n"
editor.attachToDom()
expect(config.get("editor.showInvisibles")).toBeFalsy()
expect(editor.renderedLines.find('.line:first').text()).toBe "a line that ends with a carriage return"
config.set("editor.showInvisibles", true)
cr = editor.invisibles?.cr
expect(cr).toBeTruthy()
eol = editor.invisibles?.eol
expect(eol).toBeTruthy()
expect(editor.renderedLines.find('.line:first').text()).toBe "a line that ends with a carriage return#{cr}#{eol}"
describe "gutter rendering", ->
beforeEach ->
editor.attachToDom(heightInLines: 5.5)
@@ -2212,3 +2226,8 @@ describe "Editor", ->
edited = editor.replaceSelectedText(replacer)
expect(replaced).toBe true
expect(edited).toBe false
describe "when editor:copy-path is triggered", ->
it "copies the absolute path to the editor's file to the pasteboard", ->
editor.trigger 'editor:copy-path'
expect(pasteboard.read()[0]).toBe editor.getPath()

View File

@@ -308,16 +308,16 @@ describe "LanguageMode", ->
expect(buffer.lineForRow(3)).toBe " font-weight: bold !important;"
it "uncomments lines with leading whitespace", ->
buffer.replaceLines(2, 2, " /*width: 110%;*/")
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/")
languageMode.toggleLineCommentsForBufferRows(2, 2)
expect(buffer.lineForRow(2)).toBe " width: 110%;"
it "uncomments lines with trailing whitespace", ->
buffer.replaceLines(2, 2, "/*width: 110%;*/ ")
buffer.change([[2, 0], [2, Infinity]], "/*width: 110%;*/ ")
languageMode.toggleLineCommentsForBufferRows(2, 2)
expect(buffer.lineForRow(2)).toBe "width: 110%; "
it "uncomments lines with leading and trailing whitespace", ->
buffer.replaceLines(2, 2, " /*width: 110%;*/ ")
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/ ")
languageMode.toggleLineCommentsForBufferRows(2, 2)
expect(buffer.lineForRow(2)).toBe " width: 110%; "

View File

@@ -59,6 +59,17 @@ describe "SelectList", ->
expect(selectList.error).not.toBeVisible()
expect(selectList).not.toHaveClass("error")
it "displays no elements until the array has been set on the list", ->
selectList.array = null
selectList.list.empty()
miniEditor.insertText('la')
window.advanceClock(selectList.inputThrottle)
expect(list.find('li').length).toBe 0
expect(selectList).not.toHaveClass("error")
selectList.setArray(array)
expect(list.find('li').length).toBe 2
describe "when core:move-up / core:move-down are triggered on the miniEditor", ->
it "selects the previous / next item in the list, or wraps around to the other side", ->
expect(list.find('li:first')).toHaveClass 'selected'
@@ -153,3 +164,16 @@ describe "SelectList", ->
miniEditor.trigger 'focusout'
expect(selectList.cancelled).toHaveBeenCalled()
expect(selectList.detach).toHaveBeenCalled()
describe "the core:move-to-top event", ->
it "scrolls to the top and selects the first element", ->
selectList.trigger 'core:move-down'
expect(list.find('li:eq(1)')).toHaveClass 'selected'
selectList.trigger 'core:move-to-top'
expect(list.find('li:first')).toHaveClass 'selected'
describe "the core:move-to-bottom event", ->
it "scrolls to the bottom and selects the last element", ->
expect(list.find('li:first')).toHaveClass 'selected'
selectList.trigger 'core:move-to-bottom'
expect(list.find('li:last')).toHaveClass 'selected'

View File

@@ -16,7 +16,7 @@ class AtomPackage extends Package
@loadMetadata()
@loadKeymaps()
@loadStylesheets() if @autoloadStylesheets
rootView.activatePackage(@name, this) unless @isDirectory
rootView?.activatePackage(@name, this) unless @isDirectory
catch e
console.warn "Failed to load package named '#{@name}'", e.stack
this

View File

@@ -1,4 +1,5 @@
Range = require 'range'
_ = require 'underscore'
module.exports =
class BufferChangeOperation
@@ -8,7 +9,8 @@ class BufferChangeOperation
newRange: null
newText: null
constructor: ({@buffer, @oldRange, @newText}) ->
constructor: ({@buffer, @oldRange, @newText, @options}) ->
@options ?= {}
do: ->
@oldText = @buffer.getTextInRange(@oldRange)
@@ -26,18 +28,39 @@ class BufferChangeOperation
oldText: @newText
newText: @oldText
splitLines: (text) ->
lines = text.split('\n')
lineEndings = []
for line, index in lines
if _.endsWith(line, '\r')
lines[index] = line[0..-2]
lineEndings[index] = '\r\n'
else
lineEndings[index] = '\n'
{lines, lineEndings}
changeBuffer: ({ oldRange, newRange, newText, oldText }) ->
{ prefix, suffix } = @buffer.prefixAndSuffixForRange(oldRange)
newTextLines = newText.split('\n')
if newTextLines.length == 1
newTextLines = [prefix + newText + suffix]
else
lastLineIndex = newTextLines.length - 1
newTextLines[0] = prefix + newTextLines[0]
newTextLines[lastLineIndex] += suffix
{lines, lineEndings} = @splitLines(newText)
lastLineIndex = lines.length - 1
@buffer.replaceLines(oldRange.start.row, oldRange.end.row, newTextLines)
if lines.length == 1
lines = [prefix + newText + suffix]
else
lines[0] = prefix + lines[0]
lines[lastLineIndex] += suffix
startRow = oldRange.start.row
endRow = oldRange.end.row
normalizeLineEndings = @options.normalizeLineEndings ? true
if normalizeLineEndings and suggestedLineEnding = @buffer.suggestedLineEndingForRow(startRow)
lineEndings[index] = suggestedLineEnding for index in [0..lastLineIndex]
@buffer.lines[startRow..endRow] = lines
@buffer.lineEndings[startRow..endRow] = lineEndings
@buffer.cachedMemoryContents = null
@buffer.conflict = false if @buffer.conflict and !@buffer.isModified()
event = { oldRange, newRange, oldText, newText }
@buffer.trigger 'changed', event
@@ -47,11 +70,11 @@ class BufferChangeOperation
calculateNewRange: (oldRange, newText) ->
newRange = new Range(oldRange.start.copy(), oldRange.start.copy())
newTextLines = newText.split('\n')
if newTextLines.length == 1
{lines} = @splitLines(newText)
if lines.length == 1
newRange.end.column += newText.length
else
lastLineIndex = newTextLines.length - 1
lastLineIndex = lines.length - 1
newRange.end.row += lastLineIndex
newRange.end.column = newTextLines[lastLineIndex].length
newRange.end.column = lines[lastLineIndex].length
newRange

View File

@@ -19,6 +19,7 @@ class Buffer
cachedMemoryContents: null
conflict: false
lines: null
lineEndings: null
file: null
anchors: null
anchorRanges: null
@@ -29,6 +30,7 @@ class Buffer
@anchors = []
@anchorRanges = []
@lines = ['']
@lineEndings = []
if path
throw "Path '#{path}' does not exist" unless fs.exists(path)
@@ -104,10 +106,10 @@ class Buffer
null
getText: ->
@cachedMemoryContents ?= @lines.join('\n')
@cachedMemoryContents ?= @getTextInRange(@getRange())
setText: (text) ->
@change(@getRange(), text)
@change(@getRange(), text, normalizeLineEndings: false)
getRange: ->
new Range([0, 0], [@getLastRow(), @getLastLine().length])
@@ -118,12 +120,14 @@ class Buffer
return @lines[range.start.row][range.start.column...range.end.column]
multipleLines = []
multipleLines.push @lines[range.start.row][range.start.column..] # first line
multipleLines.push @lineForRow(range.start.row)[range.start.column..] # first line
multipleLines.push @lineEndingForRow(range.start.row)
for row in [range.start.row + 1...range.end.row]
multipleLines.push @lines[row] # middle lines
multipleLines.push @lines[range.end.row][0...range.end.column] # last line
multipleLines.push @lineForRow(row) # middle lines
multipleLines.push @lineEndingForRow(row)
multipleLines.push @lineForRow(range.end.row)[0...range.end.column] # last line
return multipleLines.join '\n'
return multipleLines.join ''
getLines: ->
@lines
@@ -131,6 +135,12 @@ class Buffer
lineForRow: (row) ->
@lines[row]
lineEndingForRow: (row) ->
@lineEndings[row] unless row is @getLastRow()
suggestedLineEndingForRow: (row) ->
@lineEndingForRow(row) ? @lineEndingForRow(row - 1)
lineLengthForRow: (row) ->
@lines[row].length
@@ -195,9 +205,9 @@ class Buffer
delete: (range) ->
@change(range, '')
change: (oldRange, newText) ->
change: (oldRange, newText, options) ->
oldRange = Range.fromObject(oldRange)
operation = new BufferChangeOperation({buffer: this, oldRange, newText})
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
range = @pushOperation(operation)
range
@@ -214,11 +224,6 @@ class Buffer
prefix: @lines[range.start.row][0...range.start.column]
suffix: @lines[range.end.row][range.end.column..]
replaceLines: (startRow, endRow, newLines) ->
@lines[startRow..endRow] = newLines
@cachedMemoryContents = null
@conflict = false if @conflict and !@isModified()
pushOperation: (operation, editSession) ->
if @undoManager
@undoManager.pushOperation(operation, editSession)

View File

@@ -1,4 +1,5 @@
AtomPackage = require 'atom-package'
_ = require 'underscore'
module.exports =
class DeferredAtomPackage extends AtomPackage
@@ -10,8 +11,13 @@ class DeferredAtomPackage extends AtomPackage
activate: (@rootView, @state) ->
@instance = null
for event in @loadEvents
@rootView.command event, (e) => @onLoadEvent(e, @getInstance())
onLoadEvent = (e) => @onLoadEvent(e, @getInstance())
if _.isArray(@loadEvents)
for event in @loadEvents
@rootView.command(event, onLoadEvent)
else
for event, selector of @loadEvents
@rootView.command(event, selector, onLoadEvent)
this
deactivate: -> @instance?.deactivate?()

View File

@@ -182,6 +182,7 @@ class Editor extends View
'editor:close-other-edit-sessions': @destroyInactiveEditSessions
'editor:close-all-edit-sessions': @destroyAllEditSessions
'editor:select-grammar': @selectGrammar
'editor:copy-path': @copyPathToPasteboard
documentation = {}
for name, method of editorBindings
@@ -310,9 +311,10 @@ class Editor extends View
setInvisibles: (@invisibles={}) ->
_.defaults @invisibles,
eol: '\u00ac',
space: '\u2022',
eol: '\u00ac'
space: '\u2022'
tab: '\u00bb'
cr: '\u00a4'
@resetDisplay()
checkoutHead: -> @getBuffer().checkoutHead()
@@ -1079,8 +1081,11 @@ class Editor extends View
position += token.value.length
popScope() while scopeStack.length > 0
if not @mini and invisibles?.eol
line.push("<span class='invisible'>#{invisibles.eol}</span>")
if invisibles and not @mini
if invisibles.cr and screenLine.lineEnding is '\r\n'
line.push("<span class='invisible'>#{invisibles.cr}</span>")
if invisibles.eol
line.push("<span class='invisible'>#{invisibles.eol}</span>")
line.push('</pre>')
line.join('')
@@ -1159,3 +1164,7 @@ class Editor extends View
@insertText(text, select: true)
true
copyPathToPasteboard: ->
path = @getPath()
pasteboard.write(path) if path?

View File

@@ -27,6 +27,7 @@
'meta-+': 'window:increase-font-size'
'meta--': 'window:decrease-font-size'
'ctrl-w w': 'window:focus-next-pane'
'ctrl-tab': 'window:focus-next-pane'
'alt-meta-i': 'toggle-dev-tools'

View File

@@ -36,4 +36,5 @@
'meta-U': 'editor:lower-case'
'alt-meta-w': 'editor:close-other-edit-sessions'
'meta-P': 'editor:close-all-edit-sessions'
'meta-l': 'editor:select-grammar'
'meta-L': 'editor:select-grammar'
'ctrl-C': 'editor:copy-path'

View File

@@ -18,7 +18,7 @@ class RootView extends View
disabledPackages: []
@content: ->
@div id: 'root-view', tabindex: -1, =>
@div id: 'root-view', tabindex: 0, =>
@div id: 'horizontal', outlet: 'horizontal', =>
@div id: 'vertical', outlet: 'vertical', =>
@div id: 'panes', outlet: 'panes'

View File

@@ -2,7 +2,7 @@ _ = require 'underscore'
module.exports =
class ScreenLine
constructor: ({tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
constructor: ({tokens, @lineEnding, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
@tokens = @breakOutAtomicTokens(tokens, tabLength)
@bufferRows ?= 1
@startBufferColumn ?= 0

View File

@@ -26,6 +26,12 @@ class SelectList extends View
@miniEditor.on 'focusout', => @cancel() unless @cancelling
@on 'core:move-up', => @selectPreviousItem()
@on 'core:move-down', => @selectNextItem()
@on 'core:move-to-top', =>
@selectItem(@list.find('li:first'))
@list.scrollToTop()
@on 'core:move-to-bottom', =>
@selectItem(@list.find('li:last'))
@list.scrollToBottom()
@on 'core:confirm', => @confirmSelection()
@on 'core:cancel', => @cancel()
@@ -62,6 +68,8 @@ class SelectList extends View
@loading.text(message).show()
populateList: ->
return unless @array?
filterQuery = @miniEditor.getText()
if filterQuery.length
filteredArray = fuzzyFilter(@array, filterQuery, key: @filterKey)

View File

@@ -137,8 +137,9 @@ class TokenizedBuffer
buildTokenizedScreenLineForRow: (row, ruleStack) ->
line = @buffer.lineForRow(row)
lineEnding = @buffer.lineEndingForRow(row)
{ tokens, ruleStack } = @languageMode.tokenizeLine(line, ruleStack, row is 0)
new ScreenLine({tokens, ruleStack, @tabLength})
new ScreenLine({tokens, ruleStack, @tabLength, lineEnding})
lineForScreenRow: (row) ->
@linesForScreenRows(row, row)[0]

View File

@@ -180,17 +180,19 @@ describe "CommandPanel", ->
expect(commandPanel.hasParent()).toBeTruthy()
describe "when the mini editor is focused", ->
it "retains focus on the mini editor and does not show the preview list", ->
it "retains focus on the mini editor and does not show the preview list or preview count", ->
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeHidden()
expect(commandPanel.previewCount).toBeHidden()
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
describe "when the mini editor is not focused", ->
it "focuses the mini editor and does not show the preview list", ->
it "focuses the mini editor and does not show the preview list or preview count", ->
rootView.focus()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeHidden()
expect(commandPanel.previewCount).toBeHidden()
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
describe "when the command panel is not visible", ->
@@ -297,7 +299,7 @@ describe "CommandPanel", ->
expect(commandPanel.previewList).toBeVisible()
expect(commandPanel.previewList).toMatchSelector ':focus'
previewItem = commandPanel.previewList.find("li:contains(sample.js):first")
expect(previewItem.text()).toBe "sample.js"
expect(previewItem.text()).toBe "sample.js(1)"
expect(previewItem.next().find('.preview').text()).toBe "var quicksort = function () {"
expect(previewItem.next().find('.preview > .match').text()).toBe "quicksort"
@@ -377,6 +379,10 @@ describe "CommandPanel", ->
rootView.trigger 'command-panel:toggle'
waitsForPromise -> commandPanel.execute('X x/sort/')
it "displays the number of files and operations", ->
rootView.attachToDom()
expect(commandPanel.previewCount.text()).toBe '17 matches in 4 files'
describe "when move-down and move-up are triggered on the preview list", ->
it "selects the next/previous operation (if there is one), and scrolls the list if needed", ->
rootView.attachToDom()
@@ -405,28 +411,15 @@ describe "CommandPanel", ->
previewList.trigger 'core:move-down'
expect(previewList.scrollTop()).toBe 0
it "wraps around when the list is at the beginning or end", ->
rootView.attachToDom()
expect(previewList.find('li.operation:eq(0)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[0]
previewList.trigger 'core:move-up'
expect(previewList.find('li.operation:last')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe _.last(previewList.getOperations())
previewList.trigger 'core:move-down'
expect(previewList.find('li.operation:eq(0)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[0]
it "doesn't bubble up the event and the command panel text doesn't change", ->
rootView.attachToDom()
commandPanel.miniEditor.setText "command"
previewList.focus()
previewList.trigger 'core:move-up'
expect(previewList.find('li.operation:last')).toHaveClass 'selected'
expect(previewList.find('li.operation:eq(0)')).toHaveClass 'selected'
expect(commandPanel.miniEditor.getText()).toBe 'command'
previewList.trigger 'core:move-down'
expect(previewList.find('li.operation:eq(0)')).toHaveClass 'selected'
expect(previewList.find('li.operation:eq(1)')).toHaveClass 'selected'
expect(commandPanel.miniEditor.getText()).toBe 'command'
describe "when move-to-top and move-to-bottom are triggered on the preview list", ->

View File

@@ -24,6 +24,7 @@ class CommandPanelView extends View
@content: (rootView) ->
@div class: 'command-panel tool-panel', =>
@div outlet: 'previewCount', class: 'preview-count'
@subview 'previewList', new PreviewList(rootView)
@ul class: 'error-messages', outlet: 'errorMessages'
@div class: 'prompt-and-editor', =>
@@ -48,6 +49,7 @@ class CommandPanelView extends View
@command 'core:move-down', => @navigateForwardInHistory()
@previewList.hide()
@previewCount.hide()
@errorMessages.hide()
@prompt.iconSize(@miniEditor.fontSize)
@@ -73,12 +75,14 @@ class CommandPanelView extends View
togglePreview: ->
if @previewList.is(':focus')
@previewList.hide()
@previewCount.hide()
@detach()
@rootView.focus()
else
@attach() unless @hasParent()
if @previewList.hasOperations()
@previewList.show().focus()
@previewCount.show()
else
@miniEditor.focus()
@@ -94,6 +98,7 @@ class CommandPanelView extends View
detach: ->
@rootView.focus()
@previewList.hide()
@previewCount.hide()
super
escapedCommand: ->
@@ -115,6 +120,7 @@ class CommandPanelView extends View
else if operationsToPreview?.length
@previewList.populate(operationsToPreview)
@previewList.focus()
@previewCount.text("#{_.pluralize(operationsToPreview.length, 'match', 'matches')} in #{_.pluralize(@previewList.getPathCount(), 'file')}").show()
else
@detach()
catch error

View File

@@ -34,7 +34,9 @@ class PreviewList extends ScrollView
operation.index = index for operation, index in operations
operationsByPath = _.groupBy(operations, (operation) -> operation.getPath())
for path, ops of operationsByPath
@li path, class: 'path'
@li class: 'path', =>
@span path
@span "(#{ops.length})", class: 'path-match-number'
for operation in ops
{prefix, suffix, match, range} = operation.preview()
@li 'data-index': operation.index, class: 'operation', =>
@@ -56,16 +58,10 @@ class PreviewList extends ScrollView
lineNumbers.width(maxWidth)
selectNextOperation: ->
if @selectedOperationIndex is @operations.length - 1
@setSelectedOperationIndex(0)
else
@setSelectedOperationIndex(@selectedOperationIndex + 1)
@setSelectedOperationIndex(@selectedOperationIndex + 1)
selectPreviousOperation: ->
if @selectedOperationIndex is 0
@setSelectedOperationIndex(@operations.length - 1)
else
@setSelectedOperationIndex(@selectedOperationIndex - 1)
@setSelectedOperationIndex(@selectedOperationIndex - 1)
setSelectedOperationIndex: (index, scrollToOperation=true) ->
index = Math.max(0, index)
@@ -90,6 +86,9 @@ class PreviewList extends ScrollView
@rootView.focus()
false
getPathCount: ->
_.keys(_.groupBy(@operations, (operation) -> operation.getPath())).length
getOperations: ->
new Array(@operations...)

View File

@@ -0,0 +1,12 @@
DeferredAtomPackage = require 'deferred-atom-package'
module.exports =
class GistsPackage extends DeferredAtomPackage
loadEvents:
'gist:create': '.editor'
instanceClass: 'gists/lib/gists'
onLoadEvent: (event, instance) ->
instance.createGist(event.currentTargetView())

View File

@@ -0,0 +1,2 @@
'body':
'alt-meta-g': 'gist:create'

View File

@@ -0,0 +1,31 @@
$ = require 'jquery'
{$$} = require 'space-pen'
module.exports =
class Gists
@activate: (rootView) -> new Gists(rootView)
constructor: (@rootView) ->
createGist: (editor) ->
gist = { public: false, files: {} }
gist.files[editor.getBuffer().getBaseName()] =
content: editor.getSelectedText() or editor.getText()
$.ajax
url: 'https://api.github.com/gists'
type: 'POST'
dataType: 'json'
contentType: 'application/json; charset=UTF-8'
data: JSON.stringify(gist)
success: (response) =>
pasteboard.write(response.html_url)
notification = $$ ->
@div class: 'gist-notification', =>
@div class: 'message-area', =>
@span "Gist #{response.id} created", class: 'message'
@br()
@span "The url is on your clipboard", class: 'clipboard'
@rootView.append(notification.hide())
notification.fadeIn().delay(2000).fadeOut(complete: -> $(this).remove())

View File

@@ -0,0 +1,61 @@
RootView = require 'root-view'
$ = require 'jquery'
describe "Gists package", ->
[rootView, editor] = []
beforeEach ->
rootView = new RootView(fixturesProject.resolve('sample.js'))
atom.loadPackage('gists').getInstance()
editor = rootView.getActiveEditor()
spyOn($, 'ajax')
afterEach ->
rootView.deactivate()
describe "when gist:create is triggered on an editor", ->
describe "when the editor has no selection", ->
[request, originalFxOffValue] = []
beforeEach ->
originalFxOffValue = $.fx.off
$.fx.off = true
editor.trigger 'gist:create'
expect($.ajax).toHaveBeenCalled()
request = $.ajax.argsForCall[0][0]
afterEach ->
$.fx.off = originalFxOffValue
it "creates an Ajax request to api.github.com with the entire buffer contents as the Gist's content", ->
expect(request.url).toBe 'https://api.github.com/gists'
expect(request.type).toBe 'POST'
requestData = JSON.parse(request.data)
expect(requestData.public).toBeFalsy()
expect(requestData.files).toEqual 'sample.js': content: editor.getText()
describe "when the server responds successfully", ->
beforeEach ->
request.success(html_url: 'https://gist.github.com/1', id: '1')
it "places the created Gist's URL on the clipboard", ->
expect(pasteboard.read()[0]).toBe 'https://gist.github.com/1'
it "flashes that the Gist was created", ->
expect(rootView.find('.gist-notification')).toExist()
expect(rootView.find('.gist-notification .message').text()).toBe 'Gist 1 created'
advanceClock(2000)
expect(rootView.find('.gist-notification')).not.toExist()
describe "when the editor has a selection", ->
beforeEach ->
editor.setSelectedBufferRange [[4, 0], [8, 0]]
it "creates an Ajax with the selected text as the Gist's content", ->
editor.trigger 'gist:create'
expect($.ajax).toHaveBeenCalled()
request = $.ajax.argsForCall[0][0]
requestData = JSON.parse(request.data)
expect(requestData.files).toEqual 'sample.js': content: editor.getSelectedText()

View File

@@ -0,0 +1,12 @@
DeferredAtomPackage = require 'deferred-atom-package'
module.exports =
class GoToLinePackage extends DeferredAtomPackage
loadEvents:
'editor:go-to-line': '.editor'
instanceClass: 'go-to-line/lib/go-to-line-view'
onLoadEvent: (event, instance) ->
instance.toggle(event.currentTargetView())

View File

@@ -0,0 +1,6 @@
'body':
'meta-l': 'editor:go-to-line'
'.go-to-line .mini.editor input':
'enter': 'core:confirm',
'escape': 'core:cancel'
'meta-w': 'core:cancel'

View File

@@ -0,0 +1,54 @@
{View} = require 'space-pen'
Editor = require 'editor'
$ = require 'jquery'
Point = require 'point'
module.exports =
class GoToLineView extends View
@activate: (rootView) -> new GoToLineView(rootView)
@content: ->
@div class: 'go-to-line', =>
@subview 'miniEditor', new Editor(mini: true)
@div class: 'message', outlet: 'message'
initialize: (@rootView) ->
@miniEditor.on 'focusout', => @detach()
@on 'core:confirm', => @confirm()
@on 'core:cancel', => @detach()
@miniEditor.preempt 'textInput', (e) =>
false unless e.originalEvent.data.match(/[0-9]/)
toggle: ->
if @hasParent()
@detach()
else
@attach()
detach: ->
return unless @hasParent()
@miniEditor.setText('')
@previouslyFocusedElement?.focus()
super
confirm: ->
lineNumber = @miniEditor.getText()
editor = rootView.getActiveEditor()
@detach()
return unless editor and lineNumber.length
position = new Point(parseInt(lineNumber - 1, 0))
editor.scrollToBufferPosition(position, center: true)
editor.setCursorBufferPosition(position)
editor.moveCursorToFirstCharacterOfLine()
attach: ->
@previouslyFocusedElement = $(':focus')
@rootView.append(this)
@message.text("Enter a line number 1-#{@rootView.getActiveEditor().getLineCount()}")
@miniEditor.focus()

View File

@@ -0,0 +1,51 @@
RootView = require 'root-view'
describe 'GoToLine', ->
[rootView, goToLine, editor] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/sample.js'))
rootView.enableKeymap()
goToLine = atom.loadPackage("go-to-line").getInstance()
editor = rootView.getActiveEditor()
editor.setCursorBufferPosition([1,0])
afterEach ->
rootView.remove()
describe "when editor:go-to-line is triggered", ->
it "attaches to the root view", ->
expect(goToLine.hasParent()).toBeFalsy()
editor.trigger 'editor:go-to-line'
expect(goToLine.hasParent()).toBeTruthy()
describe "when entering a line number", ->
it "only allows 0-9 to be entered in the mini editor", ->
expect(goToLine.miniEditor.getText()).toBe ''
goToLine.miniEditor.textInput 'a'
expect(goToLine.miniEditor.getText()).toBe ''
goToLine.miniEditor.textInput '40'
expect(goToLine.miniEditor.getText()).toBe '40'
describe "when core:confirm is triggered", ->
describe "when a line number has been entered", ->
it "moves the cursor to the first character of the line", ->
goToLine.miniEditor.textInput '3'
goToLine.miniEditor.trigger 'core:confirm'
expect(editor.getCursorBufferPosition()).toEqual [2, 4]
describe "when no line number has been entered", ->
it "closes the view and does not update the cursor position", ->
editor.trigger 'editor:go-to-line'
expect(goToLine.hasParent()).toBeTruthy()
goToLine.miniEditor.trigger 'core:confirm'
expect(goToLine.hasParent()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
describe "when core:cancel is triggered", ->
it "closes the view and does not update the cursor position", ->
editor.trigger 'editor:go-to-line'
expect(goToLine.hasParent()).toBeTruthy()
goToLine.miniEditor.trigger 'core:cancel'
expect(goToLine.hasParent()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual [1, 0]

View File

@@ -57,3 +57,16 @@ describe "MarkdownPreview", ->
expect(markdownPreviewView).toExist()
markdownPreviewView.trigger('core:cancel')
expect(rootView.find('.markdown-preview')).not.toExist()
describe "when focus is lost", ->
it "removes the markdown preview view", ->
rootView.open('file.md')
editor = rootView.getActiveEditor()
expect(rootView.find('.markdown-preview')).not.toExist()
spyOn(markdownPreview, 'loadHtml')
editor.trigger('markdown-preview:toggle')
markdownPreviewView = rootView.find('.markdown-preview')
expect(markdownPreviewView).toExist()
markdownPreviewView.blur()
expect(rootView.find('.markdown-preview')).not.toExist()

View File

@@ -14,7 +14,9 @@ class MarkdownPreviewView extends ScrollView
initialize: (@rootView) ->
super
@command 'core:cancel', => @detach()
@command 'core:cancel', => @detach() unless @detaching
@on 'focusout', => @detach() unless @detaching
toggle: ->
if @hasParent()
@@ -30,8 +32,10 @@ class MarkdownPreviewView extends ScrollView
@focus()
detach: ->
super()
@detaching = true
super
@rootView.focus()
@detaching = false
getActivePath: ->
@rootView.getActiveEditor()?.getPath()

View File

@@ -6,7 +6,7 @@ class Symbols extends DeferredAtomPackage
loadEvents: [
'symbols-view:toggle-file-symbols'
'symbols-view:toggle-project-symbols'
'symbols-view:jump-to-declaration'
'symbols-view:go-to-declaration'
]
instanceClass: 'symbols-view/src/symbols-view'
@@ -17,5 +17,5 @@ class Symbols extends DeferredAtomPackage
instance.toggleFileSymbols()
when 'symbols-view:toggle-project-symbols'
instance.toggleProjectSymbols()
when 'symbols-view:jump-to-declaration'
instance.jumpToDeclaration()
when 'symbols-view:go-to-declaration'
instance.goToDeclaration()

View File

@@ -1,6 +1,6 @@
'.editor':
'meta-j': 'symbols-view:toggle-file-symbols'
'meta-.': 'symbols-view:jump-to-declaration'
'meta-.': 'symbols-view:go-to-declaration'
'body':
'meta-J': 'symbols-view:toggle-project-symbols'

View File

@@ -126,26 +126,26 @@ describe "SymbolsView", ->
generator.generate().done ->
expect(tags.length).toBe 0
describe "jump to declaration", ->
describe "go to declaration", ->
it "doesn't move the cursor when no declaration is found", ->
rootView.open("tagged.js")
editor = rootView.getActiveEditor()
editor.setCursorBufferPosition([0,2])
editor.trigger 'symbols-view:jump-to-declaration'
editor.trigger 'symbols-view:go-to-declaration'
expect(editor.getCursorBufferPosition()).toEqual [0,2]
it "moves the cursor to the declaration", ->
rootView.open("tagged.js")
editor = rootView.getActiveEditor()
editor.setCursorBufferPosition([6,24])
editor.trigger 'symbols-view:jump-to-declaration'
editor.trigger 'symbols-view:go-to-declaration'
expect(editor.getCursorBufferPosition()).toEqual [2,0]
it "displays matches when more than one exists and opens the selected match", ->
rootView.open("tagged.js")
editor = rootView.getActiveEditor()
editor.setCursorBufferPosition([8,14])
editor.trigger 'symbols-view:jump-to-declaration'
editor.trigger 'symbols-view:go-to-declaration'
expect(symbolsView.list.children('li').length).toBe 2
expect(symbolsView).toBeVisible()
symbolsView.confirmed(symbolsView.array[0])
@@ -163,7 +163,7 @@ describe "SymbolsView", ->
rootView.open("tagged.js")
editor = rootView.getActiveEditor()
editor.setCursorBufferPosition([8,14])
editor.trigger 'symbols-view:jump-to-declaration'
editor.trigger 'symbols-view:go-to-declaration'
expect(symbolsView.list.children('li').length).toBe 1
expect(symbolsView.list.children('li:first').find('.function-name')).toHaveText 'tagged.js'

View File

@@ -102,7 +102,7 @@ class SymbolsView extends SelectList
for line, index in fs.read(file).split('\n')
return new Point(index, 0) if pattern is $.trim(line)
jumpToDeclaration: ->
goToDeclaration: ->
editor = @rootView.getActiveEditor()
matches = TagReader.find(editor)
return unless matches.length

View File

@@ -1,14 +1,20 @@
.tabs {
-webkit-user-select: none;
display: -webkit-box;
-webkit-box-align: center;
}
.tab {
display: table-cell;
-webkit-box-flex: 2;
position: relative;
width:175px;
width: 175px;
max-width: 175px;
min-width: 40px;
box-sizing: border-box;
height: 24px;
}
.tab.active {
-webkit-box-flex: 1;
}
.tab.file-modified .close-icon {
@@ -23,9 +29,5 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
position: absolute;
left: 9px;
top:4px;
bottom:4px;
right: 21px;
padding: 3px 5px;
}

View File

@@ -15,6 +15,13 @@
cursor: default;
}
.command-panel .preview-count {
font-size: 11px;
color: #969696;
text-align: right;
padding-bottom: 1px;
}
.command-panel .preview-list li.selected, .command-panel .preview-list li.operation:hover {
background-color: rgba(255, 255, 255, .13);
}
@@ -53,6 +60,11 @@
display: inline-block;
}
.command-panel .preview-list .path-match-number {
padding-left: 8px;
color: rgba(255, 255, 255, .3);
}
.command-panel .preview-list .preview {
word-break: break-all;
}

View File

@@ -0,0 +1,35 @@
.gist-notification {
position: absolute;
top: 6px;
left: 50%;
margin-left: -5%;
z-index: 99;
padding-left: 5px;
padding-right: 10px;
-webkit-box-shadow: 0px 0px 5px 5px #222;
color: #BBB;
background-color: #333;
}
.gist-notification .message-area {
float: right;
padding-top: 11px;
}
.gist-notification .message {
font-size: 13px;
}
.gist-notification .clipboard {
font-size: 11px;
}
.gist-notification:before {
font-family: 'Octicons Regular';
font-size: 32px;
width: 32px;
height: 32px;
margin-right: 5px;
-webkit-font-smoothing: antialiased;
content: "\f08c";
}

View File

@@ -0,0 +1,27 @@
.go-to-line {
position: absolute;
width: 200px;
top: 0;
left: 50%;
margin-left: -100px;
box-sizing: border-box;
z-index: 99;
background-color: #484848;
border: 1px solid #444;
color: #d2d2d2;
box-shadow: 0 0 10px #000;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
padding: 5px;
cursor: pointer;
}
.go-to-line .editor {
box-sizing: border-box;
padding: 5px;
}
.go-to-line .message {
padding-top: 2px;
font-size: 11px;
}

View File

@@ -14,6 +14,8 @@
"command-panel.css",
"command-palette.css",
"command-logger.css",
"autocomplete.css"
"autocomplete.css",
"gists.css",
"go-to-line.css"
]
}

View File

@@ -61,7 +61,7 @@
.tab.active:hover {
border-top: 1px solid #4a4a4a;
box-shadow: inset -1px 0 0 #595959, inset 1px 0 0 #595959;
border-bottom: 0 none;
border-bottom-color: #424242;
background-image: -webkit-linear-gradient(#555555, #424242);
}

View File

@@ -16,6 +16,13 @@
border: 1px solid #989898;
}
.command-panel .preview-count {
font-size: 11px;
color: #333;
text-align: right;
padding-bottom: 1px;
}
.command-panel .preview-list li.selected, .command-panel .preview-list li.operation:hover {
background-color: rgba(255, 255, 255, .6);
}
@@ -50,6 +57,11 @@
display: inline-block;
}
.command-panel .preview-list .path-match-number {
padding-left: 8px;
color: #3D5075;
}
.command-panel .preview-list .preview {
word-break: break-all;
}

View File

@@ -0,0 +1,35 @@
.gist-notification {
position: absolute;
top: 6px;
left: 50%;
margin-left: -5%;
z-index: 99;
padding-left: 5px;
padding-right: 10px;
-webkit-box-shadow: 0px 0px 5px 5px #222;
color: #BBB;
background-color: #444;
}
.gist-notification .message-area {
float: right;
padding-top: 11px;
}
.gist-notification .message {
font-size: 13px;
}
.gist-notification .clipboard {
font-size: 11px;
}
.gist-notification:before {
font-family: 'Octicons Regular';
font-size: 32px;
width: 32px;
height: 32px;
margin-right: 5px;
-webkit-font-smoothing: antialiased;
content: "\f08c";
}

View File

@@ -0,0 +1,27 @@
.go-to-line {
position: absolute;
width: 200px;
top: 0;
left: 50%;
margin-left: -100px;
box-sizing: border-box;
z-index: 99;
background-color: #eeeeee;
border: 1px solid #c6c6c6;
color: #323232;
box-shadow: 0 0 10px #555;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
padding: 5px;
cursor: pointer;
}
.go-to-line .editor {
box-sizing: border-box;
padding: 5px;
}
.go-to-line .message {
padding-top: 2px;
font-size: 11px;
}

View File

@@ -14,6 +14,8 @@
"command-panel.css",
"command-palette.css",
"command-logger.css",
"autocomplete.css"
"autocomplete.css",
"gists.css",
"go-to-line.css"
]
}

View File

@@ -54,7 +54,7 @@
.tab.active,
.tab.active:hover {
border-bottom: 0 none;
border-bottom-color: #e5e5e5;
box-shadow: inset -1px 0 0 #e0e0e0, inset 1px 0 0 #e0e0e0;
background-image: -webkit-linear-gradient(#fefefe, #e7e6e7);
}
@@ -62,7 +62,7 @@
.tab.active:before,
.tab.active:after {
position: absolute;
bottom: 0;
bottom: -1px;
width: 4px;
height: 4px;
content: " ";