diff --git a/package.json b/package.json index 32e01f6d5..d96e10b4e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "ctags": "0.3.0", "oniguruma": "0.8.0", "mkdirp": "0.3.5", - "git-utils": "0.11.0", + "git-utils": "0.12.0", "underscore": "1.4.4", "d3": "3.0.8", "coffee-cache": "0.1.0", diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 94611320d..631493965 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -341,6 +341,35 @@ describe "EditSession", -> editSession.moveCursorToEndOfWord() expect(editSession.getCursorBufferPosition()).toEqual [11, 8] + describe ".moveCursorToBeginningOfNextWord()", -> + it "moves the cursor before the first character of the next word", -> + editSession.setCursorBufferPosition [0,6] + editSession.addCursorAtBufferPosition [1,11] + editSession.addCursorAtBufferPosition [2,0] + [cursor1, cursor2, cursor3] = editSession.getCursors() + + editSession.moveCursorToBeginningOfNextWord() + + expect(cursor1.getBufferPosition()).toEqual [0, 14] + expect(cursor2.getBufferPosition()).toEqual [1, 13] + expect(cursor3.getBufferPosition()).toEqual [2, 4] + + it "does not blow up when there is no next word", -> + editSession.setCursorBufferPosition [Infinity, Infinity] + endPosition = editSession.getCursorBufferPosition() + editSession.moveCursorToBeginningOfNextWord() + expect(editSession.getCursorBufferPosition()).toEqual endPosition + + it "treats lines with only whitespace as a word", -> + editSession.setCursorBufferPosition([9, 4]) + editSession.moveCursorToBeginningOfNextWord() + expect(editSession.getCursorBufferPosition()).toEqual [10, 0] + + it "works when the current line is blank", -> + editSession.setCursorBufferPosition([10, 0]) + editSession.moveCursorToBeginningOfNextWord() + expect(editSession.getCursorBufferPosition()).toEqual [11, 9] + describe ".getCurrentParagraphBufferRange()", -> it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", -> buffer.setText """ @@ -619,6 +648,25 @@ describe "EditSession", -> expect(selection2.getBufferRange()).toEqual [[3,48], [3,50]] expect(selection2.isReversed()).toBeFalsy() + describe ".selectToBeginningOfNextWord()", -> + it "selects text from cusor position to beginning of next word", -> + editSession.setCursorScreenPosition [0,4] + editSession.addCursorAtScreenPosition [3,48] + + editSession.selectToBeginningOfNextWord() + + expect(editSession.getCursors().length).toBe 2 + [cursor1, cursor2] = editSession.getCursors() + expect(cursor1.getBufferPosition()).toEqual [0,14] + expect(cursor2.getBufferPosition()).toEqual [3,51] + + expect(editSession.getSelections().length).toBe 2 + [selection1, selection2] = editSession.getSelections() + expect(selection1.getBufferRange()).toEqual [[0,4], [0,14]] + expect(selection1.isReversed()).toBeFalsy() + expect(selection2.getBufferRange()).toEqual [[3,48], [3,51]] + expect(selection2.isReversed()).toBeFalsy() + describe ".selectWord()", -> describe "when the cursor is inside a word", -> it "selects the entire word", -> diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index c27a7a31e..b93d7abbf 100644 --- a/spec/app/window-spec.coffee +++ b/spec/app/window-spec.coffee @@ -198,3 +198,31 @@ describe "Window", -> expect(deserialize({ deserializer: 'Foo', version: 3, name: 'Bar' })).toBeUndefined() expect(deserialize({ deserializer: 'Foo', version: 1, name: 'Bar' })).toBeUndefined() expect(deserialize({ deserializer: 'Foo', name: 'Bar' })).toBeUndefined() + + describe "drag and drop", -> + buildDragEvent = (type, files) -> + dataTransfer = + files: files + data: {} + setData: (key, value) -> @data[key] = value + getData: (key) -> @data[key] + + event = $.Event(type) + event.originalEvent = { dataTransfer } + event.preventDefault = -> + event.stopPropagation = -> + event + + describe "when a file is dragged to window", -> + it "opens it", -> + spyOn(atom, "open") + event = buildDragEvent("drop", [ {path: "/fake1"}, {path: "/fake2"} ]) + window.onDrop(event) + expect(atom.open.callCount).toBe 2 + + describe "when a non-file is dragged to window", -> + it "does nothing", -> + spyOn(atom, "open") + event = buildDragEvent("drop", []) + window.onDrop(event) + expect(atom.open).not.toHaveBeenCalled() diff --git a/src/app/cursor.coffee b/src/app/cursor.coffee index 1b111d33b..336591d95 100644 --- a/src/app/cursor.coffee +++ b/src/app/cursor.coffee @@ -178,6 +178,10 @@ class Cursor if position = @getEndOfCurrentWordBufferPosition() @setBufferPosition(position) + moveToBeginningOfNextWord: -> + if position = @getBeginningOfNextWordBufferPosition() + @setBufferPosition(position) + getBeginningOfCurrentWordBufferPosition: (options = {}) -> allowPrevious = options.allowPrevious ? true currentBufferPosition = @getBufferPosition() @@ -205,7 +209,20 @@ class Cursor if not endOfWordPosition?.isEqual(currentBufferPosition) stop() - endOfWordPosition or currentBufferPosition + endOfWordPosition ? currentBufferPosition + + getBeginningOfNextWordBufferPosition: (options = {}) -> + currentBufferPosition = @getBufferPosition() + start = if @isSurroundedByWhitespace() then currentBufferPosition else @getEndOfCurrentWordBufferPosition() + scanRange = [start, @editSession.getEofBufferPosition()] + + beginningOfNextWordPosition = null + @editSession.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) => + beginningOfNextWordPosition = range.start + stop() + + beginningOfNextWordPosition or currentBufferPosition + # Public: Gets the word located under the cursor. # # options - An object with properties based on {Cursor.getBeginningOfCurrentWordBufferPosition}. diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 2bea93c01..eeb7906f0 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -766,23 +766,23 @@ class EditSession # Internal: pushOperation: (operation) -> @buffer.pushOperation(operation, this) - + # Internal: markScreenRange: (args...) -> @displayBuffer.markScreenRange(args...) - + # Internal: markBufferRange: (args...) -> @displayBuffer.markBufferRange(args...) - + # Internal: markScreenPosition: (args...) -> @displayBuffer.markScreenPosition(args...) - + # Internal: markBufferPosition: (args...) -> @displayBuffer.markBufferPosition(args...) - + # Internal: destroyMarker: (args...) -> @displayBuffer.destroyMarker(args...) @@ -792,83 +792,83 @@ class EditSession # Returns a {Number}. getMarkerCount: -> @buffer.getMarkerCount() - + # Internal: getMarkerScreenRange: (args...) -> @displayBuffer.getMarkerScreenRange(args...) - + # Internal: setMarkerScreenRange: (args...) -> @displayBuffer.setMarkerScreenRange(args...) - + # Internal: getMarkerBufferRange: (args...) -> @displayBuffer.getMarkerBufferRange(args...) - + # Internal: setMarkerBufferRange: (args...) -> @displayBuffer.setMarkerBufferRange(args...) - + # Internal: getMarkerScreenPosition: (args...) -> @displayBuffer.getMarkerScreenPosition(args...) - + # Internal: getMarkerBufferPosition: (args...) -> @displayBuffer.getMarkerBufferPosition(args...) - + # Internal: getMarkerHeadScreenPosition: (args...) -> @displayBuffer.getMarkerHeadScreenPosition(args...) - + # Internal: setMarkerHeadScreenPosition: (args...) -> @displayBuffer.setMarkerHeadScreenPosition(args...) - + # Internal: getMarkerHeadBufferPosition: (args...) -> @displayBuffer.getMarkerHeadBufferPosition(args...) - + # Internal: setMarkerHeadBufferPosition: (args...) -> @displayBuffer.setMarkerHeadBufferPosition(args...) - + # Internal: getMarkerTailScreenPosition: (args...) -> @displayBuffer.getMarkerTailScreenPosition(args...) - + # Internal: setMarkerTailScreenPosition: (args...) -> @displayBuffer.setMarkerTailScreenPosition(args...) - + # Internal: getMarkerTailBufferPosition: (args...) -> @displayBuffer.getMarkerTailBufferPosition(args...) - + # Internal: setMarkerTailBufferPosition: (args...) -> @displayBuffer.setMarkerTailBufferPosition(args...) - + # Internal: observeMarker: (args...) -> @displayBuffer.observeMarker(args...) - + # Internal: placeMarkerTail: (args...) -> @displayBuffer.placeMarkerTail(args...) - + # Internal: clearMarkerTail: (args...) -> @displayBuffer.clearMarkerTail(args...) - + # Internal: isMarkerReversed: (args...) -> @displayBuffer.isMarkerReversed(args...) - + # Internal: isMarkerRangeEmpty: (args...) -> @displayBuffer.isMarkerRangeEmpty(args...) - + # Public: Returns `true` if there are multiple cursors in the edit session. # # Returns a {Boolean}. @@ -1090,6 +1090,9 @@ class EditSession moveCursorToEndOfWord: -> @moveCursors (cursor) -> cursor.moveToEndOfWord() + moveCursorToBeginningOfNextWord: -> + @moveCursors (cursor) -> cursor.moveToBeginningOfNextWord() + moveCursors: (fn) -> fn(cursor) for cursor in @getCursors() @mergeCursors() @@ -1184,6 +1187,10 @@ class EditSession selectToEndOfWord: -> @expandSelectionsForward (selection) => selection.selectToEndOfWord() + # Public: Selects all the text from the current cursor position to the beginning of the next word. + selectToBeginningOfNextWord: -> + @expandSelectionsForward (selection) => selection.selectToBeginningOfNextWord() + # Public: Selects the current word. selectWord: -> @expandSelectionsForward (selection) => selection.selectWord() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index a62b40e82..663144eea 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -130,10 +130,12 @@ class Editor extends View 'editor:move-to-first-character-of-line': @moveCursorToFirstCharacterOfLine 'editor:move-to-beginning-of-word': @moveCursorToBeginningOfWord 'editor:move-to-end-of-word': @moveCursorToEndOfWord + 'editor:move-to-beginning-of-next-word': @moveCursorToBeginningOfNextWord 'editor:select-to-end-of-line': @selectToEndOfLine 'editor:select-to-beginning-of-line': @selectToBeginningOfLine 'editor:select-to-end-of-word': @selectToEndOfWord 'editor:select-to-beginning-of-word': @selectToBeginningOfWord + 'editor:select-to-beginning-of-next-word': @selectToBeginningOfNextWord 'editor:add-selection-below': @addSelectionBelow 'editor:add-selection-above': @addSelectionAbove 'editor:select-line': @selectLine @@ -211,7 +213,8 @@ class Editor extends View moveCursorToBeginningOfWord: -> @activeEditSession.moveCursorToBeginningOfWord() # Public: Moves every cursor to the end of the current word. moveCursorToEndOfWord: -> @activeEditSession.moveCursorToEndOfWord() - # Public: Moves every cursor to the top of the buffer. + # Public: Moves the cursor to the beginning of the next word. + moveCursorToBeginningOfNextWord: -> @activeEditSession.moveCursorToBeginningOfNextWord() moveCursorToTop: -> @activeEditSession.moveCursorToTop() # Public: Moves every cursor to the bottom of the buffer. moveCursorToBottom: -> @activeEditSession.moveCursorToBottom() @@ -233,6 +236,7 @@ class Editor extends View setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options) # Public: Duplicates the current line. duplicateLine: -> @activeEditSession.duplicateLine() + joinLine: -> @activeEditSession.joinLine() getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition() # Public: Gets the current screen row. # @@ -292,7 +296,8 @@ class Editor extends View selectToBeginningOfWord: -> @activeEditSession.selectToBeginningOfWord() # Public: Selects all the text from the current cursor position to the end of the word. selectToEndOfWord: -> @activeEditSession.selectToEndOfWord() - # Public: Selects the current word. + # Public: Selects all the text from the current cursor position to the beginning of the next word. + selectToBeginningOfNextWord: -> @activeEditSession.selectToBeginningOfNextWord() selectWord: -> @activeEditSession.selectWord() selectLine: -> @activeEditSession.selectLine() selectToScreenPosition: (position) -> @activeEditSession.selectToScreenPosition(position) @@ -342,7 +347,7 @@ class Editor extends View # options - A set of options equivalent to {Selection.indent}. indent: (options) -> @activeEditSession.indent(options) # Public: TODO - autoIndent: -> @activeEditSession.autoIndentSelectedRows() + autoIndent: (options) -> @activeEditSession.autoIndentSelectedRows() # Public: Indents the selected rows. indentSelectedRows: -> @activeEditSession.indentSelectedRows() # Public: Outdents the selected rows. diff --git a/src/app/git.coffee b/src/app/git.coffee index 9bbe63117..b01cf826b 100644 --- a/src/app/git.coffee +++ b/src/app/git.coffee @@ -95,11 +95,7 @@ class Git @isStatusNew(@getPathStatus(path)) relativize: (path) -> - workingDirectory = @getWorkingDirectory() - if workingDirectory and path.indexOf("#{workingDirectory}/") is 0 - path.substring(workingDirectory.length + 1) - else - path + @getRepo().relativize(path) getShortHead: -> @getRepo().getShortHead() diff --git a/src/app/selection.coffee b/src/app/selection.coffee index 03bf8aca4..c3f91eb69 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -152,6 +152,9 @@ class Selection selectToEndOfWord: -> @modifySelection => @cursor.moveToEndOfWord() + selectToBeginningOfNextWord: -> + @modifySelection => @cursor.moveToBeginningOfNextWord() + addSelectionBelow: -> range = (@goalBufferRange ? @getBufferRange()).copy() nextRow = range.end.row + 1 diff --git a/src/app/window.coffee b/src/app/window.coffee index d78f54caa..490351c70 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -25,12 +25,6 @@ window.setUpEnvironment = -> $(document).on 'keydown', keymap.handleKeyEvent keymap.bindDefaultKeys() - ignoreEvents = (e) -> - e.preventDefault() - e.stopPropagation() - $(document).on 'dragover', ignoreEvents - $(document).on 'drop', ignoreEvents - requireStylesheet 'reset' requireStylesheet 'atom' requireStylesheet 'overlay' @@ -50,6 +44,7 @@ window.startup = -> console.warn "Failed to install `atom` binary" handleWindowEvents() + handleDragDrop() config.load() keymap.loadBundledKeymaps() atom.loadThemes() @@ -93,6 +88,18 @@ window.handleWindowEvents = -> $(window).command 'window:close', => confirmClose() $(window).command 'window:reload', => reload() +window.handleDragDrop = -> + $(document).on 'dragover', (e) -> + e.preventDefault() + e.stopPropagation() + $(document).on 'drop', onDrop + +window.onDrop = (e) -> + e.preventDefault() + e.stopPropagation() + for file in e.originalEvent.dataTransfer.files + atom.open(file.path) + window.deserializeWindowState = -> RootView = require 'root-view' Project = require 'project' diff --git a/src/packages/autocomplete/lib/autocomplete-view.coffee b/src/packages/autocomplete/lib/autocomplete-view.coffee index 8da239fc2..e559e7a97 100644 --- a/src/packages/autocomplete/lib/autocomplete-view.coffee +++ b/src/packages/autocomplete/lib/autocomplete-view.coffee @@ -27,6 +27,8 @@ class AutocompleteView extends SelectList handleEvents: -> @editor.on 'editor:path-changed', => @setCurrentBuffer(@editor.getBuffer()) @editor.command 'autocomplete:attach', => @attach() + @editor.command 'autocomplete:next', => @selectNextItem() + @editor.command 'autocomplete:previous', => @selectPreviousItem() @miniEditor.preempt 'textInput', (e) => text = e.originalEvent.data diff --git a/src/packages/markdown-preview/lib/markdown-preview.coffee b/src/packages/markdown-preview/lib/markdown-preview.coffee index 1b46eccb5..8011339b9 100644 --- a/src/packages/markdown-preview/lib/markdown-preview.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview.coffee @@ -15,12 +15,19 @@ module.exports = console.warn("Can not render markdown for '#{editSession.getUri() ? 'untitled'}'") return - if nextPane = activePane.getNextPane() - if preview = nextPane.itemForUri("markdown-preview:#{editSession.getPath()}") - nextPane.showItem(preview) - preview.fetchRenderedMarkdown() - else - nextPane.showItem(new MarkdownPreviewView(editSession.buffer)) + {previewPane, previewItem} = @getExistingPreview(editSession) + if previewItem? + previewPane.showItem(previewItem) + previewItem.fetchRenderedMarkdown() + else if nextPane = activePane.getNextPane() + nextPane.showItem(new MarkdownPreviewView(editSession.buffer)) else activePane.splitRight(new MarkdownPreviewView(editSession.buffer)) activePane.focus() + + getExistingPreview: (editSession) -> + uri = "markdown-preview:#{editSession.getPath()}" + for previewPane in rootView.getPanes() + previewItem = previewPane.itemForUri(uri) + return {previewPane, previewItem} if previewItem? + {} diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index ea8abd24b..be0a0c0af 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -77,11 +77,8 @@ class TabBarView extends View (@paneContainer.getPanes().length > 1) or (@pane.getItems().length > 1) onDragStart: (event) => - unless @shouldAllowDrag(event) - event.preventDefault() - return - - event.originalEvent.dataTransfer.setData 'atom-event', 'true' + if @shouldAllowDrag() + event.originalEvent.dataTransfer.setData 'atom-event', 'true' el = $(event.target).closest('.sortable') el.addClass 'is-dragging' @@ -91,8 +88,15 @@ class TabBarView extends View paneIndex = @paneContainer.indexOfPane(pane) event.originalEvent.dataTransfer.setData 'from-pane-index', paneIndex + item = @pane.getItems()[el.index()] + if item.getPath? + event.originalEvent.dataTransfer.setData 'text/uri-list', 'file://' + item.getPath() + event.originalEvent.dataTransfer.setData 'text/plain', item.getPath() + onDragEnd: (event) => @find(".is-dragging").removeClass 'is-dragging' + @children('.is-drop-target').removeClass 'is-drop-target' + @children('.drop-target-is-after').removeClass 'drop-target-is-after' onDragOver: (event) => unless event.originalEvent.dataTransfer.getData('atom-event') is 'true' @@ -120,8 +124,6 @@ class TabBarView extends View return event.stopPropagation() - @children('.is-drop-target').removeClass 'is-drop-target' - @children('.drop-target-is-after').removeClass 'drop-target-is-after' dataTransfer = event.originalEvent.dataTransfer fromIndex = parseInt(dataTransfer.getData('sortable-index')) diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index b5b733c17..a8222d6be 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -279,8 +279,8 @@ describe "TabBarView", -> expect(pane2.activeItem).toBe item1 expect(pane2.focus).toHaveBeenCalled() - describe 'when a non-tab is dragged to pane', -> - it 'has no effect', -> + describe "when a non-tab is dragged to pane", -> + it "has no effect", -> expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"] expect(pane.getItems()).toEqual [item1, editSession1, item2] expect(pane.activeItem).toBe item2 @@ -294,3 +294,11 @@ describe "TabBarView", -> expect(pane.activeItem).toBe item2 expect(pane.focus).not.toHaveBeenCalled() + describe "when a tab is dragged out of application", -> + it "should carry file's information", -> + [dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(1), tabBar.tabAtIndex(1)) + tabBar.onDragStart(dragStartEvent) + + expect(dragStartEvent.originalEvent.dataTransfer.getData("text/plain")).toEqual editSession1.getPath() + expect(dragStartEvent.originalEvent.dataTransfer.getData("text/uri-list")).toEqual 'file://' + editSession1.getPath() +