Move auto-indent code to TokenizedBuffer, 🔥 LanguageMode

This commit is contained in:
Max Brunsfeld
2017-09-22 16:21:06 -07:00
parent 274a699272
commit e14aa842ff
4 changed files with 142 additions and 146 deletions

View File

@@ -1,123 +0,0 @@
{Range, Point} = require 'text-buffer'
_ = require 'underscore-plus'
{OnigRegExp} = require 'oniguruma'
ScopeDescriptor = require './scope-descriptor'
NullGrammar = require './null-grammar'
module.exports =
class LanguageMode
# Sets up a `LanguageMode` for the given {TextEditor}.
#
# editor - The {TextEditor} to associate with
constructor: (@editor) ->
{@buffer} = @editor
@regexesByPattern = {}
# Given a buffer row, this returns a suggested indentation level.
#
# The indentation level provided is based on the current {LanguageMode}.
#
# bufferRow - A {Number} indicating the buffer row
#
# Returns a {Number}.
suggestedIndentForBufferRow: (bufferRow, options) ->
line = @buffer.lineForRow(bufferRow)
tokenizedLine = @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow)
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
suggestedIndentForLineAtBufferRow: (bufferRow, line, options) ->
tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line)
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
suggestedIndentForTokenizedLineAtBufferRow: (bufferRow, line, tokenizedLine, options) ->
iterator = tokenizedLine.getTokenIterator()
iterator.next()
scopeDescriptor = new ScopeDescriptor(scopes: iterator.getScopes())
increaseIndentRegex = @increaseIndentRegexForScopeDescriptor(scopeDescriptor)
decreaseIndentRegex = @decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
decreaseNextIndentRegex = @decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor)
if options?.skipBlankLines ? true
precedingRow = @buffer.previousNonBlankRow(bufferRow)
return 0 unless precedingRow?
else
precedingRow = bufferRow - 1
return 0 if precedingRow < 0
desiredIndentLevel = @editor.indentationForBufferRow(precedingRow)
return desiredIndentLevel unless increaseIndentRegex
unless @editor.isBufferRowCommented(precedingRow)
precedingLine = @buffer.lineForRow(precedingRow)
desiredIndentLevel += 1 if increaseIndentRegex?.testSync(precedingLine)
desiredIndentLevel -= 1 if decreaseNextIndentRegex?.testSync(precedingLine)
unless @buffer.isRowBlank(precedingRow)
desiredIndentLevel -= 1 if decreaseIndentRegex?.testSync(line)
Math.max(desiredIndentLevel, 0)
# Calculate a minimum indent level for a range of lines excluding empty lines.
#
# startRow - The row {Number} to start at
# endRow - The row {Number} to end at
#
# Returns a {Number} of the indent level of the block of lines.
# Indents all the rows between two buffer row numbers.
#
# startRow - The row {Number} to start at
# endRow - The row {Number} to end at
autoIndentBufferRows: (startRow, endRow) ->
@autoIndentBufferRow(row) for row in [startRow..endRow] by 1
return
# Given a buffer row, this indents it.
#
# bufferRow - The row {Number}.
# options - An options {Object} to pass through to {TextEditor::setIndentationForBufferRow}.
autoIndentBufferRow: (bufferRow, options) ->
indentLevel = @suggestedIndentForBufferRow(bufferRow, options)
@editor.setIndentationForBufferRow(bufferRow, indentLevel, options)
# Given a buffer row, this decreases the indentation.
#
# bufferRow - The row {Number}
autoDecreaseIndentForBufferRow: (bufferRow) ->
scopeDescriptor = @editor.scopeDescriptorForBufferPosition([bufferRow, 0])
return unless decreaseIndentRegex = @decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
line = @buffer.lineForRow(bufferRow)
return unless decreaseIndentRegex.testSync(line)
currentIndentLevel = @editor.indentationForBufferRow(bufferRow)
return if currentIndentLevel is 0
precedingRow = @buffer.previousNonBlankRow(bufferRow)
return unless precedingRow?
precedingLine = @buffer.lineForRow(precedingRow)
desiredIndentLevel = @editor.indentationForBufferRow(precedingRow)
if increaseIndentRegex = @increaseIndentRegexForScopeDescriptor(scopeDescriptor)
desiredIndentLevel -= 1 unless increaseIndentRegex.testSync(precedingLine)
if decreaseNextIndentRegex = @decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor)
desiredIndentLevel -= 1 if decreaseNextIndentRegex.testSync(precedingLine)
if desiredIndentLevel >= 0 and desiredIndentLevel < currentIndentLevel
@editor.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
cacheRegex: (pattern) ->
if pattern
@regexesByPattern[pattern] ?= new OnigRegExp(pattern)
increaseIndentRegexForScopeDescriptor: (scopeDescriptor) ->
@cacheRegex(@editor.getIncreaseIndentPattern(scopeDescriptor))
decreaseIndentRegexForScopeDescriptor: (scopeDescriptor) ->
@cacheRegex(@editor.getDecreaseIndentPattern(scopeDescriptor))
decreaseNextIndentRegexForScopeDescriptor: (scopeDescriptor) ->
@cacheRegex(@editor.getDecreaseNextIndentPattern(scopeDescriptor))

