diff --git a/src/language-mode.coffee b/src/language-mode.coffee deleted file mode 100644 index 6d306a38a..000000000 --- a/src/language-mode.coffee +++ /dev/null @@ -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)) diff --git a/src/selection.coffee b/src/selection.coffee index e361d0b5c..4d3fe8882 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -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) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index d75276f06..8cbcc94f3 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -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]) diff --git a/src/tokenized-buffer.js b/src/tokenized-buffer.js index f51baa950..4cb0e7b4e 100644 --- a/src/tokenized-buffer.js +++ b/src/tokenized-buffer.js @@ -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))