diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 5b1c4b15e..95f6a2112 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -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", -> diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index 9bb5fa426..d92460043 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -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) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index e8ff173fa..232b28841 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -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) diff --git a/src/app/language-mode.coffee b/src/app/language-mode.coffee index 265bbd9a0..3c7bb0e6b 100644 --- a/src/app/language-mode.coffee +++ b/src/app/language-mode.coffee @@ -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) diff --git a/src/app/range.coffee b/src/app/range.coffee index 924c6349a..11efe0c94 100644 --- a/src/app/range.coffee +++ b/src/app/range.coffee @@ -74,3 +74,6 @@ class Range else columns = @end.column new Point(rows, columns) + + getRowCount: -> + @end.row - @start.row + 1 diff --git a/src/app/selection.coffee b/src/app/selection.coffee index ccdeee59b..e464f4ada 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -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())