View File

@@ -381,7 +381,7 @@ class Selection extends Model
if options.autoIndent and textIsAutoIndentable and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
autoIndentFirstLine = true
firstLine = precedingText + firstInsertedLine
desiredIndentLevel = @editor.languageMode.suggestedIndentForLineAtBufferRow(oldBufferRange.start.row, firstLine)
desiredIndentLevel = @editor.tokenizedBuffer.suggestedIndentForLineAtBufferRow(oldBufferRange.start.row, firstLine)
indentAdjustment = desiredIndentLevel - @editor.indentLevelForLine(firstLine)
@adjustIndent(remainingLines, indentAdjustment)

View File

@@ -5,7 +5,6 @@ Grim = require 'grim'
{CompositeDisposable, Disposable, Emitter} = require 'event-kit'
{OnigRegExp} = require 'oniguruma'
{Point, Range} = TextBuffer = require 'text-buffer'
LanguageMode = require './language-mode'
DecorationManager = require './decoration-manager'
TokenizedBuffer = require './tokenized-buffer'
Cursor = require './cursor'
@@ -80,7 +79,6 @@ class TextEditor extends Model
serializationVersion: 1
buffer: null
languageMode: null
cursors: null
showCursorOnSelection: null
selections: null
@@ -245,8 +243,6 @@ class TextEditor extends Model
initialColumn = Math.max(parseInt(initialColumn) or 0, 0)
@addCursorAtBufferPosition([initialLine, initialColumn])
@languageMode = new LanguageMode(this)
@gutterContainer = new GutterContainer(this)
@lineNumberGutter = @gutterContainer.addGutter
name: 'line-number'
@@ -3085,7 +3081,8 @@ class TextEditor extends Model
else
endColumn = @lineTextForBufferRow(bufferRow).match(/^\s*/)[0].length
newIndentString = @buildIndentString(newLevel)
@buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString)
if newIndentString.length isnt endColumn
@buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString)
# Extended: Indent rows intersecting selections by one level.
indentSelectedRows: ->
@@ -3626,18 +3623,6 @@ class TextEditor extends Model
getCommentStrings: (scopes) ->
@scopedSettingsDelegate?.getCommentStrings?(scopes)
getIncreaseIndentPattern: (scopes) ->
@scopedSettingsDelegate?.getIncreaseIndentPattern?(scopes)
getDecreaseIndentPattern: (scopes) ->
@scopedSettingsDelegate?.getDecreaseIndentPattern?(scopes)
getDecreaseNextIndentPattern: (scopes) ->
@scopedSettingsDelegate?.getDecreaseNextIndentPattern?(scopes)
getFoldEndPattern: (scopes) ->
@scopedSettingsDelegate?.getFoldEndPattern?(scopes)
###
Section: Event Handlers
###
@@ -3873,15 +3858,32 @@ class TextEditor extends Model
Section: Language Mode Delegated Methods
###
suggestedIndentForBufferRow: (bufferRow, options) -> @languageMode.suggestedIndentForBufferRow(bufferRow, options)
suggestedIndentForBufferRow: (bufferRow, options) -> @tokenizedBuffer.suggestedIndentForBufferRow(bufferRow, options)
autoIndentBufferRow: (bufferRow, options) -> @languageMode.autoIndentBufferRow(bufferRow, options)
# Given a buffer row, indent it.
#
# * bufferRow - The row {Number}.
# * options - An options {Object} to pass through to {TextEditor::setIndentationForBufferRow}.
autoIndentBufferRow: (bufferRow, options) ->
indentLevel = @suggestedIndentForBufferRow(bufferRow, options)
@setIndentationForBufferRow(bufferRow, indentLevel, options)
autoIndentBufferRows: (startRow, endRow) -> @languageMode.autoIndentBufferRows(startRow, endRow)
# Indents all the rows between two buffer row numbers.
#
# * startRow - The row {Number} to start at
# * endRow - The row {Number} to end at
autoIndentBufferRows: (startRow, endRow) ->
row = startRow
while row <= endRow
@autoIndentBufferRow(row)
row++
return
autoDecreaseIndentForBufferRow: (bufferRow) -> @languageMode.autoDecreaseIndentForBufferRow(bufferRow)
autoDecreaseIndentForBufferRow: (bufferRow) ->
indentLevel = @tokenizedBuffer.suggestedIndentForEditedBufferRow(bufferRow)
@setIndentationForBufferRow(bufferRow, indentLevel)
toggleLineCommentForBufferRow: (row) -> @languageMode.toggleLineCommentsForBufferRow(row)
toggleLineCommentForBufferRow: (row) -> @toggleLineCommentsForBufferRows(row, row)
toggleLineCommentsForBufferRows: (start, end) ->
scope = @scopeDescriptorForBufferPosition([start, 0])

