Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Expansion of nested snippets #192

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions lib/snippet-expansion.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = []
58 changes: 34 additions & 24 deletions lib/snippets.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
64 changes: 60 additions & 4 deletions spec/snippets-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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", ->
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test fails. The keybindings are resolved in the wrong order, but only in tests, it works in real life. This is the reason that the next test fails too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused about the current intended behaviour when a snippet is triggered within an existing expansion. According to the specs, it should go to the next tabstop without expanding, but according to the keymaps (snippets-1.cson and snippets-2.cson), it should try to expand first. When I try it myself, it expands most of the times, but not always.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to attempt to expand first if possible.

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')
Expand Down