Skip to content

Commit ea36664

Browse files
authored
Merge pull request #6 from viablelab/feature/auto-close-fix
Feature/auto close fix
2 parents cccc42a + 3b0acdf commit ea36664

File tree

2 files changed

+122
-53
lines changed

2 files changed

+122
-53
lines changed

lib/bracket-padder.coffee

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
2-
adviseBefore, contains, findLastIndex,
3-
first, has, invert, keys,
2+
adviseBefore, first, invert,
3+
compose, last, filter,
44
} = require 'underscore-plus'
55

66
pairsToPad =
77
'(': ')'
88
'[': ']'
99
'{': '}'
1010

11-
invertedPairsToPad = invert pairsToPad
11+
invertedPairsToPad =
12+
invert pairsToPad
1213

1314
pairsToUnpad =
1415
'( ': ' )'
@@ -22,6 +23,9 @@ defaultPairs = [
2223
'"', "'", '`',
2324
]
2425

26+
removeEscapedQuotes =
27+
(str) -> str.replace(/(\\"|\\')/g, '')
28+
2529
module.exports =
2630
class BracketPadder
2731
constructor: (@editor) ->
@@ -32,15 +36,17 @@ class BracketPadder
3236
return true unless text
3337
return true if options?.select or options?.undo is 'skip'
3438

35-
closingBracket = invertedPairsToPad[text]
36-
return true unless text is ' ' or closingBracket
39+
openBracket = invertedPairsToPad[text]
40+
isClosingBracket = !!openBracket
41+
42+
return true unless text is ' ' or isClosingBracket
3743

3844
if @shouldPad(text)
3945
@editor.insertText(' ')
4046
@editor.moveLeft()
4147
return false
4248

43-
if @shouldClosePair(closingBracket)
49+
if @shouldClosePair(text, openBracket)
4450
@editor.moveRight(2)
4551
return false
4652

@@ -51,9 +57,7 @@ class BracketPadder
5157
previousCharacters = @getPreviousCharacters(2, cursor)
5258
nextCharacters = @getNextCharacters(2, cursor)
5359

54-
match = pairsToUnpad[previousCharacters]
55-
56-
return true unless match and nextCharacters is match
60+
return true unless pairsToUnpad[previousCharacters] is nextCharacters
5761

5862
@editor.moveRight()
5963
@editor.backspace()
@@ -67,25 +71,24 @@ class BracketPadder
6771
previousCharacter = @getPreviousCharacters 1, cursor
6872
nextCharacter = @getNextCharacters 1, cursor
6973

70-
match = pairsToPad[previousCharacter]
71-
return match and nextCharacter is match
74+
return true if pairsToPad[previousCharacter] is nextCharacter
7275

73-
shouldClosePair: (character) =>
76+
shouldClosePair: (closeBracket, openBracket) =>
7477
cursor = @editor.getCursorBufferPosition()
75-
previousCharacters = @getPreviousCharacters(cursor.column, cursor)
78+
previousCharacters =
79+
removeEscapedQuotes @getPreviousCharacters(cursor.column, cursor)
80+
nextCharacters = @getNextCharacters(2, cursor)
7681

77-
match = findLastOccurringCharacter defaultPairs, previousCharacters
78-
return false unless match and match is character
82+
return false unless previousCharacters.includes(openBracket)
7983

80-
nextCharacters = @getNextCharacters(2, cursor)
84+
unclosed = getUnclosedPairs(previousCharacters)
85+
return false unless last(unclosed) is openBracket
8186

8287
return false unless first(nextCharacters) is ' '
83-
84-
match = invert(pairsToPad)[nextCharacters.trim()]
85-
return match and match is character
88+
return true if nextCharacters.trim() is closeBracket
8689

8790
getPreviousCharacters: (count, cursor) =>
88-
return -1 unless cursor.column
91+
return '' unless cursor.column
8992

9093
return @editor.getTextInBufferRange([
9194
cursor.traverse([0, -count]),
@@ -99,16 +102,35 @@ class BracketPadder
99102
])
100103

101104
###
102-
* @param {Array<String>} characters
103-
* @param {String} string
105+
* Filters out characters between `opening` and `closing` characters.
106+
* @param {String} opening
107+
* @param {String} closing
108+
* @return {Function}
104109
###
105-
findLastOccurringCharacter = (characters, string) ->
106-
index = string.length - 1
110+
removePairs = (opening, closing) -> (str) ->
111+
if not closing
112+
closing = opening
107113

108-
while index--
109-
char = string[index]
114+
regex = new RegExp("#{opening}([^#{opening}]+(?=#{closing}))#{closing}", 'g')
115+
str.replace(regex, '')
110116

111-
if contains(characters, char)
112-
return char
117+
###
118+
* :: String -> String
119+
###
120+
removeClosedPairs = compose(
121+
removePairs("'"),
122+
removePairs('"'),
123+
removePairs('`'),
124+
removePairs('\\{', '\\}'),
125+
removePairs('\\[', '\\]'),
126+
removePairs('\\(', '\\)')
127+
)
113128

114-
return undefined
129+
###
130+
* Returns any unclosed "bracket pair characters" found within `str`.
131+
* @param {String} str
132+
* @return {Array<String>}
133+
###
134+
getUnclosedPairs = (str) ->
135+
trimmed = removeClosedPairs(str)
136+
return filter(trimmed, (char) -> defaultPairs.includes(char))

spec/bracket-padder-spec.coffee

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,27 +80,74 @@ describe "bracket padding", ->
8080

8181
expect(test).not.toThrow()
8282

83-
# it 'autocloses padded (), [], {} pairs', ->
84-
# testPair = (opening, closing) ->
85-
# editor.selectAll()
86-
# editor.delete()
87-
#
88-
# editor.insertText(opening)
89-
# editor.insertText(' ')
90-
# editor
91-
#
92-
# editor.insertText("#{opening} #{closing}")
93-
# editor.setCursorBufferPosition([0, 2])
94-
# editor.insertText(closing)
95-
#
96-
# actualText = editor.buffer.getText()
97-
# expectedText = "#{opening} #{closing}"
98-
# expect(actualText).toBe expectedText
99-
#
100-
# actualCursorPosition = editor.getCursorBufferPosition().column
101-
# expectedCursorPosition = 4
102-
# expect(actualCursorPosition).toBe expectedCursorPosition
103-
#
104-
# testPair '(', ')'
105-
# testPair '[', ']'
106-
# testPair '{', '}'
83+
it 'autocloses padded (), [], {} pairs properly', ->
84+
reset = ->
85+
editor.selectAll()
86+
editor.delete()
87+
88+
testPair = (opening, closing) ->
89+
reset()
90+
91+
testStrings = [
92+
'foo: bar'
93+
'"foo": "bar"'
94+
"'foo': 'bar'"
95+
'`foo`: `bar`'
96+
'[foo]: [bar]'
97+
'{foo}: {bar}'
98+
'(foo): (bar)'
99+
]
100+
101+
testStrings.forEach (str) ->
102+
reset()
103+
104+
text = "#{opening} #{str} #{closing}"
105+
editor.insertText(text)
106+
107+
editor.moveToEndOfLine()
108+
editor.moveLeft(2)
109+
110+
editor.insertText(closing)
111+
112+
expected = text
113+
actual = editor.buffer.getText()
114+
115+
expect(actual).toBe(expected)
116+
117+
testPair '(', ')'
118+
testPair '[', ']'
119+
testPair '{', '}'
120+
121+
it 'skips autoclosing when cursor is within unclosed quotes', ->
122+
reset = ->
123+
editor.selectAll()
124+
editor.delete()
125+
126+
testPair = (opening, closing) ->
127+
reset()
128+
129+
testStrings = [
130+
'"foo": "bar'
131+
"'foo: 'bar'"
132+
'`foo`: `bar'
133+
]
134+
135+
testStrings.forEach (str) ->
136+
reset()
137+
138+
text = "#{opening} #{str} #{closing}"
139+
editor.insertText(text)
140+
141+
editor.moveToEndOfLine()
142+
editor.moveLeft(2)
143+
144+
editor.insertText(closing)
145+
146+
expected = "#{opening} #{str}#{closing} #{closing}"
147+
actual = editor.buffer.getText()
148+
149+
expect(actual).toBe(expected)
150+
151+
testPair '(', ')'
152+
testPair '[', ']'
153+
testPair '{', '}'

0 commit comments

Comments
 (0)