diff --git a/lib/snippet-expansion.coffee b/lib/snippet-expansion.coffee index b9a89e8..b4d2f40 100644 --- a/lib/snippet-expansion.coffee +++ b/lib/snippet-expansion.coffee @@ -4,7 +4,7 @@ module.exports = class SnippetExpansion settingTabStop: false - constructor: (@snippet, @editor, @cursor, @snippets) -> + constructor: (@snippet, @editor, @cursor, @snippets, @group) -> @subscriptions = new CompositeDisposable @tabStopMarkers = [] @selections = [@cursor.selection] @@ -18,7 +18,7 @@ class SnippetExpansion @subscriptions.add @cursor.onDidChangePosition (event) => @cursorMoved(event) @subscriptions.add @cursor.onDidDestroy => @cursorDestroyed() @placeTabStopMarkers(startPosition, @snippet.tabStops) - @snippets.addExpansion(@editor, this) + @group.expansions.push(this) @editor.normalizeTabsInBufferRange(newRange) @indentSubsequentLines(startPosition.row, @snippet) if @snippet.lineCount > 1 @@ -52,7 +52,14 @@ class SnippetExpansion false goToPreviousTabStop: -> - @setTabStopIndex(@tabStopIndex - 1) if @tabStopIndex > 0 + if @tabStopIndex > 0 + if @setTabStopIndex(@tabStopIndex - 1) + true + else + @goToPreviousTabStop() + else + @destroy() + false setTabStopIndex: (@tabStopIndex) -> @settingTabStop = true @@ -83,7 +90,4 @@ class SnippetExpansion for markers in @tabStopMarkers marker.destroy() for marker in markers @tabStopMarkers = [] - @snippets.clearExpansions(@editor) - - restore: (@editor) -> - @snippets.addExpansion(@editor, this) + @group.expansions = [] diff --git a/lib/snippets.coffee b/lib/snippets.coffee index 90402df..3c5280f 100644 --- a/lib/snippets.coffee +++ b/lib/snippets.coffee @@ -31,11 +31,7 @@ module.exports = @subscriptions.add atom.commands.add 'atom-text-editor', 'snippets:expand': (event) -> editor = @getModel() - if snippets.snippetToExpandUnderCursor(editor) - snippets.clearExpansions(editor) - snippets.expandSnippetsUnderCursors(editor) - else - event.abortKeyBinding() + event.abortKeyBinding() unless snippets.expandSnippetsUnderCursors(editor) 'snippets:next-tab-stop': (event) -> editor = @getModel() @@ -297,42 +293,56 @@ module.exports = editor.transact => cursors = editor.getCursors() + group = @newExpansionGroup(editor) for cursor in cursors cursorPosition = cursor.getBufferPosition() startPoint = cursorPosition.translate([0, -snippet.prefix.length], [0, 0]) cursor.selection.setBufferRange([startPoint, cursorPosition]) - @insert(snippet, editor, cursor) + @insert(snippet, editor, cursor, group) true goToNextTabStop: (editor) -> - nextTabStopVisited = false - for expansion in @getExpansions(editor) - if expansion?.goToNextTabStop() - nextTabStopVisited = true - nextTabStopVisited + group = @getExpansionGroup(editor) + while group + for expansion in group.expansions + expansion.goToNextTabStop() + return true if group.expansions.length + group = @nextExpansionGroup(editor) + false goToPreviousTabStop: (editor) -> - previousTabStopVisited = false - for expansion in @getExpansions(editor) - if expansion?.goToPreviousTabStop() - previousTabStopVisited = true - previousTabStopVisited - - getExpansions: (editor) -> - @editorSnippetExpansions?.get(editor) ? [] + group = @getExpansionGroup(editor) + while group + for expansion in group.expansions + expansion.goToPreviousTabStop() + return true if group.expansions.length + group = @nextExpansionGroup(editor) + false clearExpansions: (editor) -> @editorSnippetExpansions ?= new WeakMap() - @editorSnippetExpansions.set(editor, []) + @editorSnippetExpansions.set(editor, null) + + getExpansionGroup: (editor) -> + @editorSnippetExpansions.get(editor) + + nextExpansionGroup: (editor) -> + group = @editorSnippetExpansions.get(editor)?.parent + @editorSnippetExpansions.set(editor, group) + group - addExpansion: (editor, snippetExpansion) -> - @getExpansions(editor).push(snippetExpansion) + newExpansionGroup: (editor) -> + group = + parent: @editorSnippetExpansions.get(editor) + expansions: [] + @editorSnippetExpansions.set(editor, group) + group - insert: (snippet, editor=atom.workspace.getActiveTextEditor(), cursor=editor.getLastCursor()) -> + insert: (snippet, editor=atom.workspace.getActiveTextEditor(), cursor=editor.getLastCursor(), group=@newExpansionGroup(editor)) -> if typeof snippet is 'string' bodyTree = @getBodyParser().parse(snippet) snippet = new Snippet({name: '__anonymous', prefix: '', bodyTree, bodyText: snippet}) - new SnippetExpansion(snippet, editor, cursor, this) + new SnippetExpansion(snippet, editor, cursor, this, group) getUnparsedSnippets: -> _.deepClone(@scopedPropertyStore.propertySets) diff --git a/spec/snippets-spec.coffee b/spec/snippets-spec.coffee index 5dfce40..cf727d6 100644 --- a/spec/snippets-spec.coffee +++ b/spec/snippets-spec.coffee @@ -267,10 +267,6 @@ describe "Snippets extension", -> simulateTabKeyEvent(shift: true) expect(editor.getSelectedBufferRange()).toEqual [[3, 15], [3, 15]] - # shift-tab on first tab-stop does nothing - simulateTabKeyEvent(shift: true) - expect(editor.getCursorScreenPosition()).toEqual [3, 15] - # tab through all tab stops, then tab on last stop to terminate snippet simulateTabKeyEvent() simulateTabKeyEvent() @@ -654,6 +650,66 @@ describe "Snippets extension", -> expect(editor.lineTextForBufferRow(7)).toBe " }one t1 three" expect(editor.lineTextForBufferRow(12)).toBe "};one t1 three" + describe "when there are nested snippets", -> + it "tries to expand before moving to the next tabstop", -> + spyOn(Snippets, 'snippetToExpandUnderCursor').andReturn(false) + spyOn(Snippets, 'goToNextTabStop').andReturn(true) + + simulateTabKeyEvent() + expect(Snippets.snippetToExpandUnderCursor).toHaveBeenCalled() + expect(Snippets.goToNextTabStop).toHaveBeenCalled() + + it "moves to surrounding expansions after terminating a snippet", -> + editor.setText 't2' + editor.setCursorScreenPosition [0, 2] + simulateTabKeyEvent() + editor.insertText 't2' + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe "go here next:() and finally go here:()" + expect(editor.lineTextForBufferRow(1)).toBe "go here first:(go here next:() and finally go here:()" + expect(editor.lineTextForBufferRow(2)).toBe "go here first:()" + expect(editor.lineTextForBufferRow(3)).toBe ")" + expect(editor.getSelectedBufferRange()).toEqual [[2, 15], [2, 15]] + + simulateTabKeyEvent() + simulateTabKeyEvent() + editor.insertText 't5' + simulateTabKeyEvent() + simulateTabKeyEvent(shift: true) + expect(editor.lineTextForBufferRow(1)).toBe 'go here first:(go here next:() and finally go here:("key": value)' + expect(editor.getSelectedBufferRange()).toEqual [[1, 29], [1, 29]] + + simulateTabKeyEvent() + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe "go here next:() and finally go here:()" + expect(editor.getSelectedBufferRange()).toEqual [[0, 14], [0, 14]] + + simulateTabKeyEvent() + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe "go here next:() and finally go here:( )" + + it "handles multi-tabstops", -> + editor.setText 't9b' + editor.setCursorScreenPosition [0, 3] + simulateTabKeyEvent() + editor.insertText 't9b' + simulateTabKeyEvent() + editor.insertText 't9b' + simulateTabKeyEvent() + for i in [1..7] + editor.insertText "#{i}" + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe "with placeholder with placeholder with placeholder 1" + expect(editor.lineTextForBufferRow(10)).toBe "without placeholder with placeholder with placeholder 1" + expect(editor.lineTextForBufferRow(i)).toBe "without placeholder with placeholder 1" for i in [4, 14] + expect(editor.lineTextForBufferRow(i)).toBe "without placeholder 1" for i in [1, 5, 11, 15] + expect(editor.lineTextForBufferRow(i)).toBe "second tabstop 2" for i in [2, 6, 12, 16] + expect(editor.lineTextForBufferRow(i)).toBe "third tabstop 3" for i in [3, 7, 13, 17] + expect(editor.lineTextForBufferRow(i)).toBe "second tabstop 4" for i in [8, 18] + expect(editor.lineTextForBufferRow(i)).toBe "third tabstop 5" for i in [9, 19] + expect(editor.lineTextForBufferRow(20)).toBe "second tabstop 6" + expect(editor.lineTextForBufferRow(21)).toBe "third tabstop 7 " + describe "when atom://.atom/snippets is opened", -> it "opens ~/.atom/snippets.cson", -> jasmine.unspy(Snippets, 'getUserSnippetsPath')