WIP: Reworking auto-indent/outdent logic

This commit is contained in:
Corey Johnson & Nathan Sobo
2012-08-09 12:57:47 -06:00
parent ad4b3db439
commit e8aaec43f7
6 changed files with 135 additions and 74 deletions

View File

@@ -656,60 +656,78 @@ describe "EditSession", ->
editSession.insertText('holy cow')
expect(editSession.lineForScreenRow(2).fold).toBeUndefined()
xdescribe "when auto-indent is enabled", ->
fdescribe "when auto-indent is enabled", ->
beforeEach ->
editSession.setAutoIndent(true)
describe "when editing a non-wrapped line", ->
describe "when a newline is inserted", ->
it "auto-indents the new line for each cursor", ->
editSession.setCursorScreenPosition([1, 30])
editSession.addCursorAtScreenPosition([4, 29])
editSession.insertText("\n")
expect(editSession.buffer.lineForRow(2)).toEqual(" ")
expect(editSession.buffer.lineForRow(6)).toEqual(" ")
describe "when a single newline is inserted", ->
describe "when the newline is inserted on a line that starts a new level of indentation", ->
it "auto-indents the new line to one additional level of indentation beyond the preceding line", ->
editSession.setCursorBufferPosition([1, Infinity])
editSession.insertText('\n')
expect(buffer.indentationForRow(2)).toBe buffer.indentationForRow(1) + 2
describe "when text beginning with a newline is inserted", ->
it "indents cursor based on the indentation of previous buffer line", ->
editSession.setCursorBufferPosition([4, 29])
editSession.insertText("\nvar thisIsCool")
expect(buffer.lineForRow(5)).toEqual(" var thisIsCool")
describe "when the newline is inserted on a normal line", ->
it "auto-indents the new line to the same level of indentation as the preceding line", ->
editSession.setCursorBufferPosition([5, 13])
editSession.insertText('\n')
expect(buffer.indentationForRow(6)).toBe buffer.indentationForRow(5)
describe "when text that closes a scope entered", ->
it "outdents the text", ->
editSession.setCursorBufferPosition([1, 30])
editSession.insertText("\n")
expect(editSession.buffer.lineForRow(2)).toEqual(" ")
editSession.insertText("}")
expect(buffer.lineForRow(2)).toEqual(" }")
expect(editSession.getCursorBufferPosition().column).toBe 3
describe "when text with newlines is inserted", ->
describe "when the portion of the line preceding the inserted text is blank", ->
it "auto-increases the indentation of the first line, then fully auto-indents the subsequent lines", ->
editSession.setCursorBufferPosition([5, 2])
editSession.insertText """
if (true) {
console.log("It's true!")
}\n
"""
describe "when the line is already indented beyond the suggested depth", ->
describe "when text without a newline is inserted", ->
it "does not modify the line's indentation level", ->
expect(buffer.indentationForRow(5)).toBe buffer.indentationForRow(4) + 2
expect(buffer.indentationForRow(6)).toBe buffer.indentationForRow(5) + 2
expect(buffer.indentationForRow(7)).toBe buffer.indentationForRow(4) + 2
expect(buffer.indentationForRow(8)).toBe buffer.indentationForRow(4) + 2
describe "when text with a newline is inserted", ->
it "only modifies the indentation level of subsequent lines, but not the current line", ->
describe "when the portion of the line preceding the inserted text is non-blank", ->
it "fully auto-indents lines subsequent to the first inserted line", ->
buffer.delete([[5, 0], [5, 2]])
editSession.setCursorBufferPosition([5, Infinity])
editSession.insertText """
if (true) {
console.log("It's true!")
}
"""
expect(buffer.indentationForRow(5)).toBe 4
expect(buffer.indentationForRow(6)).toBe 6
expect(buffer.indentationForRow(7)).toBe 4
describe "when text without newlines is inserted", ->
describe "when the current line matches an auto-outdent pattern", ->
describe "when the preceding line matches an auto-indent pattern", ->
it "auto-decreases the indentation of the line to match that of the preceding line", ->
editSession.setCursorBufferPosition([2, 4])
editSession.insertText '\n'
editSession.setCursorBufferPosition([2, 4])
expect(buffer.indentationForRow(2)).toBe buffer.indentationForRow(1) + 2
editSession.insertText ' }'
buffer.logLines()
expect(buffer.indentationForRow(2)).toBe buffer.indentationForRow(1)
describe "when editing a wrapped line", ->
beforeEach ->
editSession.setSoftWrapColumn(50)
describe "when the preceding does not match an outo-indent pattern", ->
ffit "auto-decreases the indentation of the line to be one level below that of the preceding line", ->
editSession.setCursorBufferPosition([3, Infinity])
editSession.insertText '\n'
expect(buffer.indentationForRow(4)).toBe buffer.indentationForRow(3)
editSession.insertText ' }'
buffer.logLines()
expect(buffer.indentationForRow(4)).toBe buffer.indentationForRow(3) - 2
describe "when newline is inserted", ->
it "indents cursor based on the indentation of previous buffer line", ->
editSession.setCursorBufferPosition([4, 29])
editSession.insertText("\n")
expect(editSession.buffer.lineForRow(5)).toEqual(" ")
describe "when text that closes a scope is entered", ->
it "outdents the text", ->
editSession.setCursorBufferPosition([4, 29])
editSession.insertText("\n")
expect(editSession.buffer.lineForRow(5)).toEqual(" ")
editSession.insertText("}")
expect(editSession.buffer.lineForRow(5)).toEqual(" }")
expect(editSession.getCursorBufferPosition().column).toBe 5
describe "when the current line does not match an auto-outdent pattern", ->
it "leaves the line unchanged", ->
editSession.setCursorBufferPosition([2, 4])
expect(buffer.indentationForRow(2)).toBe buffer.indentationForRow(1) + 2
editSession.insertText 'foo'
expect(buffer.indentationForRow(2)).toBe buffer.indentationForRow(1) + 2
describe ".insertNewline()", ->
describe "when there is a single cursor", ->

