mirror of
https://github.com/atom/atom.git
synced 2026-01-23 22:08:08 -05:00
Store line endings on a per-line basis in Buffer
The line ending for each line is recorded and reused when lines are modified or inserted. Closes #166
This commit is contained in:
@@ -833,3 +833,51 @@ describe 'Buffer', ->
|
||||
expect(buffer.getText()).toBe "a"
|
||||
buffer.append("b\nc");
|
||||
expect(buffer.getText()).toBe "ab\nc"
|
||||
|
||||
describe "line ending support", ->
|
||||
describe ".lineEndingForRow(line)", ->
|
||||
it "return the line ending for each buffer line", ->
|
||||
buffer.setText("a\r\nb\nc")
|
||||
expect(buffer.lineEndingForRow(0)).toBe '\r\n'
|
||||
expect(buffer.lineEndingForRow(1)).toBe '\n'
|
||||
expect(buffer.lineEndingForRow(2)).toBeUndefined()
|
||||
|
||||
describe ".lineForRow(line)", ->
|
||||
it "returns the line text without the line ending for both lf and crlf lines", ->
|
||||
buffer.setText("a\r\nb\nc")
|
||||
expect(buffer.lineForRow(0)).toBe 'a'
|
||||
expect(buffer.lineForRow(1)).toBe 'b'
|
||||
expect(buffer.lineForRow(2)).toBe 'c'
|
||||
|
||||
describe ".getText()", ->
|
||||
it "returns the text with the corrent line endings for each row", ->
|
||||
buffer.setText("a\r\nb\nc")
|
||||
expect(buffer.getText()).toBe "a\r\nb\nc"
|
||||
buffer.setText("a\r\nb\nc\n")
|
||||
expect(buffer.getText()).toBe "a\r\nb\nc\n"
|
||||
|
||||
describe "when editing a line", ->
|
||||
it "preserves the existing line ending", ->
|
||||
buffer.setText("a\r\nb\nc")
|
||||
buffer.insert([0, 1], "1")
|
||||
expect(buffer.getText()).toBe "a1\r\nb\nc"
|
||||
|
||||
describe "when inserting text with multiple lines", ->
|
||||
describe "when the current line has a line ending", ->
|
||||
it "uses the same line ending as the line where the text is inserted", ->
|
||||
buffer.setText("a\r\n")
|
||||
buffer.insert([0,1], "hello\n1\n\n2")
|
||||
expect(buffer.getText()).toBe "ahello\r\n1\r\n\r\n2\r\n"
|
||||
|
||||
describe "when the current line has no line ending (because it's the last line of the buffer)", ->
|
||||
describe "when the buffer contains only a single line", ->
|
||||
it "honors the line endings in the inserted text", ->
|
||||
buffer.setText("initialtext")
|
||||
buffer.append("hello\n1\r\n2\n")
|
||||
expect(buffer.getText()).toBe "initialtexthello\n1\r\n2\n"
|
||||
|
||||
describe "when the buffer contains a preceding line", ->
|
||||
it "uses the line ending of the preceding line", ->
|
||||
buffer.setText("\ninitialtext")
|
||||
buffer.append("hello\n1\r\n2\n")
|
||||
expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n"
|
||||
|
||||
@@ -1609,7 +1609,7 @@ describe "Editor", ->
|
||||
editor.attachToDom()
|
||||
|
||||
expect(config.get("editor.showInvisibles")).toBeFalsy()
|
||||
expect(editor.renderedLines.find('.line:first').text()).toBe "a line that ends with a carriage return\n"
|
||||
expect(editor.renderedLines.find('.line:first').text()).toBe "a line that ends with a carriage return"
|
||||
|
||||
config.set("editor.showInvisibles", true)
|
||||
cr = editor.invisibles?.cr
|
||||
|
||||
@@ -308,16 +308,16 @@ describe "LanguageMode", ->
|
||||
expect(buffer.lineForRow(3)).toBe " font-weight: bold !important;"
|
||||
|
||||
it "uncomments lines with leading whitespace", ->
|
||||
buffer.replaceLines(2, 2, " /*width: 110%;*/")
|
||||
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/")
|
||||
languageMode.toggleLineCommentsForBufferRows(2, 2)
|
||||
expect(buffer.lineForRow(2)).toBe " width: 110%;"
|
||||
|
||||
it "uncomments lines with trailing whitespace", ->
|
||||
buffer.replaceLines(2, 2, "/*width: 110%;*/ ")
|
||||
buffer.change([[2, 0], [2, Infinity]], "/*width: 110%;*/ ")
|
||||
languageMode.toggleLineCommentsForBufferRows(2, 2)
|
||||
expect(buffer.lineForRow(2)).toBe "width: 110%; "
|
||||
|
||||
it "uncomments lines with leading and trailing whitespace", ->
|
||||
buffer.replaceLines(2, 2, " /*width: 110%;*/ ")
|
||||
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/ ")
|
||||
languageMode.toggleLineCommentsForBufferRows(2, 2)
|
||||
expect(buffer.lineForRow(2)).toBe " width: 110%; "
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Range = require 'range'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class BufferChangeOperation
|
||||
@@ -26,18 +27,37 @@ class BufferChangeOperation
|
||||
oldText: @newText
|
||||
newText: @oldText
|
||||
|
||||
splitLines: (text) ->
|
||||
lines = text.split('\n')
|
||||
lineEndings = []
|
||||
for line, index in lines
|
||||
if _.endsWith(line, '\r')
|
||||
lines[index] = line[0..-2]
|
||||
lineEndings[index] = '\r\n'
|
||||
else
|
||||
lineEndings[index] = '\n'
|
||||
{lines, lineEndings}
|
||||
|
||||
changeBuffer: ({ oldRange, newRange, newText, oldText }) ->
|
||||
{ prefix, suffix } = @buffer.prefixAndSuffixForRange(oldRange)
|
||||
|
||||
newTextLines = newText.split('\n')
|
||||
if newTextLines.length == 1
|
||||
newTextLines = [prefix + newText + suffix]
|
||||
else
|
||||
lastLineIndex = newTextLines.length - 1
|
||||
newTextLines[0] = prefix + newTextLines[0]
|
||||
newTextLines[lastLineIndex] += suffix
|
||||
{lines, lineEndings} = @splitLines(newText)
|
||||
lastLineIndex = lines.length - 1
|
||||
|
||||
@buffer.replaceLines(oldRange.start.row, oldRange.end.row, newTextLines)
|
||||
if lines.length == 1
|
||||
lines = [prefix + newText + suffix]
|
||||
else
|
||||
lines[0] = prefix + lines[0]
|
||||
lines[lastLineIndex] += suffix
|
||||
|
||||
startRow = oldRange.start.row
|
||||
endRow = oldRange.end.row
|
||||
if suggestedLineEnding = @buffer.suggestedLineEndingForRow(startRow)
|
||||
lineEndings[index] = suggestedLineEnding for index in [0..lastLineIndex]
|
||||
@buffer.lines[startRow..endRow] = lines
|
||||
@buffer.lineEndings[startRow..endRow] = lineEndings
|
||||
@buffer.cachedMemoryContents = null
|
||||
@buffer.conflict = false if @buffer.conflict and !@buffer.isModified()
|
||||
|
||||
event = { oldRange, newRange, oldText, newText }
|
||||
@buffer.trigger 'changed', event
|
||||
@@ -47,11 +67,11 @@ class BufferChangeOperation
|
||||
|
||||
calculateNewRange: (oldRange, newText) ->
|
||||
newRange = new Range(oldRange.start.copy(), oldRange.start.copy())
|
||||
newTextLines = newText.split('\n')
|
||||
if newTextLines.length == 1
|
||||
{lines} = @splitLines(newText)
|
||||
if lines.length == 1
|
||||
newRange.end.column += newText.length
|
||||
else
|
||||
lastLineIndex = newTextLines.length - 1
|
||||
lastLineIndex = lines.length - 1
|
||||
newRange.end.row += lastLineIndex
|
||||
newRange.end.column = newTextLines[lastLineIndex].length
|
||||
newRange.end.column = lines[lastLineIndex].length
|
||||
newRange
|
||||
|
||||
@@ -19,6 +19,7 @@ class Buffer
|
||||
cachedMemoryContents: null
|
||||
conflict: false
|
||||
lines: null
|
||||
lineEndings: null
|
||||
file: null
|
||||
anchors: null
|
||||
anchorRanges: null
|
||||
@@ -29,6 +30,7 @@ class Buffer
|
||||
@anchors = []
|
||||
@anchorRanges = []
|
||||
@lines = ['']
|
||||
@lineEndings = []
|
||||
|
||||
if path
|
||||
throw "Path '#{path}' does not exist" unless fs.exists(path)
|
||||
@@ -104,9 +106,10 @@ class Buffer
|
||||
null
|
||||
|
||||
getText: ->
|
||||
@cachedMemoryContents ?= @lines.join('\n')
|
||||
@cachedMemoryContents ?= @getTextInRange(@getRange())
|
||||
|
||||
setText: (text) ->
|
||||
@lineEndings = []
|
||||
@change(@getRange(), text)
|
||||
|
||||
getRange: ->
|
||||
@@ -118,12 +121,14 @@ class Buffer
|
||||
return @lines[range.start.row][range.start.column...range.end.column]
|
||||
|
||||
multipleLines = []
|
||||
multipleLines.push @lines[range.start.row][range.start.column..] # first line
|
||||
multipleLines.push @lineForRow(range.start.row)[range.start.column..] # first line
|
||||
multipleLines.push @lineEndingForRow(range.start.row)
|
||||
for row in [range.start.row + 1...range.end.row]
|
||||
multipleLines.push @lines[row] # middle lines
|
||||
multipleLines.push @lines[range.end.row][0...range.end.column] # last line
|
||||
multipleLines.push @lineForRow(row) # middle lines
|
||||
multipleLines.push @lineEndingForRow(row)
|
||||
multipleLines.push @lineForRow(range.end.row)[0...range.end.column] # last line
|
||||
|
||||
return multipleLines.join '\n'
|
||||
return multipleLines.join ''
|
||||
|
||||
getLines: ->
|
||||
@lines
|
||||
@@ -131,6 +136,12 @@ class Buffer
|
||||
lineForRow: (row) ->
|
||||
@lines[row]
|
||||
|
||||
lineEndingForRow: (row) ->
|
||||
@lineEndings[row] unless row is @getLastRow()
|
||||
|
||||
suggestedLineEndingForRow: (row) ->
|
||||
@lineEndingForRow(row) ? @lineEndingForRow(row - 1)
|
||||
|
||||
lineLengthForRow: (row) ->
|
||||
@lines[row].length
|
||||
|
||||
@@ -214,11 +225,6 @@ class Buffer
|
||||
prefix: @lines[range.start.row][0...range.start.column]
|
||||
suffix: @lines[range.end.row][range.end.column..]
|
||||
|
||||
replaceLines: (startRow, endRow, newLines) ->
|
||||
@lines[startRow..endRow] = newLines
|
||||
@cachedMemoryContents = null
|
||||
@conflict = false if @conflict and !@isModified()
|
||||
|
||||
pushOperation: (operation, editSession) ->
|
||||
if @undoManager
|
||||
@undoManager.pushOperation(operation, editSession)
|
||||
|
||||
@@ -1076,8 +1076,11 @@ class Editor extends View
|
||||
position += token.value.length
|
||||
|
||||
popScope() while scopeStack.length > 0
|
||||
if not @mini and invisibles?.eol
|
||||
line.push("<span class='invisible'>#{invisibles.eol}</span>")
|
||||
if invisibles and not @mini
|
||||
if invisibles.cr and screenLine.lineEnding is '\r\n'
|
||||
line.push("<span class='invisible'>#{invisibles.cr}</span>")
|
||||
if invisibles.eol
|
||||
line.push("<span class='invisible'>#{invisibles.eol}</span>")
|
||||
|
||||
line.push('</pre>')
|
||||
line.join('')
|
||||
|
||||
@@ -2,7 +2,7 @@ _ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class ScreenLine
|
||||
constructor: ({tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
|
||||
constructor: ({tokens, @lineEnding, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
|
||||
@tokens = @breakOutAtomicTokens(tokens, tabLength)
|
||||
@bufferRows ?= 1
|
||||
@startBufferColumn ?= 0
|
||||
|
||||
@@ -80,8 +80,5 @@ class Token
|
||||
if hasTrailingWhitespace
|
||||
html = html.replace /[ ]+$/, (match) ->
|
||||
"<span class='invisible'>#{match.replace(/./g, invisibles.space)}</span>"
|
||||
if invisibles.cr
|
||||
html = html.replace /\r$/, (match) ->
|
||||
"<span class='invisible'>#{invisibles.cr}</span>"
|
||||
|
||||
html
|
||||
|
||||
@@ -137,8 +137,9 @@ class TokenizedBuffer
|
||||
|
||||
buildTokenizedScreenLineForRow: (row, ruleStack) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
lineEnding = @buffer.lineEndingForRow(row)
|
||||
{ tokens, ruleStack } = @languageMode.tokenizeLine(line, ruleStack, row is 0)
|
||||
new ScreenLine({tokens, ruleStack, @tabLength})
|
||||
new ScreenLine({tokens, ruleStack, @tabLength, lineEnding})
|
||||
|
||||
lineForScreenRow: (row) ->
|
||||
@linesForScreenRows(row, row)[0]
|
||||
|
||||
Reference in New Issue
Block a user