View File

@@ -57,6 +57,105 @@ class TokenizedBuffer {
return !this.alive
}
/*
Section - auto-indent
*/
// Get the suggested indentation level for an existing line in the buffer.
//
// * bufferRow - A {Number} indicating the buffer row
//
// Returns a {Number}.
suggestedIndentForBufferRow (bufferRow, options) {
const line = this.buffer.lineForRow(bufferRow)
const tokenizedLine = this.tokenizedLineForRow(bufferRow)
return this._suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
}
// Get the suggested indentation level for a given line of text, if it were inserted at the given
// row in the buffer.
//
// * bufferRow - A {Number} indicating the buffer row
//
// Returns a {Number}.
suggestedIndentForLineAtBufferRow (bufferRow, line, options) {
const tokenizedLine = this.buildTokenizedLineForRowWithText(bufferRow, line)
return this._suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
}
// Get the suggested indentation level for a line in the buffer on which the user is currently
// typing. This may return a different result from {::suggestedIndentForBufferRow} in order
// to avoid unexpected changes in indentation.
//
// * bufferRow - The row {Number}
//
// Returns a {Number}.
suggestedIndentForEditedBufferRow (bufferRow) {
const line = this.buffer.lineForRow(bufferRow)
const currentIndentLevel = this.indentLevelForLine(line)
if (currentIndentLevel === 0) return currentIndentLevel
const scopeDescriptor = this.scopeDescriptorForPosition([bufferRow, 0])
const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
if (!decreaseIndentRegex) return currentIndentLevel
if (!decreaseIndentRegex.testSync(line)) return currentIndentLevel
const precedingRow = this.buffer.previousNonBlankRow(bufferRow)
if (precedingRow == null) return currentIndentLevel
const precedingLine = this.buffer.lineForRow(precedingRow)
let desiredIndentLevel = this.indentLevelForLine(precedingLine)
const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor(scopeDescriptor)
if (increaseIndentRegex) {
if (!increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1
}
const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor)
if (decreaseNextIndentRegex) {
if (decreaseNextIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1
}
if (desiredIndentLevel < 0) return 0
if (desiredIndentLevel > currentIndentLevel) return currentIndentLevel
return desiredIndentLevel
}
_suggestedIndentForTokenizedLineAtBufferRow (bufferRow, line, tokenizedLine, options) {
const iterator = tokenizedLine.getTokenIterator()
iterator.next()
const scopeDescriptor = new ScopeDescriptor({scopes: iterator.getScopes()})
const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor(scopeDescriptor)
const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor)
let precedingRow
if (!options || options.skipBlankLines !== false) {
precedingRow = this.buffer.previousNonBlankRow(bufferRow)
if (precedingRow == null) return 0
} else {
precedingRow = bufferRow - 1
if (precedingRow < 0) return 0
}
const precedingLine = this.buffer.lineForRow(precedingRow)
let desiredIndentLevel = this.indentLevelForLine(precedingLine)
if (!increaseIndentRegex) return desiredIndentLevel
if (!this.isRowCommented(precedingRow)) {
if (increaseIndentRegex && increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel += 1
if (decreaseNextIndentRegex && decreaseNextIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1
}
if (!this.buffer.isRowBlank(precedingRow)) {
if (decreaseIndentRegex && decreaseIndentRegex.testSync(line)) desiredIndentLevel -= 1
}
return Math.max(desiredIndentLevel, 0)
}
buildIterator () {
return new TokenizedBufferIterator(this)
}
@@ -595,6 +694,24 @@ class TokenizedBuffer {
return foldEndRow
}
increaseIndentRegexForScopeDescriptor (scopeDescriptor) {
if (this.scopedSettingsDelegate) {
return this.regexForPattern(this.scopedSettingsDelegate.getIncreaseIndentPattern(scopeDescriptor))
}
}
decreaseIndentRegexForScopeDescriptor (scopeDescriptor) {
if (this.scopedSettingsDelegate) {
return this.regexForPattern(this.scopedSettingsDelegate.getDecreaseIndentPattern(scopeDescriptor))
}
}
decreaseNextIndentRegexForScopeDescriptor (scopeDescriptor) {
if (this.scopedSettingsDelegate) {
return this.regexForPattern(this.scopedSettingsDelegate.getDecreaseNextIndentPattern(scopeDescriptor))
}
}
foldEndRegexForScopeDescriptor (scopes) {
if (this.scopedSettingsDelegate) {
return this.regexForPattern(this.scopedSettingsDelegate.getFoldEndPattern(scopes))