View File

@@ -329,17 +329,27 @@ class Buffer
isRowBlank: (row) ->
not /\S/.test @lineForRow(row)
nextNonBlankRow: (row) ->
lastRow = @getLastRow()
if row < lastRow
for row in [(row + 1)..lastRow]
return row unless @isRowBlank(row)
previousNonBlankRow: (startRow) ->
startRow = Math.min(startRow, @getLastRow())
for row in [(startRow - 1)..0]
return row unless @isRowBlank(row)
null
nextNonBlankRow: (startRow) ->
lastRow = @getLastRow()
if startRow < lastRow
for row in [(startRow + 1)..lastRow]
return row unless @isRowBlank(row)
null
indentationForRow: (row) ->
@lineForRow(row).match(/^\s*/)?[0].length
setIndentationForRow: (bufferRow, newLevel) ->
currentLevel = @indentationForRow(bufferRow)
indentString = [0...newLevel].map(-> ' ').join('')
@change([[bufferRow, 0], [bufferRow, currentLevel]], indentString)
logLines: (start=0, end=@getLastRow())->
for row in [start..end]
line = @lineForRow(row)

View File

@@ -247,17 +247,17 @@ class EditSession
largestFoldStartingAtScreenRow: (screenRow) ->
@displayBuffer.largestFoldStartingAtScreenRow(screenRow)
indentationForRow: (row) ->
@languageMode.indentationForRow(row)
autoIndentBufferRows: (startRow, endRow) ->
@languageMode.autoIndentBufferRows(startRow, endRow)
autoIndentRows: (startRow, endRow) ->
@autoIndentRow(row) for row in [startRow..endRow]
autoIndentBufferRow: (bufferRow) ->
@languageMode.autoIndentBufferRow(bufferRow)
autoIndentRow: (row) ->
actualIndentation = @lineForBufferRow(row).match(/^\s*/)[0]
desiredIndentation = @indentationForRow(row)
if actualIndentation != desiredIndentation
@buffer.change([[row, 0], [row, actualIndentation.length]], desiredIndentation)
autoIncreaseIndentForBufferRow: (bufferRow) ->
@languageMode.autoIncreaseIndentForBufferRow(bufferRow)
autoDecreaseIndentForRow: (bufferRow) ->
@languageMode.autoDecreaseIndentForBufferRow(bufferRow)
toggleLineCommentsInRange: (range) ->
@languageMode.toggleLineCommentsInRange(range)

View File

