From ceaf19ef18ffcaf59a42a5267e16044d6d129b6f Mon Sep 17 00:00:00 2001 From: Pieter Goetschalckx <3.14.e.ter@gmail.com> Date: Thu, 11 Feb 2016 01:49:44 +0100 Subject: [PATCH 1/4] Enable expansion of nested snippets --- lib/snippet-expansion.coffee | 11 +++++++++-- lib/snippets.coffee | 36 +++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/snippet-expansion.coffee b/lib/snippet-expansion.coffee index 9524a42..fb0b57e 100644 --- a/lib/snippet-expansion.coffee +++ b/lib/snippet-expansion.coffee @@ -53,7 +53,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 @@ -84,7 +91,7 @@ class SnippetExpansion for markers in @tabStopMarkers marker.destroy() for marker in markers @tabStopMarkers = [] - @snippets.clearExpansions(@editor) + @snippets.clearCurrentExpansions(@editor) restore: (@editor) -> @snippets.addExpansion(@editor, this) diff --git a/lib/snippets.coffee b/lib/snippets.coffee index 2f73700..d2ff39d 100644 --- a/lib/snippets.coffee +++ b/lib/snippets.coffee @@ -32,7 +32,7 @@ module.exports = 'snippets:expand': (event) -> editor = @getModel() if snippets.snippetToExpandUnderCursor(editor) - snippets.clearExpansions(editor) + snippets.newCurrentExpansions(editor) snippets.expandSnippetsUnderCursors(editor) else event.abortKeyBinding() @@ -306,27 +306,45 @@ module.exports = goToNextTabStop: (editor) -> nextTabStopVisited = false - for expansion in @getExpansions(editor) - if expansion?.goToNextTabStop() - nextTabStopVisited = true + while currentExpansions = @getCurrentExpansions(editor) + @clearCurrentExpansions(editor) unless currentExpansions.length + for expansion in currentExpansions + if expansion?.goToNextTabStop() + nextTabStopVisited = true + break if nextTabStopVisited nextTabStopVisited goToPreviousTabStop: (editor) -> previousTabStopVisited = false - for expansion in @getExpansions(editor) - if expansion?.goToPreviousTabStop() - previousTabStopVisited = true + while currentExpansions = @getCurrentExpansions(editor) + @clearCurrentExpansions(editor) unless currentExpansions.length + for expansion in currentExpansions + if expansion?.goToPreviousTabStop() + previousTabStopVisited = true + break if previousTabStopVisited previousTabStopVisited getExpansions: (editor) -> - @editorSnippetExpansions?.get(editor) ? [] + @editorSnippetExpansions.get(editor) + + getCurrentExpansions: (editor) -> + expansions = @getExpansions(editor) + return expansions[expansions.length - 1] if expansions.length clearExpansions: (editor) -> @editorSnippetExpansions ?= new WeakMap() @editorSnippetExpansions.set(editor, []) + clearCurrentExpansions: (editor) -> + expansions = @getExpansions(editor) + expansions.pop() if expansions.length + addExpansion: (editor, snippetExpansion) -> - @getExpansions(editor).push(snippetExpansion) + if not @getCurrentExpansions(editor)?.push(snippetExpansion) + @getExpansions(editor).push([snippetExpansion]) + + newCurrentExpansions: (editor) -> + @getExpansions(editor).push([]) insert: (snippet, editor=atom.workspace.getActiveTextEditor(), cursor=editor.getLastCursor()) -> if typeof snippet is 'string' From 99efea2c6b4bc4689be4a2f702c390fdf2a1c84c Mon Sep 17 00:00:00 2001 From: Pieter Goetschalckx <3.14.e.ter@gmail.com> Date: Thu, 11 Feb 2016 01:50:29 +0100 Subject: [PATCH 2/4] Remove failing spec shift-tab on first tab-stop should destroy the expansion, and if possible move to the previous tab-stop of a surrounding expansion --- spec/snippets-spec.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/snippets-spec.coffee b/spec/snippets-spec.coffee index 1a7c4d8..b6be8ad 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() From d9b14e5788c210bdab6320d552ccdbf9e49544b8 Mon Sep 17 00:00:00 2001 From: Pieter Goetschalckx <3.14.e.ter@gmail.com> Date: Thu, 11 Feb 2016 04:34:23 +0100 Subject: [PATCH 3/4] Add specs for nested snippets --- spec/snippets-spec.coffee | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spec/snippets-spec.coffee b/spec/snippets-spec.coffee index b6be8ad..f3e7270 100644 --- a/spec/snippets-spec.coffee +++ b/spec/snippets-spec.coffee @@ -651,6 +651,43 @@ 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.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:( )" + describe "when atom://.atom/snippets is opened", -> it "opens ~/.atom/snippets.cson", -> jasmine.unspy(Snippets, 'getUserSnippetsPath') From b9ca65885d0ee918c6b40983afa1b3b678f96059 Mon Sep 17 00:00:00 2001 From: Pieter Goetschalckx <3.14.e.ter@gmail.com> Date: Thu, 11 Feb 2016 20:12:00 +0100 Subject: [PATCH 4/4] Rewrite to make things clearer Works with multiple cursors and multi-snippets now. --- lib/snippet-expansion.coffee | 9 ++--- lib/snippets.coffee | 72 ++++++++++++++++-------------------- spec/snippets-spec.coffee | 27 +++++++++++++- 3 files changed, 60 insertions(+), 48 deletions(-) diff --git a/lib/snippet-expansion.coffee b/lib/snippet-expansion.coffee index fb0b57e..78137d1 100644 --- a/lib/snippet-expansion.coffee +++ b/lib/snippet-expansion.coffee @@ -5,7 +5,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] @@ -19,7 +19,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 @@ -91,7 +91,4 @@ class SnippetExpansion for markers in @tabStopMarkers marker.destroy() for marker in markers @tabStopMarkers = [] - @snippets.clearCurrentExpansions(@editor) - - restore: (@editor) -> - @snippets.addExpansion(@editor, this) + @group.expansions = [] diff --git a/lib/snippets.coffee b/lib/snippets.coffee index d2ff39d..b12daad 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.newCurrentExpansions(editor) - snippets.expandSnippetsUnderCursors(editor) - else - event.abortKeyBinding() + event.abortKeyBinding() unless snippets.expandSnippetsUnderCursors(editor) 'snippets:next-tab-stop': (event) -> editor = @getModel() @@ -297,60 +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 - while currentExpansions = @getCurrentExpansions(editor) - @clearCurrentExpansions(editor) unless currentExpansions.length - for expansion in currentExpansions - if expansion?.goToNextTabStop() - nextTabStopVisited = true - break if nextTabStopVisited - 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 - while currentExpansions = @getCurrentExpansions(editor) - @clearCurrentExpansions(editor) unless currentExpansions.length - for expansion in currentExpansions - if expansion?.goToPreviousTabStop() - previousTabStopVisited = true - break if previousTabStopVisited - previousTabStopVisited - - getExpansions: (editor) -> - @editorSnippetExpansions.get(editor) - - getCurrentExpansions: (editor) -> - expansions = @getExpansions(editor) - return expansions[expansions.length - 1] if expansions.length + 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) - clearCurrentExpansions: (editor) -> - expansions = @getExpansions(editor) - expansions.pop() if expansions.length + getExpansionGroup: (editor) -> + @editorSnippetExpansions.get(editor) - addExpansion: (editor, snippetExpansion) -> - if not @getCurrentExpansions(editor)?.push(snippetExpansion) - @getExpansions(editor).push([snippetExpansion]) + nextExpansionGroup: (editor) -> + group = @editorSnippetExpansions.get(editor)?.parent + @editorSnippetExpansions.set(editor, group) + group - newCurrentExpansions: (editor) -> - @getExpansions(editor).push([]) + 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 f3e7270..2b3bc89 100644 --- a/spec/snippets-spec.coffee +++ b/spec/snippets-spec.coffee @@ -668,7 +668,8 @@ describe "Snippets extension", -> 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(2)).toBe "go here first:()" + expect(editor.lineTextForBufferRow(3)).toBe ")" expect(editor.getSelectedBufferRange()).toEqual [[2, 15], [2, 15]] simulateTabKeyEvent() @@ -686,7 +687,29 @@ describe "Snippets extension", -> simulateTabKeyEvent() simulateTabKeyEvent() - expect(editor.lineTextForBufferRow(0)).toBe "go here next:() and finally go here:( )" + 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", ->