mirror of
https://github.com/atom/atom.git
synced 2026-01-25 06:48:28 -05:00
Make leading spaces atomic (length based on tabLength)
This commit is contained in:
@@ -110,12 +110,12 @@ describe "EditSession", ->
|
||||
lastLine = buffer.lineForRow(lastLineIndex)
|
||||
expect(lastLine.length).toBeGreaterThan(0)
|
||||
|
||||
editSession.setCursorScreenPosition(row: lastLineIndex, column: 1)
|
||||
editSession.setCursorScreenPosition(row: lastLineIndex, column: editSession.tabLength)
|
||||
editSession.moveCursorDown()
|
||||
expect(editSession.getCursorScreenPosition()).toEqual(row: lastLineIndex, column: lastLine.length)
|
||||
|
||||
editSession.moveCursorUp()
|
||||
expect(editSession.getCursorScreenPosition().column).toBe 1
|
||||
expect(editSession.getCursorScreenPosition().column).toBe editSession.tabLength
|
||||
|
||||
it "retains a goal column of 0 when moving back up", ->
|
||||
lastLineIndex = buffer.getLines().length - 1
|
||||
@@ -138,9 +138,9 @@ describe "EditSession", ->
|
||||
|
||||
describe ".moveCursorLeft()", ->
|
||||
it "moves the cursor by one column to the left", ->
|
||||
editSession.setCursorScreenPosition([3, 3])
|
||||
editSession.setCursorScreenPosition([1, 8])
|
||||
editSession.moveCursorLeft()
|
||||
expect(editSession.getCursorScreenPosition()).toEqual [3, 2]
|
||||
expect(editSession.getCursorScreenPosition()).toEqual [1, 7]
|
||||
|
||||
describe "when the cursor is in the first column", ->
|
||||
describe "when there is a previous line", ->
|
||||
@@ -155,6 +155,13 @@ describe "EditSession", ->
|
||||
editSession.moveCursorLeft()
|
||||
expect(editSession.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
||||
|
||||
describe "when softTabs is enabled and the cursor is preceded by leading whitespace", ->
|
||||
it "skips tabLength worth of whitespace at a time", ->
|
||||
editSession.setCursorBufferPosition([5, 6])
|
||||
|
||||
editSession.moveCursorLeft()
|
||||
expect(editSession.getCursorBufferPosition()).toEqual [5, 4]
|
||||
|
||||
it "merges cursors when they overlap", ->
|
||||
editSession.setCursorScreenPosition([0, 0])
|
||||
editSession.addCursorAtScreenPosition([0, 1])
|
||||
@@ -354,14 +361,14 @@ describe "EditSession", ->
|
||||
describe ".selectToScreenPosition(screenPosition)", ->
|
||||
it "expands the last selection to the given position", ->
|
||||
editSession.setSelectedBufferRange([[3, 0], [4, 5]])
|
||||
editSession.addCursorAtScreenPosition([5, 5])
|
||||
editSession.selectToScreenPosition([6, 1])
|
||||
editSession.addCursorAtScreenPosition([5, 6])
|
||||
editSession.selectToScreenPosition([6, 2])
|
||||
|
||||
selections = editSession.getSelections()
|
||||
expect(selections.length).toBe 2
|
||||
[selection1, selection2] = selections
|
||||
expect(selection1.getScreenRange()).toEqual [[3, 0], [4, 5]]
|
||||
expect(selection2.getScreenRange()).toEqual [[5, 5], [6, 1]]
|
||||
expect(selection2.getScreenRange()).toEqual [[5, 6], [6, 2]]
|
||||
|
||||
it "merges selections if they intersect, maintaining the directionality of the last selection", ->
|
||||
editSession.setCursorScreenPosition([4, 10])
|
||||
@@ -1637,18 +1644,18 @@ describe "EditSession", ->
|
||||
|
||||
it "merges cursors when the change causes them to overlap", ->
|
||||
editSession.setCursorScreenPosition([0, 0])
|
||||
editSession.addCursorAtScreenPosition([0, 1])
|
||||
editSession.addCursorAtScreenPosition([1, 1])
|
||||
editSession.addCursorAtScreenPosition([0, 2])
|
||||
editSession.addCursorAtScreenPosition([1, 2])
|
||||
|
||||
[cursor1, cursor2, cursor3] = editSession.getCursors()
|
||||
expect(editSession.getCursors().length).toBe 3
|
||||
|
||||
buffer.delete([[0, 0], [0, 1]])
|
||||
buffer.delete([[0, 0], [0, 2]])
|
||||
|
||||
expect(editSession.getCursors().length).toBe 2
|
||||
expect(editSession.getCursors()).toEqual [cursor1, cursor3]
|
||||
expect(cursor1.getBufferPosition()).toEqual [0,0]
|
||||
expect(cursor3.getBufferPosition()).toEqual [1,1]
|
||||
expect(cursor3.getBufferPosition()).toEqual [1,2]
|
||||
|
||||
describe "folding", ->
|
||||
describe "structural folding", ->
|
||||
|
||||
@@ -486,13 +486,13 @@ describe "Editor", ->
|
||||
rootView.setFontSize(10)
|
||||
lineHeightBefore = editor.lineHeight
|
||||
charWidthBefore = editor.charWidth
|
||||
editor.setCursorScreenPosition [5, 5]
|
||||
editor.setCursorScreenPosition [5, 6]
|
||||
|
||||
rootView.setFontSize(30)
|
||||
expect(editor.css('font-size')).toBe '30px'
|
||||
expect(editor.lineHeight).toBeGreaterThan lineHeightBefore
|
||||
expect(editor.charWidth).toBeGreaterThan charWidthBefore
|
||||
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 5 * editor.charWidth }
|
||||
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth }
|
||||
|
||||
# ensure we clean up font size subscription
|
||||
editor.trigger('core:close')
|
||||
|
||||
@@ -77,7 +77,7 @@ describe "RootView", ->
|
||||
editor4 = editor2.splitDown()
|
||||
editor2.edit(rootView.project.buildEditSessionForPath('dir/b'))
|
||||
editor3.edit(rootView.project.buildEditSessionForPath('sample.js'))
|
||||
editor3.setCursorScreenPosition([2, 3])
|
||||
editor3.setCursorScreenPosition([2, 4])
|
||||
editor4.edit(rootView.project.buildEditSessionForPath('sample.txt'))
|
||||
editor4.setCursorScreenPosition([0, 2])
|
||||
rootView.attachToDom()
|
||||
@@ -98,7 +98,7 @@ describe "RootView", ->
|
||||
expect(editor1.getPath()).toBe require.resolve('fixtures/dir/a')
|
||||
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
|
||||
expect(editor3.getPath()).toBe require.resolve('fixtures/sample.js')
|
||||
expect(editor3.getCursorScreenPosition()).toEqual [2, 3]
|
||||
expect(editor3.getCursorScreenPosition()).toEqual [2, 4]
|
||||
expect(editor4.getPath()).toBe require.resolve('fixtures/sample.txt')
|
||||
expect(editor4.getCursorScreenPosition()).toEqual [0, 2]
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(event.newRange).toEqual new Range([0, 0], [2,0])
|
||||
|
||||
# line 2 is unchanged
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
|
||||
|
||||
it "updates tokens for lines beyond the changed lines if needed", ->
|
||||
buffer.insert([5, 30], '/* */')
|
||||
@@ -85,9 +85,9 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
|
||||
# lines below deleted regions should be shifted upward
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js'])
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
@@ -126,7 +126,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
|
||||
|
||||
# previous line 3 is pushed down to become line 5
|
||||
expect(tokenizedBuffer.lineForScreenRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
@@ -167,6 +167,7 @@ describe "TokenizedBuffer", ->
|
||||
screenLine0 = tokenizedBuffer.lineForScreenRow(0)
|
||||
expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
|
||||
{ tokens } = screenLine0
|
||||
|
||||
expect(tokens.length).toBe 3
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[1].value).toBe " Econ 101"
|
||||
|
||||
@@ -61,8 +61,15 @@ class TextMateGrammar
|
||||
))
|
||||
break
|
||||
|
||||
tokens = _.flatten(tokens.map (token) -> token.breakOutTabCharacters(tabLength))
|
||||
{ tokens, ruleStack }
|
||||
{ tokens: @breakOutAtomicTokens(tokens, tabLength), ruleStack }
|
||||
|
||||
breakOutAtomicTokens: (inputTokens, tabLength) ->
|
||||
outputTokens = []
|
||||
breakOutLeadingWhitespace = true
|
||||
for token in inputTokens
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingWhitespace)...)
|
||||
breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace
|
||||
outputTokens
|
||||
|
||||
ruleForInclude: (name) ->
|
||||
if name[0] == "#"
|
||||
|
||||
@@ -5,9 +5,9 @@ class Token
|
||||
value: null
|
||||
scopes: null
|
||||
isAtomic: null
|
||||
isTab: null
|
||||
isHardTab: null
|
||||
|
||||
constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @fold, @isTab}) ->
|
||||
constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @fold, @isHardTab}) ->
|
||||
@screenDelta = @value.length
|
||||
@bufferDelta ?= @screenDelta
|
||||
|
||||
@@ -22,24 +22,45 @@ class Token
|
||||
value2 = @value.substring(splitIndex)
|
||||
[new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)]
|
||||
|
||||
breakOutTabCharacters: (tabLength) ->
|
||||
return [this] unless /\t/.test(@value)
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingWhitespace) ->
|
||||
value = @value
|
||||
outputTokens = []
|
||||
|
||||
for substring in @value.match(/[^\t]+|\t/g)
|
||||
if breakOutLeadingWhitespace
|
||||
return [this] unless /^ |\t/.test(value)
|
||||
else
|
||||
return [this] unless /\t/.test(value)
|
||||
|
||||
if breakOutLeadingWhitespace
|
||||
endOfLeadingWhitespace = value.match(new RegExp("^( {#{tabLength}})*"))[0].length
|
||||
whitespaceTokenCount = endOfLeadingWhitespace / tabLength
|
||||
_.times whitespaceTokenCount, =>
|
||||
outputTokens.push(@buildTabToken(tabLength, false))
|
||||
|
||||
value = @value[endOfLeadingWhitespace..]
|
||||
|
||||
return outputTokens unless value.length > 0
|
||||
|
||||
for substring in value.match(/[^\t]+|\t/g)
|
||||
if substring == "\t"
|
||||
@buildTabToken(tabLength)
|
||||
outputTokens.push(@buildTabToken(tabLength, true))
|
||||
else
|
||||
new Token(value: substring, scopes: @scopes)
|
||||
outputTokens.push(new Token(value: substring, scopes: @scopes))
|
||||
|
||||
buildTabToken: (tabLength) ->
|
||||
outputTokens
|
||||
|
||||
buildTabToken: (tabLength, isHardTab) ->
|
||||
new Token(
|
||||
value: _.multiplyString(" ", tabLength)
|
||||
scopes: @scopes
|
||||
bufferDelta: 1
|
||||
bufferDelta: if isHardTab then 1 else tabLength
|
||||
isAtomic: true
|
||||
isTab: true
|
||||
isHardTab: isHardTab
|
||||
)
|
||||
|
||||
isOnlyWhitespace: ->
|
||||
not /\S/.test(@value)
|
||||
|
||||
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace})->
|
||||
html = @value
|
||||
.replace(/&/g, '&')
|
||||
@@ -49,7 +70,7 @@ class Token
|
||||
.replace(/>/g, '>')
|
||||
|
||||
if invisibles
|
||||
if @isTab and invisibles.tab
|
||||
if @isHardTab and invisibles.tab
|
||||
html = html.replace(/^./, "<span class='invisible'>#{invisibles.tab}</span>")
|
||||
else if invisibles.space
|
||||
if hasLeadingWhitespace
|
||||
|
||||
Reference in New Issue
Block a user