@@ -13,7 +13,8 @@ class LanguageMode
"'": "'"
constructor: (@editSession) ->
@grammar = TextMateBundle.grammarForFileName(@editSession.buffer.getBaseName())
@buffer = @editSession.buffer
@grammar = TextMateBundle.grammarForFileName(@buffer.getBaseName())
_.adviseBefore @editSession, 'insertText', (text) =>
return true if @editSession.hasMultipleCursors()
@@ -84,25 +85,43 @@ class LanguageMode
[bufferRow, foldEndRow]
indentationForRow: (row) ->
for precedingRow in [row - 1..-1]
return if precedingRow < 0
precedingLine = @editSession.buffer.lineForRow(precedingRow)
break if /\S/.test(precedingLine)
autoIndentBufferRows: (startRow, endRow) ->
@autoIndentBufferRow(row) for row in [startRow..endRow]
autoIndentBufferRow: (bufferRow) ->
@autoIncreaseIndentForBufferRow(bufferRow)
@autoDecreaseIndentForBufferRow(bufferRow)
autoIncreaseIndentForBufferRow: (bufferRow) ->
precedingRow = @buffer.previousNonBlankRow(bufferRow)
return unless precedingRow?
precedingLine = @editSession.lineForBufferRow(precedingRow)
scopes = @tokenizedBuffer.scopesForPosition([precedingRow, Infinity])
indentation = precedingLine.match(/^\s*/)[0]
increaseIndentPattern = TextMateBundle.getPreferenceInScope(scopes[0], 'increaseIndentPattern')
decreaseIndentPattern = TextMateBundle.getPreferenceInScope(scopes[0], 'decreaseIndentPattern')
increaseIndentPattern = new OnigRegExp(TextMateBundle.getPreferenceInScope(scopes[0], 'increaseIndentPattern'))
if new OnigRegExp(increaseIndentPattern).search(precedingLine)
indentation += @editSession.tabText
currentIndentation = @buffer.indentationForRow(bufferRow)
desiredIndentation = @buffer.indentationForRow(precedingRow)
desiredIndentation += @editSession.tabText.length if increaseIndentPattern.test(precedingLine)
if desiredIndentation > currentIndentation
@buffer.setIndentationForRow(bufferRow, desiredIndentation)
line = @editSession.buffer.lineForRow(row)
if new OnigRegExp(decreaseIndentPattern).search(line)
indentation = indentation.replace(@editSession.tabText, "")
autoDecreaseIndentForBufferRow: (bufferRow) ->
scopes = @tokenizedBuffer.scopesForPosition([bufferRow, 0])
increaseIndentPattern = new OnigRegExp(TextMateBundle.getPreferenceInScope(scopes[0], 'increaseIndentPattern'))
decreaseIndentPattern = new OnigRegExp(TextMateBundle.getPreferenceInScope(scopes[0], 'decreaseIndentPattern'))
line = @buffer.lineForRow(bufferRow)
return unless decreaseIndentPattern.test(line)
indentation
currentIndentation = @buffer.indentationForRow(bufferRow)
precedingRow = @buffer.previousNonBlankRow(bufferRow)
precedingLine = @buffer.lineForRow(precedingRow)
desiredIndentation = @buffer.indentationForRow(precedingRow)
desiredIndentation -= @editSession.tabText.length unless increaseIndentPattern.test(precedingLine)
if desiredIndentation < currentIndentation
@buffer.setIndentationForRow(bufferRow, desiredIndentation)
getLineTokens: (line, stack) ->
{tokens, stack} = @grammar.getLineTokens(line, stack)

View File

@@ -74,3 +74,6 @@ class Range
else
columns = @end.column
new Point(rows, columns)
getRowCount: ->
@end.row - @start.row + 1

View File

@@ -134,7 +134,18 @@ class Selection
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
if @editSession.autoIndent
@editSession.autoIndentRows(newBufferRange.start.row, newBufferRange.end.row)
if /\n/.test(text)
firstLinePrefix = @editSession.getTextInBufferRange([[newBufferRange.start.row, 0], newBufferRange.start])
if /^\s*$/.test(firstLinePrefix)
@editSession.autoIncreaseIndentForBufferRow(newBufferRange.start.row)
if newBufferRange.getRowCount() > 1
@editSession.autoIndentBufferRows(newBufferRange.start.row + 1, newBufferRange.end.row)
else
@editSession.autoIncreaseIndentForBufferRow(newBufferRange.start.row + 1)
if newBufferRange.getRowCount() > 2
@editSession.autoIndentBufferRows(newBufferRange.start.row + 2, newBufferRange.end.row)
else
@editSession.autoDecreaseIndentForRow(newBufferRange.start.row)
backspace: ->
if @isEmpty() and not @editSession.isFoldedAtScreenRow(@cursor.getScreenRow())