From 7f1bb6958073c74273c0b93fac9099c9145acc81 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 14 Feb 2015 19:04:53 +0100 Subject: [PATCH 01/42] Initial attempt --- src/display-buffer.coffee | 2 +- src/token.coffee | 8 ++++++++ src/tokenized-line.coffee | 10 +++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 1177dd15f..fabf72532 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -841,7 +841,7 @@ class DisplayBuffer extends Model if screenLine.isSoftWrapped() and column >= maxScreenColumn if wrapAtSoftNewlines row++ - column = 0 + column = @screenLines[row].tokens[0].value.length # TODO: call screenLine.clipScreenColumn else column = screenLine.clipScreenColumn(maxScreenColumn - 1) else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() diff --git a/src/token.coffee b/src/token.coffee index 85afe680e..78ccbfbb4 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -144,6 +144,14 @@ class Token isHardTab: isHardTab ) + buildPhantomToken: (length) -> + new Token( + value: _.multiplyString(" ", length), + scopes: @scopes, + bufferDelta: 0, + isAtomic: true + ) + isOnlyWhitespace: -> not WhitespaceRegex.test(@value) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index d1576ab9a..612333198 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -97,19 +97,23 @@ class TokenizedLine leftTextLength += nextToken.value.length leftTokens.push nextToken + tab = leftTokens[0].buildPhantomToken(@indentLevel * 2) + leftFragment = new TokenizedLine( tokens: leftTokens startBufferColumn: @startBufferColumn ruleStack: @ruleStack invisibles: @invisibles - lineEnding: null + lineEnding: null, + indentLevel: @indentLevel ) rightFragment = new TokenizedLine( - tokens: rightTokens + tokens: [tab].concat(rightTokens) startBufferColumn: @bufferColumnForScreenColumn(column) ruleStack: @ruleStack invisibles: @invisibles - lineEnding: @lineEnding + lineEnding: @lineEnding, + indentLevel: @indentLevel ) [leftFragment, rightFragment] From fb21cf6123a12ee9f209058e3c14d8f6d0ce7715 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sun, 15 Feb 2015 12:33:34 +0100 Subject: [PATCH 02/42] Wrap appropriately --- src/display-buffer.coffee | 8 +++++++- src/token.coffee | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index fabf72532..ae7e08f23 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -841,9 +841,15 @@ class DisplayBuffer extends Model if screenLine.isSoftWrapped() and column >= maxScreenColumn if wrapAtSoftNewlines row++ - column = @screenLines[row].tokens[0].value.length # TODO: call screenLine.clipScreenColumn + column = if @screenLines[row].tokens[0].isPhantom + @screenLines[row].tokens[0].screenDelta + else + 0 else column = screenLine.clipScreenColumn(maxScreenColumn - 1) + else if screenLine.tokens[0].isPhantom and column < screenLine.tokens[0].screenDelta + row-- + column = @screenLines[row].getMaxScreenColumn() else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() row++ column = 0 diff --git a/src/token.coffee b/src/token.coffee index 78ccbfbb4..d3e17a570 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -21,7 +21,7 @@ class Token firstTrailingWhitespaceIndex: null hasInvisibleCharacters: false - constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab, @hasPairedCharacter}) -> + constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab, @hasPairedCharacter, @isPhantom}) -> @screenDelta = @value.length @bufferDelta ?= @screenDelta @hasPairedCharacter ?= textUtils.hasPairedCharacter(@value) @@ -149,7 +149,8 @@ class Token value: _.multiplyString(" ", length), scopes: @scopes, bufferDelta: 0, - isAtomic: true + isAtomic: true, + isPhantom: true ) isOnlyWhitespace: -> From 46fca8360b773f76fcab17f70c3071d8816690bb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 11:58:21 +0100 Subject: [PATCH 03/42] Use @tabLength instead of an arbitrary value --- src/tokenized-line.coffee | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 612333198..b09e39e68 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -97,7 +97,7 @@ class TokenizedLine leftTextLength += nextToken.value.length leftTokens.push nextToken - tab = leftTokens[0].buildPhantomToken(@indentLevel * 2) + tab = leftTokens[0].buildPhantomToken(@indentLevel * @tabLength) leftFragment = new TokenizedLine( tokens: leftTokens @@ -105,7 +105,8 @@ class TokenizedLine ruleStack: @ruleStack invisibles: @invisibles lineEnding: null, - indentLevel: @indentLevel + indentLevel: @indentLevel, + tabLength: @tabLength ) rightFragment = new TokenizedLine( tokens: [tab].concat(rightTokens) @@ -113,7 +114,8 @@ class TokenizedLine ruleStack: @ruleStack invisibles: @invisibles lineEnding: @lineEnding, - indentLevel: @indentLevel + indentLevel: @indentLevel, + tabLength: @tabLength ) [leftFragment, rightFragment] From 5ce01118be245bce8568647c1d4a5f461be2b708 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 12:30:00 +0100 Subject: [PATCH 04/42] Cover edge cases --- src/display-buffer.coffee | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index ae7e08f23..9fea3c2bf 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -866,17 +866,25 @@ class DisplayBuffer extends Model # Returns `null` if a wrap wouldn't occur. findWrapColumn: (line, softWrapColumn=@getSoftWrapColumn()) -> return unless @isSoftWrapped() - return unless line.length > softWrapColumn + return unless line.text.length > softWrapColumn - if /\s/.test(line[softWrapColumn]) + if /\s/.test(line.text[softWrapColumn]) # search forward for the start of a word past the boundary - for column in [softWrapColumn..line.length] - return column if /\S/.test(line[column]) - return line.length + for column in [softWrapColumn..line.text.length] + if /\S/.test(line.text[column]) + if line.tokens[0].isPhantom && column <= line.tokens[0].screenDelta + continue + + return column + return line.text.length else # search backward for the start of the word on the boundary for column in [softWrapColumn..0] - return column + 1 if /\s/.test(line[column]) + if /\s/.test(line.text[column]) + if line.tokens[0].isPhantom && column <= line.tokens[0].screenDelta + continue + + return column + 1 return softWrapColumn # Calculates a {Range} representing the start of the {TextBuffer} until the end. @@ -1163,8 +1171,9 @@ class DisplayBuffer extends Model bufferRow += foldedRowCount else softWraps = 0 - while wrapScreenColumn = @findWrapColumn(tokenizedLine.text) + while wrapScreenColumn = @findWrapColumn(tokenizedLine) [wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn) + break if wrappedLine.text == tokenizedLine.text screenLines.push(wrappedLine) softWraps++ screenLines.push(tokenizedLine) From 469876161a4a4e8844e3ea698586171928ceba4e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 12:53:09 +0100 Subject: [PATCH 05/42] Improve TokenizedLine#clipScreenColumn --- src/display-buffer.coffee | 5 +---- src/tokenized-line.coffee | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 9fea3c2bf..06bee4a34 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -841,10 +841,7 @@ class DisplayBuffer extends Model if screenLine.isSoftWrapped() and column >= maxScreenColumn if wrapAtSoftNewlines row++ - column = if @screenLines[row].tokens[0].isPhantom - @screenLines[row].tokens[0].screenDelta - else - 0 + column = @screenLines[row].clipScreenColumn(0) else column = screenLine.clipScreenColumn(maxScreenColumn - 1) else if screenLine.tokens[0].isPhantom and column < screenLine.tokens[0].screenDelta diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index b09e39e68..b8156967f 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -48,7 +48,9 @@ class TokenizedLine break if tokenStartColumn + token.screenDelta > column tokenStartColumn += token.screenDelta - if token.isAtomic and tokenStartColumn < column + if token.isPhantom and tokenStartColumn <= column + tokenStartColumn + token.screenDelta + else if token.isAtomic and tokenStartColumn < column if skipAtomicTokens tokenStartColumn + token.screenDelta else From abd143a25524b62c22805bdf68a3126558de1fb8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 14:33:37 +0100 Subject: [PATCH 06/42] Extract phantom token recognition into TokenizedLine --- src/display-buffer.coffee | 16 +++++----------- src/tokenized-line.coffee | 3 +++ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 06bee4a34..cc293fcbd 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -866,22 +866,16 @@ class DisplayBuffer extends Model return unless line.text.length > softWrapColumn if /\s/.test(line.text[softWrapColumn]) - # search forward for the start of a word past the boundary - for column in [softWrapColumn..line.text.length] - if /\S/.test(line.text[column]) - if line.tokens[0].isPhantom && column <= line.tokens[0].screenDelta - continue + # search forward for the start of a word past the boundary + for column in [softWrapColumn..line.text.length] when line.isOutsidePhantomToken(column) + return column if /\S/.test(line.text[column]) - return column return line.text.length else # search backward for the start of the word on the boundary - for column in [softWrapColumn..0] - if /\s/.test(line.text[column]) - if line.tokens[0].isPhantom && column <= line.tokens[0].screenDelta - continue + for column in [softWrapColumn..0] when line.isOutsidePhantomToken(column) + return column + 1 if /\s/.test(line.text[column]) - return column + 1 return softWrapColumn # Calculates a {Range} representing the start of the {TextBuffer} until the end. diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index b8156967f..3bf9407b3 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -124,6 +124,9 @@ class TokenizedLine isSoftWrapped: -> @lineEnding is null + isOutsidePhantomToken: (column) -> + !@tokens[0].isPhantom || column > @tokens[0].screenDelta + tokenAtBufferColumn: (bufferColumn) -> @tokens[@tokenIndexAtBufferColumn(bufferColumn)] From 131048af657f16e1d87f9342226680851357694d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 15:11:57 +0100 Subject: [PATCH 07/42] Use intention-revealing method names --- src/display-buffer.coffee | 2 +- src/tokenized-line.coffee | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index cc293fcbd..8b30851fd 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -844,7 +844,7 @@ class DisplayBuffer extends Model column = @screenLines[row].clipScreenColumn(0) else column = screenLine.clipScreenColumn(maxScreenColumn - 1) - else if screenLine.tokens[0].isPhantom and column < screenLine.tokens[0].screenDelta + else if screenLine.isInsidePhantomToken(column) row-- column = @screenLines[row].getMaxScreenColumn() else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 3bf9407b3..446556e0e 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -125,7 +125,10 @@ class TokenizedLine @lineEnding is null isOutsidePhantomToken: (column) -> - !@tokens[0].isPhantom || column > @tokens[0].screenDelta + @tokens[0].isPhantom && column > @tokens[0].screenDelta + + isInsidePhantomToken: (column) -> + @tokens[0].isPhantom && column < @tokens[0].screenDelta tokenAtBufferColumn: (bufferColumn) -> @tokens[@tokenIndexAtBufferColumn(bufferColumn)] From 38118b66cbfbfec97aef4968714e6cfc35f576e7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 15:47:17 +0100 Subject: [PATCH 08/42] Do not rely on text anymore, check phantom tokens instead --- src/display-buffer.coffee | 4 ++-- src/tokenized-line.coffee | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 8b30851fd..89026ed67 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -867,7 +867,7 @@ class DisplayBuffer extends Model if /\s/.test(line.text[softWrapColumn]) # search forward for the start of a word past the boundary - for column in [softWrapColumn..line.text.length] when line.isOutsidePhantomToken(column) + for column in [softWrapColumn..line.text.length] return column if /\S/.test(line.text[column]) return line.text.length @@ -1164,7 +1164,7 @@ class DisplayBuffer extends Model softWraps = 0 while wrapScreenColumn = @findWrapColumn(tokenizedLine) [wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn) - break if wrappedLine.text == tokenizedLine.text + break if wrappedLine.hasOnlyPhantomTokens() screenLines.push(wrappedLine) softWraps++ screenLines.push(tokenizedLine) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 446556e0e..d88610ef7 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -130,6 +130,9 @@ class TokenizedLine isInsidePhantomToken: (column) -> @tokens[0].isPhantom && column < @tokens[0].screenDelta + hasOnlyPhantomTokens: -> + @tokens.length == 1 && @tokens[0].isPhantom + tokenAtBufferColumn: (bufferColumn) -> @tokens[@tokenIndexAtBufferColumn(bufferColumn)] From e62e26a3c22205d6e2e1e69b078e685b56b90973 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 17:45:57 +0100 Subject: [PATCH 09/42] Wrap on words, not chars --- src/tokenized-line.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index d88610ef7..2694da93b 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -125,7 +125,7 @@ class TokenizedLine @lineEnding is null isOutsidePhantomToken: (column) -> - @tokens[0].isPhantom && column > @tokens[0].screenDelta + !@tokens[0].isPhantom || column > @tokens[0].screenDelta isInsidePhantomToken: (column) -> @tokens[0].isPhantom && column < @tokens[0].screenDelta From 88ca44d53b06c3751fefb6695f0dbc07d299258f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Feb 2015 17:56:36 +0100 Subject: [PATCH 10/42] Use guard clauses, instead of crappy ANDs --- src/tokenized-line.coffee | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 2694da93b..3878cf181 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -125,10 +125,14 @@ class TokenizedLine @lineEnding is null isOutsidePhantomToken: (column) -> - !@tokens[0].isPhantom || column > @tokens[0].screenDelta + return true unless @tokens[0].isPhantom + + column > @tokens[0].screenDelta isInsidePhantomToken: (column) -> - @tokens[0].isPhantom && column < @tokens[0].screenDelta + return false unless @tokens[0].isPhantom + + column < @tokens[0].screenDelta hasOnlyPhantomTokens: -> @tokens.length == 1 && @tokens[0].isPhantom From 5e4221f91cd7b0072e99e466d56bbd39dfba1f0a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Feb 2015 09:46:03 +0100 Subject: [PATCH 11/42] Don't show leading phantom tokens ...thanks @grizzilus :smile: --- src/token.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token.coffee b/src/token.coffee index d3e17a570..2c82fa23f 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -229,7 +229,7 @@ class Token else match hasLeadingWhitespace: -> - @firstNonWhitespaceIndex? and @firstNonWhitespaceIndex > 0 + !@isPhantom and @firstNonWhitespaceIndex? and @firstNonWhitespaceIndex > 0 hasTrailingWhitespace: -> @firstTrailingWhitespaceIndex? and @firstTrailingWhitespaceIndex < @value.length From b0c670b80afe9c4034f8e9ff78a1b7f048324fc2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Feb 2015 09:52:23 +0100 Subject: [PATCH 12/42] Better naming ...thanks @nathansobo --- src/display-buffer.coffee | 4 ++-- src/token.coffee | 2 +- src/tokenized-line.coffee | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 89026ed67..da86e5d2c 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -844,7 +844,7 @@ class DisplayBuffer extends Model column = @screenLines[row].clipScreenColumn(0) else column = screenLine.clipScreenColumn(maxScreenColumn - 1) - else if screenLine.isInsidePhantomToken(column) + else if screenLine.isColumnInsidePhantomToken(column) row-- column = @screenLines[row].getMaxScreenColumn() else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() @@ -873,7 +873,7 @@ class DisplayBuffer extends Model return line.text.length else # search backward for the start of the word on the boundary - for column in [softWrapColumn..0] when line.isOutsidePhantomToken(column) + for column in [softWrapColumn..0] when line.isColumnOutsidePhantomToken(column) return column + 1 if /\s/.test(line.text[column]) return softWrapColumn diff --git a/src/token.coffee b/src/token.coffee index 2c82fa23f..76386fb85 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -144,7 +144,7 @@ class Token isHardTab: isHardTab ) - buildPhantomToken: (length) -> + buildPhantomTabToken: (length) -> new Token( value: _.multiplyString(" ", length), scopes: @scopes, diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 3878cf181..d40352303 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -99,7 +99,7 @@ class TokenizedLine leftTextLength += nextToken.value.length leftTokens.push nextToken - tab = leftTokens[0].buildPhantomToken(@indentLevel * @tabLength) + phantomTab = leftTokens[0].buildPhantomTabToken(@indentLevel * @tabLength) leftFragment = new TokenizedLine( tokens: leftTokens @@ -111,7 +111,7 @@ class TokenizedLine tabLength: @tabLength ) rightFragment = new TokenizedLine( - tokens: [tab].concat(rightTokens) + tokens: [phantomTab].concat(rightTokens) startBufferColumn: @bufferColumnForScreenColumn(column) ruleStack: @ruleStack invisibles: @invisibles @@ -124,12 +124,12 @@ class TokenizedLine isSoftWrapped: -> @lineEnding is null - isOutsidePhantomToken: (column) -> + isColumnOutsidePhantomToken: (column) -> return true unless @tokens[0].isPhantom column > @tokens[0].screenDelta - isInsidePhantomToken: (column) -> + isColumnInsidePhantomToken: (column) -> return false unless @tokens[0].isPhantom column < @tokens[0].screenDelta From 62434d9ad55baf137f30d34d68c13553d0eb2afe Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Feb 2015 10:05:43 +0100 Subject: [PATCH 13/42] Move `findWrapColumn` into `TokenizedLine` * :racehorse: Check `isSoftWrapped` only once when updating screen lines --- src/display-buffer.coffee | 35 ++++++----------------------------- src/tokenized-line.coffee | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index da86e5d2c..a092d2c8d 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -854,30 +854,6 @@ class DisplayBuffer extends Model column = screenLine.clipScreenColumn(column, options) new Point(row, column) - # Given a line, finds the point where it would wrap. - # - # line - The {String} to check - # softWrapColumn - The {Number} where you want soft wrapping to occur - # - # Returns a {Number} representing the `line` position where the wrap would take place. - # Returns `null` if a wrap wouldn't occur. - findWrapColumn: (line, softWrapColumn=@getSoftWrapColumn()) -> - return unless @isSoftWrapped() - return unless line.text.length > softWrapColumn - - if /\s/.test(line.text[softWrapColumn]) - # search forward for the start of a word past the boundary - for column in [softWrapColumn..line.text.length] - return column if /\S/.test(line.text[column]) - - return line.text.length - else - # search backward for the start of the word on the boundary - for column in [softWrapColumn..0] when line.isColumnOutsidePhantomToken(column) - return column + 1 if /\s/.test(line.text[column]) - - return softWrapColumn - # Calculates a {Range} representing the start of the {TextBuffer} until the end. # # Returns a {Range}. @@ -1162,11 +1138,12 @@ class DisplayBuffer extends Model bufferRow += foldedRowCount else softWraps = 0 - while wrapScreenColumn = @findWrapColumn(tokenizedLine) - [wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn) - break if wrappedLine.hasOnlyPhantomTokens() - screenLines.push(wrappedLine) - softWraps++ + if @isSoftWrapped() + while wrapScreenColumn = tokenizedLine.findWrapColumn(@getSoftWrapColumn()) + [wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn) + break if wrappedLine.hasOnlyPhantomTokens() + screenLines.push(wrappedLine) + softWraps++ screenLines.push(tokenizedLine) if softWraps > 0 diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index d40352303..c817592e6 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -86,6 +86,28 @@ class TokenizedLine getMaxBufferColumn: -> @startBufferColumn + @bufferDelta + # Given a boundary column, finds the point where this line would wrap. + # + # maxColumn - The {Number} where you want soft wrapping to occur + # + # Returns a {Number} representing the `line` position where the wrap would take place. + # Returns `null` if a wrap wouldn't occur. + findWrapColumn: (maxColumn) -> + return unless @text.length > maxColumn + + if /\s/.test(@text[maxColumn]) + # search forward for the start of a word past the boundary + for column in [maxColumn..@text.length] + return column if /\S/.test(@text[column]) + + return @text.length + else + # search backward for the start of the word on the boundary + for column in [maxColumn..0] when @isColumnOutsidePhantomToken(column) + return column + 1 if /\s/.test(@text[column]) + + return maxColumn + softWrapAt: (column) -> return [new TokenizedLine([], '', [0, 0], [0, 0]), this] if column == 0 From e5eaa2538189ad2424fc13e6b204c10460026be5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Feb 2015 18:58:43 +0100 Subject: [PATCH 14/42] Wrap at phantom tokens only when moving cursor left This prevents a weird behavior that affects selection. Before this commit, when moving the cursor down, if it was inside a real (soft|hard)-tab of a soft wrapped line, it would reach the end of the line rather than the beginning of the next wrapped line. /cc: @nathansobo --- src/cursor.coffee | 2 +- src/display-buffer.coffee | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cursor.coffee b/src/cursor.coffee index bfd6de536..e62f3a472 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -310,7 +310,7 @@ class Cursor extends Model columnCount-- # subtract 1 for the row move column = column - columnCount - @setScreenPosition({row, column}) + @setScreenPosition({row, column}, wrapAtPhantomTokens: true) # Public: Moves the cursor right one screen column. # diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index a092d2c8d..39a54326c 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -819,11 +819,12 @@ class DisplayBuffer extends Model # options - A hash with the following values: # wrapBeyondNewlines: if `true`, continues wrapping past newlines # wrapAtSoftNewlines: if `true`, continues wrapping past soft newlines + # wrapAtPhantomTokens: if `true`, continues wrapping before phantom tokens # screenLine: if `true`, indicates that you're using a line number, not a row number # # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. clipScreenPosition: (screenPosition, options={}) -> - { wrapBeyondNewlines, wrapAtSoftNewlines } = options + { wrapBeyondNewlines, wrapAtSoftNewlines, wrapAtPhantomTokens } = options { row, column } = Point.fromObject(screenPosition) if row < 0 @@ -845,8 +846,11 @@ class DisplayBuffer extends Model else column = screenLine.clipScreenColumn(maxScreenColumn - 1) else if screenLine.isColumnInsidePhantomToken(column) - row-- - column = @screenLines[row].getMaxScreenColumn() + if wrapAtPhantomTokens + row-- + column = @screenLines[row].getMaxScreenColumn() + else + column = screenLine.clipScreenColumn(0) else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() row++ column = 0 From bef1e862cc1079a410878a0da90e9fa222968d91 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Feb 2015 21:18:37 +0100 Subject: [PATCH 15/42] :white_check_mark: Fix failing specs (and add new ones) When fixing failing specs, I adapted existing expectations to match the "soft wrap indenting" behaviour: this means that there's no need to write a new spec for it. --- spec/display-buffer-spec.coffee | 46 +++++++++++++++++++------------ spec/text-editor-spec.coffee | 14 ++++++++-- spec/tokenized-buffer-spec.coffee | 4 +-- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index cd1cc92cb..a7ef2528a 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -75,7 +75,7 @@ describe "DisplayBuffer", -> expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return ' atom.config.set('editor.preferredLineLength', 5) - expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe 'funct' + expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' fun' atom.config.set('editor.softWrapAtPreferredLineLength', false) expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return ' @@ -92,7 +92,7 @@ describe "DisplayBuffer", -> describe "when there is whitespace before the boundary", -> it "wraps the line at the end of the first whitespace preceding the boundary", -> expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return ' - expect(displayBuffer.tokenizedLineForScreenRow(11).text).toBe 'sort(left).concat(pivot).concat(sort(right));' + expect(displayBuffer.tokenizedLineForScreenRow(11).text).toBe ' sort(left).concat(pivot).concat(sort(right));' describe "when there is no whitespace before the boundary", -> it "wraps the line exactly at the boundary since there's no more graceful place to wrap it", -> @@ -105,7 +105,7 @@ describe "DisplayBuffer", -> describe "when there is a whitespace character at the max length boundary", -> it "wraps the line at the first non-whitespace character following the boundary", -> expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe ' var pivot = items.shift(), current, left = [], ' - expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe 'right = [];' + expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe ' right = [];' describe "when there are hard tabs", -> beforeEach -> @@ -138,8 +138,8 @@ describe "DisplayBuffer", -> it "rewraps the line and emits a change event", -> buffer.insert([6, 28], '1234567890') expect(displayBuffer.tokenizedLineForScreenRow(7).text).toBe ' current < pivot ? ' - expect(displayBuffer.tokenizedLineForScreenRow(8).text).toBe 'left1234567890.push(current) : ' - expect(displayBuffer.tokenizedLineForScreenRow(9).text).toBe 'right.push(current);' + expect(displayBuffer.tokenizedLineForScreenRow(8).text).toBe ' left1234567890.push(current) : ' + expect(displayBuffer.tokenizedLineForScreenRow(9).text).toBe ' right.push(current);' expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' }' expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, screenDelta: 1, bufferDelta: 0) @@ -148,7 +148,7 @@ describe "DisplayBuffer", -> it "inserts / updates wrapped lines and emits a change event", -> buffer.insert([6, 21], '1234567890 abcdefghij 1234567890\nabcdefghij') expect(displayBuffer.tokenizedLineForScreenRow(7).text).toBe ' current < pivot1234567890 abcdefghij ' - expect(displayBuffer.tokenizedLineForScreenRow(8).text).toBe '1234567890' + expect(displayBuffer.tokenizedLineForScreenRow(8).text).toBe ' 1234567890' expect(displayBuffer.tokenizedLineForScreenRow(9).text).toBe 'abcdefghij ? left.push(current) : ' expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe 'right.push(current);' @@ -159,7 +159,7 @@ describe "DisplayBuffer", -> buffer.setTextInRange([[3, 21], [7, 5]], ';') expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe ' var pivot = items;' expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe ' return ' - expect(displayBuffer.tokenizedLineForScreenRow(5).text).toBe 'sort(left).concat(pivot).concat(sort(right));' + expect(displayBuffer.tokenizedLineForScreenRow(5).text).toBe ' sort(left).concat(pivot).concat(sort(right));' expect(displayBuffer.tokenizedLineForScreenRow(6).text).toBe ' };' expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 9, screenDelta: -6, bufferDelta: -4) @@ -193,8 +193,8 @@ describe "DisplayBuffer", -> expect(displayBuffer.screenPositionForBufferPosition([3, 51])).toEqual([3, 50]) expect(displayBuffer.bufferPositionForScreenPosition([4, 0])).toEqual([3, 51]) expect(displayBuffer.bufferPositionForScreenPosition([3, 50])).toEqual([3, 50]) - expect(displayBuffer.screenPositionForBufferPosition([3, 62])).toEqual([4, 11]) - expect(displayBuffer.bufferPositionForScreenPosition([4, 11])).toEqual([3, 62]) + expect(displayBuffer.screenPositionForBufferPosition([3, 62])).toEqual([4, 15]) + expect(displayBuffer.bufferPositionForScreenPosition([4, 11])).toEqual([3, 58]) # following a wrapped line expect(displayBuffer.screenPositionForBufferPosition([4, 5])).toEqual([5, 5]) @@ -209,9 +209,9 @@ describe "DisplayBuffer", -> describe ".setEditorWidthInChars(length)", -> it "changes the length at which lines are wrapped and emits a change event for all screen lines", -> displayBuffer.setEditorWidthInChars(40) - expect(tokensText displayBuffer.tokenizedLineForScreenRow(4).tokens).toBe 'left = [], right = [];' + expect(tokensText displayBuffer.tokenizedLineForScreenRow(4).tokens).toBe ' left = [], right = [];' expect(tokensText displayBuffer.tokenizedLineForScreenRow(5).tokens).toBe ' while(items.length > 0) {' - expect(tokensText displayBuffer.tokenizedLineForScreenRow(12).tokens).toBe 'sort(left).concat(pivot).concat(sort(rig' + expect(tokensText displayBuffer.tokenizedLineForScreenRow(12).tokens).toBe ' sort(left).concat(pivot).concat(sort' expect(changeHandler).toHaveBeenCalledWith(start: 0, end: 15, screenDelta: 3, bufferDelta: 0) it "only allows positive widths to be assigned", -> @@ -594,11 +594,23 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([1000, 0])).toEqual [15, 2] expect(displayBuffer.clipScreenPosition([1000, 1000])).toEqual [15, 2] + describe "when wrapAtPhantomTokens is false (the default)", -> + it "clips positions inside a phantom token to the beginning of the line", -> + expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [4, 4] + + describe "when wrapAtPhantomTokens is true", -> + it "wraps positions inside a phantom token to the previous line", -> + expect(displayBuffer.clipScreenPosition([4, 0], wrapAtPhantomTokens: true)).toEqual [3, 51] + expect(displayBuffer.clipScreenPosition([4, 1], wrapAtPhantomTokens: true)).toEqual [3, 51] + expect(displayBuffer.clipScreenPosition([4, 3], wrapAtPhantomTokens: true)).toEqual [3, 51] + describe "when wrapBeyondNewlines is false (the default)", -> it "wraps positions beyond the end of hard newlines to the end of the line", -> expect(displayBuffer.clipScreenPosition([1, 10000])).toEqual [1, 30] - expect(displayBuffer.clipScreenPosition([4, 30])).toEqual [4, 11] - expect(displayBuffer.clipScreenPosition([4, 1000])).toEqual [4, 11] + expect(displayBuffer.clipScreenPosition([4, 30])).toEqual [4, 15] + expect(displayBuffer.clipScreenPosition([4, 1000])).toEqual [4, 15] describe "when wrapBeyondNewlines is true", -> it "wraps positions past the end of hard newlines to the next line", -> @@ -620,9 +632,9 @@ describe "DisplayBuffer", -> describe "when wrapAtSoftNewlines is true", -> it "wraps positions at the end of soft-wrapped lines to the next screen line", -> expect(displayBuffer.clipScreenPosition([3, 50], wrapAtSoftNewlines: true)).toEqual [3, 50] - expect(displayBuffer.clipScreenPosition([3, 51], wrapAtSoftNewlines: true)).toEqual [4, 0] - expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 0] - expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 0] + expect(displayBuffer.clipScreenPosition([3, 51], wrapAtSoftNewlines: true)).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 4] describe "when skipAtomicTokens is false (the default)", -> it "clips screen positions in the middle of atomic tab characters to the beginning of the character", -> @@ -655,7 +667,7 @@ describe "DisplayBuffer", -> buffer.setText('\t\taa bb cc dd ee ff gg') displayBuffer.setSoftWrapped(true) displayBuffer.setEditorWidthInChars(10) - expect(displayBuffer.screenPositionForBufferPosition([0, 10], wrapAtSoftNewlines: true)).toEqual [1, 0] + expect(displayBuffer.screenPositionForBufferPosition([0, 10], wrapAtSoftNewlines: true)).toEqual [1, 4] expect(displayBuffer.bufferPositionForScreenPosition([1, 0])).toEqual [0, 10] describe "::getMaxLineLength()", -> diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index fe12d6532..f45f3063f 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -432,6 +432,16 @@ describe "TextEditor", -> editor.moveLeft() expect(editor.getCursorScreenPosition()).toEqual [10, 0] + describe "when line is wrapped and follow previous line indentation", -> + beforeEach -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(50) + + it "wraps to the end of the previous line", -> + editor.setCursorScreenPosition([4, 4]) + editor.moveLeft() + expect(editor.getCursorScreenPosition()).toEqual [3, 50] + describe "when the cursor is on the first line", -> it "remains in the same position (0,0)", -> editor.setCursorScreenPosition(row: 0, column: 0) @@ -628,11 +638,11 @@ describe "TextEditor", -> editor.moveToFirstCharacterOfLine() [cursor1, cursor2] = editor.getCursors() expect(cursor1.getScreenPosition()).toEqual [2,0] - expect(cursor2.getScreenPosition()).toEqual [8,4] + expect(cursor2.getScreenPosition()).toEqual [8,2] editor.moveToFirstCharacterOfLine() expect(cursor1.getScreenPosition()).toEqual [2,0] - expect(cursor2.getScreenPosition()).toEqual [8,0] + expect(cursor2.getScreenPosition()).toEqual [8,2] describe "when soft wrap is off", -> it "moves to the first character of the current line or the beginning of the line if it's already on the first character", -> diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 41a63a562..103a54b85 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -670,8 +670,8 @@ describe "TokenizedBuffer", -> [segment1, segment2] = tokenizedLine.softWrapAt(16) expect(segment1.tokens[5].value).toBe ' ' expect(segment1.tokens[5].firstTrailingWhitespaceIndex).toBe null - expect(segment2.tokens[6].value).toBe ' ' - expect(segment2.tokens[6].firstTrailingWhitespaceIndex).toBe 0 + expect(segment2.tokens[7].value).toBe ' ' + expect(segment2.tokens[7].firstTrailingWhitespaceIndex).toBe 0 it "sets leading and trailing whitespace correctly on a line with invisible characters that is copied", -> buffer.setText(" \t a line with tabs\tand \tspaces \t ") From 8184ad9a77d1c1564740e7e4818a1ea6e624a786 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Feb 2015 21:31:00 +0100 Subject: [PATCH 16/42] :white_check_mark: Ensure phantom tabs are correctly tokenized --- spec/display-buffer-spec.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index a7ef2528a..3110c2ffb 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -115,6 +115,11 @@ describe "DisplayBuffer", -> expect(displayBuffer.tokenizedLineForScreenRow(3).tokens[0].isHardTab).toBeTruthy() expect(displayBuffer.tokenizedLineForScreenRow(3).tokens[1].isHardTab).toBeTruthy() + describe "when a line is wrapped", -> + it "correctly tokenizes the phantom tokens", -> + expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[0].isPhantom).toBeTruthy() + expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[0].hasLeadingWhitespace()).toBeFalsy() + describe "when the buffer changes", -> describe "when buffer lines are updated", -> describe "when whitespace is added after the max line length", -> From 85e202ee0a53a9c720aa803810b4455e165fb4d4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Feb 2015 16:08:44 +0100 Subject: [PATCH 17/42] Get rid of `wrapAtPhantomTokens` --- spec/display-buffer-spec.coffee | 22 ++++++++++------------ src/cursor.coffee | 3 ++- src/display-buffer.coffee | 7 +++---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 3110c2ffb..73a424a34 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -599,18 +599,6 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([1000, 0])).toEqual [15, 2] expect(displayBuffer.clipScreenPosition([1000, 1000])).toEqual [15, 2] - describe "when wrapAtPhantomTokens is false (the default)", -> - it "clips positions inside a phantom token to the beginning of the line", -> - expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [4, 4] - expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [4, 4] - expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [4, 4] - - describe "when wrapAtPhantomTokens is true", -> - it "wraps positions inside a phantom token to the previous line", -> - expect(displayBuffer.clipScreenPosition([4, 0], wrapAtPhantomTokens: true)).toEqual [3, 51] - expect(displayBuffer.clipScreenPosition([4, 1], wrapAtPhantomTokens: true)).toEqual [3, 51] - expect(displayBuffer.clipScreenPosition([4, 3], wrapAtPhantomTokens: true)).toEqual [3, 51] - describe "when wrapBeyondNewlines is false (the default)", -> it "wraps positions beyond the end of hard newlines to the end of the line", -> expect(displayBuffer.clipScreenPosition([1, 10000])).toEqual [1, 30] @@ -634,6 +622,11 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([3, 58])).toEqual [3, 50] expect(displayBuffer.clipScreenPosition([3, 1000])).toEqual [3, 50] + it "clips positions inside a phantom token to the beginning of the line", -> + expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [4, 4] + describe "when wrapAtSoftNewlines is true", -> it "wraps positions at the end of soft-wrapped lines to the next screen line", -> expect(displayBuffer.clipScreenPosition([3, 50], wrapAtSoftNewlines: true)).toEqual [3, 50] @@ -641,6 +634,11 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 4] expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 4] + it "wraps positions inside a phantom token to the previous line", -> + expect(displayBuffer.clipScreenPosition([4, 0], wrapAtSoftNewlines: true)).toEqual [3, 50] + expect(displayBuffer.clipScreenPosition([4, 1], wrapAtSoftNewlines: true)).toEqual [3, 50] + expect(displayBuffer.clipScreenPosition([4, 3], wrapAtSoftNewlines: true)).toEqual [3, 50] + describe "when skipAtomicTokens is false (the default)", -> it "clips screen positions in the middle of atomic tab characters to the beginning of the character", -> buffer.insert([0, 0], '\t') diff --git a/src/cursor.coffee b/src/cursor.coffee index e62f3a472..589dfca08 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -304,13 +304,14 @@ class Cursor extends Model else {row, column} = @getScreenPosition() + originalRow = row while columnCount > column and row > 0 columnCount -= column column = @editor.lineTextForScreenRow(--row).length columnCount-- # subtract 1 for the row move column = column - columnCount - @setScreenPosition({row, column}, wrapAtPhantomTokens: true) + @setScreenPosition({row, column}, wrapAtSoftNewlines: originalRow == row) # Public: Moves the cursor right one screen column. # diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 39a54326c..053863472 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -819,12 +819,11 @@ class DisplayBuffer extends Model # options - A hash with the following values: # wrapBeyondNewlines: if `true`, continues wrapping past newlines # wrapAtSoftNewlines: if `true`, continues wrapping past soft newlines - # wrapAtPhantomTokens: if `true`, continues wrapping before phantom tokens # screenLine: if `true`, indicates that you're using a line number, not a row number # # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. clipScreenPosition: (screenPosition, options={}) -> - { wrapBeyondNewlines, wrapAtSoftNewlines, wrapAtPhantomTokens } = options + { wrapBeyondNewlines, wrapAtSoftNewlines } = options { row, column } = Point.fromObject(screenPosition) if row < 0 @@ -846,9 +845,9 @@ class DisplayBuffer extends Model else column = screenLine.clipScreenColumn(maxScreenColumn - 1) else if screenLine.isColumnInsidePhantomToken(column) - if wrapAtPhantomTokens + if wrapAtSoftNewlines row-- - column = @screenLines[row].getMaxScreenColumn() + column = @screenLines[row].getMaxScreenColumn() - 1 else column = screenLine.clipScreenColumn(0) else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() From 67ef17e0c85e63bb6a1fb56980718a78507006dd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Feb 2015 16:20:27 +0100 Subject: [PATCH 18/42] :green_heart: Fix soft wrapping scroll spec --- spec/display-buffer-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 73a424a34..66dc070d6 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -227,7 +227,7 @@ describe "DisplayBuffer", -> it "sets ::scrollLeft to 0 and keeps it there when soft wrapping is enabled", -> displayBuffer.setDefaultCharWidth(10) - displayBuffer.setWidth(50) + displayBuffer.setWidth(85) displayBuffer.manageScrollPosition = true displayBuffer.setSoftWrapped(false) From ac6a7bb47f5a43a1b3d8ddcb4c8db883c86f9ddd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Feb 2015 18:06:20 +0100 Subject: [PATCH 19/42] Rename to `Token#buildSoftWrapIndentToken` --- src/token.coffee | 2 +- src/tokenized-line.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/token.coffee b/src/token.coffee index 76386fb85..4f17bb658 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -144,7 +144,7 @@ class Token isHardTab: isHardTab ) - buildPhantomTabToken: (length) -> + buildSoftWrapIndentToken: (length) -> new Token( value: _.multiplyString(" ", length), scopes: @scopes, diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index c817592e6..3809f46f7 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -121,7 +121,7 @@ class TokenizedLine leftTextLength += nextToken.value.length leftTokens.push nextToken - phantomTab = leftTokens[0].buildPhantomTabToken(@indentLevel * @tabLength) + phantomTab = leftTokens[0].buildSoftWrapIndentToken(@indentLevel * @tabLength) leftFragment = new TokenizedLine( tokens: leftTokens From d3b7ea475ff24169019056eaa231dce9967f958f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Feb 2015 18:58:32 +0100 Subject: [PATCH 20/42] Rename `phantomTab` to `indentToken` --- src/tokenized-line.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 3809f46f7..932b15cd1 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -121,7 +121,7 @@ class TokenizedLine leftTextLength += nextToken.value.length leftTokens.push nextToken - phantomTab = leftTokens[0].buildSoftWrapIndentToken(@indentLevel * @tabLength) + indentToken = leftTokens[0].buildSoftWrapIndentToken(@indentLevel * @tabLength) leftFragment = new TokenizedLine( tokens: leftTokens @@ -133,7 +133,7 @@ class TokenizedLine tabLength: @tabLength ) rightFragment = new TokenizedLine( - tokens: [phantomTab].concat(rightTokens) + tokens: [indentToken].concat(rightTokens) startBufferColumn: @bufferColumnForScreenColumn(column) ruleStack: @ruleStack invisibles: @invisibles From 79c16a0d00b0a2412e9cfee1f04700e21924ac8c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Feb 2015 20:01:37 +0100 Subject: [PATCH 21/42] Use softWrapAtNewLines in moveDown and moveUp /cc: @nathansobo --- spec/display-buffer-spec.coffee | 16 ++++++++-------- spec/text-editor-spec.coffee | 21 +++++++++++++++++++++ src/cursor.coffee | 7 +++---- src/display-buffer.coffee | 4 ++-- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 66dc070d6..4b6219faa 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -622,10 +622,10 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([3, 58])).toEqual [3, 50] expect(displayBuffer.clipScreenPosition([3, 1000])).toEqual [3, 50] - it "clips positions inside a phantom token to the beginning of the line", -> - expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [4, 4] - expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [4, 4] - expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [4, 4] + it "wraps positions at the end of previous soft-wrapped line", -> + expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [3, 50] + expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [3, 50] + expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [3, 50] describe "when wrapAtSoftNewlines is true", -> it "wraps positions at the end of soft-wrapped lines to the next screen line", -> @@ -634,10 +634,10 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 4] expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 4] - it "wraps positions inside a phantom token to the previous line", -> - expect(displayBuffer.clipScreenPosition([4, 0], wrapAtSoftNewlines: true)).toEqual [3, 50] - expect(displayBuffer.clipScreenPosition([4, 1], wrapAtSoftNewlines: true)).toEqual [3, 50] - expect(displayBuffer.clipScreenPosition([4, 3], wrapAtSoftNewlines: true)).toEqual [3, 50] + it "clips positions to the beginning of the line", -> + expect(displayBuffer.clipScreenPosition([4, 0], wrapAtSoftNewlines: true)).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 1], wrapAtSoftNewlines: true)).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 3], wrapAtSoftNewlines: true)).toEqual [4, 4] describe "when skipAtomicTokens is false (the default)", -> it "clips screen positions in the middle of atomic tab characters to the beginning of the character", -> diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index f45f3063f..028ad8677 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -334,6 +334,17 @@ describe "TextEditor", -> expect(editor.getCursors()).toEqual [cursor1] expect(cursor1.getBufferPosition()).toEqual [0,0] + describe "when the cursor was moved down from the beginning of an indented soft-wrapped line", -> + it "moves to the beginning of the previous line", -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(50) + + editor.setCursorScreenPosition([3, 0]) + editor.moveDown() + editor.moveDown() + editor.moveUp() + expect(editor.getCursorScreenPosition()).toEqual [4, 4] + describe ".moveDown()", -> it "moves the cursor down", -> editor.setCursorScreenPosition([2, 2]) @@ -375,6 +386,16 @@ describe "TextEditor", -> editor.moveUp() expect(editor.getCursorScreenPosition().column).toBe 0 + describe "when the cursor is at the beginning of an indented soft-wrapped line", -> + it "moves to the beginning of the line's continuation on the next screen row", -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(50) + + editor.setCursorScreenPosition([3, 0]) + editor.moveDown() + expect(editor.getCursorScreenPosition()).toEqual [4, 4] + + describe "when there is a selection", -> beforeEach -> editor.setSelectedBufferRange([[4, 9],[5, 10]]) diff --git a/src/cursor.coffee b/src/cursor.coffee index 589dfca08..ee7aee5a4 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -271,7 +271,7 @@ class Cursor extends Model { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? - @setScreenPosition({row: row - rowCount, column: column}) + @setScreenPosition({row: row - rowCount, column: column}, wrapAtSoftNewlines: true) @goalColumn = column # Public: Moves the cursor down one screen row. @@ -288,7 +288,7 @@ class Cursor extends Model { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? - @setScreenPosition({row: row + rowCount, column: column}) + @setScreenPosition({row: row + rowCount, column: column}, wrapAtSoftNewlines: true) @goalColumn = column # Public: Moves the cursor left one screen column. @@ -304,14 +304,13 @@ class Cursor extends Model else {row, column} = @getScreenPosition() - originalRow = row while columnCount > column and row > 0 columnCount -= column column = @editor.lineTextForScreenRow(--row).length columnCount-- # subtract 1 for the row move column = column - columnCount - @setScreenPosition({row, column}, wrapAtSoftNewlines: originalRow == row) + @setScreenPosition({row, column}) # Public: Moves the cursor right one screen column. # diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 053863472..f5b6145a0 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -846,10 +846,10 @@ class DisplayBuffer extends Model column = screenLine.clipScreenColumn(maxScreenColumn - 1) else if screenLine.isColumnInsidePhantomToken(column) if wrapAtSoftNewlines + column = screenLine.clipScreenColumn(0) + else row-- column = @screenLines[row].getMaxScreenColumn() - 1 - else - column = screenLine.clipScreenColumn(0) else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow() row++ column = 0 From 59cc10a1eef45b92220abad9cc92278cdd5da8c9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Feb 2015 21:07:14 +0100 Subject: [PATCH 22/42] Bring in @nathansobo PR changes ...as they were needed to pass specs --- spec/display-buffer-spec.coffee | 4 ++-- spec/text-editor-spec.coffee | 2 +- src/cursor.coffee | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 4b6219faa..75c256aec 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -196,7 +196,7 @@ describe "DisplayBuffer", -> expect(displayBuffer.bufferPositionForScreenPosition([3, 5])).toEqual([3, 5]) expect(displayBuffer.screenPositionForBufferPosition([3, 50])).toEqual([3, 50]) expect(displayBuffer.screenPositionForBufferPosition([3, 51])).toEqual([3, 50]) - expect(displayBuffer.bufferPositionForScreenPosition([4, 0])).toEqual([3, 51]) + expect(displayBuffer.bufferPositionForScreenPosition([4, 0])).toEqual([3, 50]) expect(displayBuffer.bufferPositionForScreenPosition([3, 50])).toEqual([3, 50]) expect(displayBuffer.screenPositionForBufferPosition([3, 62])).toEqual([4, 15]) expect(displayBuffer.bufferPositionForScreenPosition([4, 11])).toEqual([3, 58]) @@ -671,7 +671,7 @@ describe "DisplayBuffer", -> displayBuffer.setSoftWrapped(true) displayBuffer.setEditorWidthInChars(10) expect(displayBuffer.screenPositionForBufferPosition([0, 10], wrapAtSoftNewlines: true)).toEqual [1, 4] - expect(displayBuffer.bufferPositionForScreenPosition([1, 0])).toEqual [0, 10] + expect(displayBuffer.bufferPositionForScreenPosition([1, 0])).toEqual [0, 9] describe "::getMaxLineLength()", -> it "returns the length of the longest screen line", -> diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 028ad8677..0a6c14055 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -287,7 +287,7 @@ describe "TextEditor", -> it "positions the cursor at the buffer position that corresponds to the given screen position", -> editor.setCursorScreenPosition([9, 0]) - expect(editor.getCursorBufferPosition()).toEqual [8, 11] + expect(editor.getCursorBufferPosition()).toEqual [8, 10] describe ".moveUp()", -> it "moves the cursor up", -> diff --git a/src/cursor.coffee b/src/cursor.coffee index ee7aee5a4..969681ef0 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -359,19 +359,21 @@ class Cursor extends Model # line. moveToFirstCharacterOfLine: -> screenRow = @getScreenRow() - lineBufferRange = @editor.bufferRangeForScreenRange([[screenRow, 0], [screenRow, Infinity]]) + screenLineStart = @editor.clipScreenPosition([screenRow, 0], wrapAtSoftNewlines: true) + screenLineEnd = [screenRow, Infinity] + screenLineBufferRange = @editor.bufferRangeForScreenRange([screenLineStart, screenLineEnd]) firstCharacterColumn = null - @editor.scanInBufferRange /\S/, lineBufferRange, ({range, stop}) -> + @editor.scanInBufferRange /\S/, screenLineBufferRange, ({range, stop}) -> firstCharacterColumn = range.start.column stop() if firstCharacterColumn? and firstCharacterColumn isnt @getBufferColumn() targetBufferColumn = firstCharacterColumn else - targetBufferColumn = lineBufferRange.start.column + targetBufferColumn = screenLineBufferRange.start.column - @setBufferPosition([lineBufferRange.start.row, targetBufferColumn]) + @setBufferPosition([screenLineBufferRange.start.row, targetBufferColumn]) # Public: Moves the cursor to the end of the line. moveToEndOfScreenLine: -> From ed773585158e3aec4fc8ec4638be11aad59f3f34 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Feb 2015 09:29:37 +0100 Subject: [PATCH 23/42] Show guide on soft wrapped lines with indentation * Rename phantomToken to softWrapIndent and use it consistently * Build multiple softWrapIndent tokens in order to show guides * Memoize softWrapIndentTokens and softWrapIndentDelta --- spec/display-buffer-spec.coffee | 6 +++--- src/display-buffer.coffee | 4 ++-- src/token.coffee | 6 +++--- src/tokenized-line.coffee | 37 ++++++++++++++++++++------------- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 75c256aec..14ee31176 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -116,9 +116,9 @@ describe "DisplayBuffer", -> expect(displayBuffer.tokenizedLineForScreenRow(3).tokens[1].isHardTab).toBeTruthy() describe "when a line is wrapped", -> - it "correctly tokenizes the phantom tokens", -> - expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[0].isPhantom).toBeTruthy() - expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[0].hasLeadingWhitespace()).toBeFalsy() + it "correctly tokenizes soft wrap indent tokens", -> + expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[0].isSoftWrapIndent).toBeTruthy() + expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[1].isSoftWrapIndent).toBeTruthy() describe "when the buffer changes", -> describe "when buffer lines are updated", -> diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index f5b6145a0..1b4db0f71 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -844,7 +844,7 @@ class DisplayBuffer extends Model column = @screenLines[row].clipScreenColumn(0) else column = screenLine.clipScreenColumn(maxScreenColumn - 1) - else if screenLine.isColumnInsidePhantomToken(column) + else if screenLine.isColumnInsideSoftWrapIndentation(column) if wrapAtSoftNewlines column = screenLine.clipScreenColumn(0) else @@ -1144,7 +1144,7 @@ class DisplayBuffer extends Model if @isSoftWrapped() while wrapScreenColumn = tokenizedLine.findWrapColumn(@getSoftWrapColumn()) [wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn) - break if wrappedLine.hasOnlyPhantomTokens() + break if wrappedLine.hasOnlySoftWrapIndentation() screenLines.push(wrappedLine) softWraps++ screenLines.push(tokenizedLine) diff --git a/src/token.coffee b/src/token.coffee index 4f17bb658..a088acac7 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -21,7 +21,7 @@ class Token firstTrailingWhitespaceIndex: null hasInvisibleCharacters: false - constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab, @hasPairedCharacter, @isPhantom}) -> + constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab, @hasPairedCharacter, @isSoftWrapIndent}) -> @screenDelta = @value.length @bufferDelta ?= @screenDelta @hasPairedCharacter ?= textUtils.hasPairedCharacter(@value) @@ -150,7 +150,7 @@ class Token scopes: @scopes, bufferDelta: 0, isAtomic: true, - isPhantom: true + isSoftWrapIndent: true ) isOnlyWhitespace: -> @@ -229,7 +229,7 @@ class Token else match hasLeadingWhitespace: -> - !@isPhantom and @firstNonWhitespaceIndex? and @firstNonWhitespaceIndex > 0 + @firstNonWhitespaceIndex? and @firstNonWhitespaceIndex > 0 hasTrailingWhitespace: -> @firstTrailingWhitespaceIndex? and @firstTrailingWhitespaceIndex < @value.length diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 932b15cd1..d9a45241e 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -17,6 +17,8 @@ class TokenizedLine @tokens = @breakOutAtomicTokens(tokens) @text = @buildText() @bufferDelta = @buildBufferDelta() + @softWrapIndentTokens = @getSoftWrapIndentTokens() + @softWrapIndentDelta = @buildSoftWrapIndentDelta() @id = idCounter++ @markLeadingAndTrailingWhitespaceTokens() @@ -48,8 +50,8 @@ class TokenizedLine break if tokenStartColumn + token.screenDelta > column tokenStartColumn += token.screenDelta - if token.isPhantom and tokenStartColumn <= column - tokenStartColumn + token.screenDelta + if @isColumnInsideSoftWrapIndentation(tokenStartColumn) + @softWrapIndentDelta else if token.isAtomic and tokenStartColumn < column if skipAtomicTokens tokenStartColumn + token.screenDelta @@ -103,7 +105,7 @@ class TokenizedLine return @text.length else # search backward for the start of the word on the boundary - for column in [maxColumn..0] when @isColumnOutsidePhantomToken(column) + for column in [maxColumn..0] when @isColumnOutsideSoftWrapIndentation(column) return column + 1 if /\s/.test(@text[column]) return maxColumn @@ -121,7 +123,8 @@ class TokenizedLine leftTextLength += nextToken.value.length leftTokens.push nextToken - indentToken = leftTokens[0].buildSoftWrapIndentToken(@indentLevel * @tabLength) + indentTokens = [0...@indentLevel].map => + leftTokens[0].buildSoftWrapIndentToken(@tabLength) leftFragment = new TokenizedLine( tokens: leftTokens @@ -133,7 +136,7 @@ class TokenizedLine tabLength: @tabLength ) rightFragment = new TokenizedLine( - tokens: [indentToken].concat(rightTokens) + tokens: indentTokens.concat(rightTokens) startBufferColumn: @bufferColumnForScreenColumn(column) ruleStack: @ruleStack invisibles: @invisibles @@ -146,18 +149,24 @@ class TokenizedLine isSoftWrapped: -> @lineEnding is null - isColumnOutsidePhantomToken: (column) -> - return true unless @tokens[0].isPhantom + isColumnOutsideSoftWrapIndentation: (column) -> + return true if @softWrapIndentTokens.length == 0 - column > @tokens[0].screenDelta + column > @softWrapIndentDelta - isColumnInsidePhantomToken: (column) -> - return false unless @tokens[0].isPhantom + isColumnInsideSoftWrapIndentation: (column) -> + return false if @softWrapIndentTokens.length == 0 - column < @tokens[0].screenDelta + column < @softWrapIndentDelta - hasOnlyPhantomTokens: -> - @tokens.length == 1 && @tokens[0].isPhantom + getSoftWrapIndentTokens: -> + _.select(@tokens, (token) -> token.isSoftWrapIndent) + + buildSoftWrapIndentDelta: -> + _.reduce @softWrapIndentTokens, ((acc, token) -> acc + token.screenDelta), 0 + + hasOnlySoftWrapIndentation: -> + @tokens.length == @softWrapIndentTokens.length tokenAtBufferColumn: (bufferColumn) -> @tokens[@tokenIndexAtBufferColumn(bufferColumn)] @@ -213,7 +222,7 @@ class TokenizedLine changedText = true else if invisibles.space - if token.hasLeadingWhitespace() + if token.hasLeadingWhitespace() and not token.isSoftWrapIndent token.value = token.value.replace LeadingWhitespaceRegex, (leadingWhitespace) -> leadingWhitespace.replace RepeatedSpaceRegex, invisibles.space token.hasInvisibleCharacters = true From ab5c79d009fae4ae8d46aef79758765c8d1ea03a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Feb 2015 09:46:39 +0100 Subject: [PATCH 24/42] Revert softWrapAtNewline attempt ...now we use `skipSoftWrapIndentation: true` when moving up and down. --- spec/display-buffer-spec.coffee | 22 ++++++++++++---------- spec/tokenized-buffer-spec.coffee | 4 ++-- src/cursor.coffee | 6 +++--- src/display-buffer.coffee | 5 +++-- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 14ee31176..aaf2c68ed 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -615,6 +615,18 @@ describe "DisplayBuffer", -> displayBuffer.createFold(3, 5) expect(displayBuffer.clipScreenPosition([3, 5], wrapBeyondNewlines: true)).toEqual [4, 0] + describe "when skipSoftWrapIndentation is false (the default)", -> + it "clips positions to the beginning of the line", -> + expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [3, 50] + expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [3, 50] + expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [3, 50] + + describe "when skipSoftWrapIndentation is true", -> + it "wraps positions at the end of previous soft-wrapped line", -> + expect(displayBuffer.clipScreenPosition([4, 0], skipSoftWrapIndentation: true)).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 1], skipSoftWrapIndentation: true)).toEqual [4, 4] + expect(displayBuffer.clipScreenPosition([4, 3], skipSoftWrapIndentation: true)).toEqual [4, 4] + describe "when wrapAtSoftNewlines is false (the default)", -> it "clips positions at the end of soft-wrapped lines to the character preceding the end of the line", -> expect(displayBuffer.clipScreenPosition([3, 50])).toEqual [3, 50] @@ -622,11 +634,6 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([3, 58])).toEqual [3, 50] expect(displayBuffer.clipScreenPosition([3, 1000])).toEqual [3, 50] - it "wraps positions at the end of previous soft-wrapped line", -> - expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [3, 50] - expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [3, 50] - expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [3, 50] - describe "when wrapAtSoftNewlines is true", -> it "wraps positions at the end of soft-wrapped lines to the next screen line", -> expect(displayBuffer.clipScreenPosition([3, 50], wrapAtSoftNewlines: true)).toEqual [3, 50] @@ -634,11 +641,6 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 4] expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 4] - it "clips positions to the beginning of the line", -> - expect(displayBuffer.clipScreenPosition([4, 0], wrapAtSoftNewlines: true)).toEqual [4, 4] - expect(displayBuffer.clipScreenPosition([4, 1], wrapAtSoftNewlines: true)).toEqual [4, 4] - expect(displayBuffer.clipScreenPosition([4, 3], wrapAtSoftNewlines: true)).toEqual [4, 4] - describe "when skipAtomicTokens is false (the default)", -> it "clips screen positions in the middle of atomic tab characters to the beginning of the character", -> buffer.insert([0, 0], '\t') diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 103a54b85..41a63a562 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -670,8 +670,8 @@ describe "TokenizedBuffer", -> [segment1, segment2] = tokenizedLine.softWrapAt(16) expect(segment1.tokens[5].value).toBe ' ' expect(segment1.tokens[5].firstTrailingWhitespaceIndex).toBe null - expect(segment2.tokens[7].value).toBe ' ' - expect(segment2.tokens[7].firstTrailingWhitespaceIndex).toBe 0 + expect(segment2.tokens[6].value).toBe ' ' + expect(segment2.tokens[6].firstTrailingWhitespaceIndex).toBe 0 it "sets leading and trailing whitespace correctly on a line with invisible characters that is copied", -> buffer.setText(" \t a line with tabs\tand \tspaces \t ") diff --git a/src/cursor.coffee b/src/cursor.coffee index 969681ef0..33f50ca9c 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -271,7 +271,7 @@ class Cursor extends Model { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? - @setScreenPosition({row: row - rowCount, column: column}, wrapAtSoftNewlines: true) + @setScreenPosition({row: row - rowCount, column: column}, skipSoftWrapIndentation: true) @goalColumn = column # Public: Moves the cursor down one screen row. @@ -288,7 +288,7 @@ class Cursor extends Model { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? - @setScreenPosition({row: row + rowCount, column: column}, wrapAtSoftNewlines: true) + @setScreenPosition({row: row + rowCount, column: column}, skipSoftWrapIndentation: true) @goalColumn = column # Public: Moves the cursor left one screen column. @@ -359,7 +359,7 @@ class Cursor extends Model # line. moveToFirstCharacterOfLine: -> screenRow = @getScreenRow() - screenLineStart = @editor.clipScreenPosition([screenRow, 0], wrapAtSoftNewlines: true) + screenLineStart = @editor.clipScreenPosition([screenRow, 0], skipSoftWrapIndentation: true) screenLineEnd = [screenRow, Infinity] screenLineBufferRange = @editor.bufferRangeForScreenRange([screenLineStart, screenLineEnd]) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 1b4db0f71..8c5bd430e 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -819,11 +819,12 @@ class DisplayBuffer extends Model # options - A hash with the following values: # wrapBeyondNewlines: if `true`, continues wrapping past newlines # wrapAtSoftNewlines: if `true`, continues wrapping past soft newlines + # skipSoftWrapIndentation: if `true`, skips soft wrap indentation # screenLine: if `true`, indicates that you're using a line number, not a row number # # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. clipScreenPosition: (screenPosition, options={}) -> - { wrapBeyondNewlines, wrapAtSoftNewlines } = options + { wrapBeyondNewlines, wrapAtSoftNewlines, skipSoftWrapIndentation } = options { row, column } = Point.fromObject(screenPosition) if row < 0 @@ -845,7 +846,7 @@ class DisplayBuffer extends Model else column = screenLine.clipScreenColumn(maxScreenColumn - 1) else if screenLine.isColumnInsideSoftWrapIndentation(column) - if wrapAtSoftNewlines + if skipSoftWrapIndentation column = screenLine.clipScreenColumn(0) else row-- From 0258531a3c727aeb7ae3033d51442d84f1fa1ac9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Feb 2015 10:11:47 +0100 Subject: [PATCH 25/42] Use softWrapIndentation name consistently --- spec/display-buffer-spec.coffee | 10 +++++----- src/display-buffer.coffee | 2 +- src/token.coffee | 6 +++--- src/tokenized-line.coffee | 28 ++++++++++++++-------------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index aaf2c68ed..38cf3acc9 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -116,9 +116,9 @@ describe "DisplayBuffer", -> expect(displayBuffer.tokenizedLineForScreenRow(3).tokens[1].isHardTab).toBeTruthy() describe "when a line is wrapped", -> - it "correctly tokenizes soft wrap indent tokens", -> - expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[0].isSoftWrapIndent).toBeTruthy() - expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[1].isSoftWrapIndent).toBeTruthy() + it "correctly tokenizes soft wrap indentation tokens", -> + expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[0].isSoftWrapIndentation).toBeTruthy() + expect(displayBuffer.tokenizedLineForScreenRow(4).tokens[1].isSoftWrapIndentation).toBeTruthy() describe "when the buffer changes", -> describe "when buffer lines are updated", -> @@ -616,13 +616,13 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([3, 5], wrapBeyondNewlines: true)).toEqual [4, 0] describe "when skipSoftWrapIndentation is false (the default)", -> - it "clips positions to the beginning of the line", -> + it "wraps positions at the end of previous soft-wrapped line", -> expect(displayBuffer.clipScreenPosition([4, 0])).toEqual [3, 50] expect(displayBuffer.clipScreenPosition([4, 1])).toEqual [3, 50] expect(displayBuffer.clipScreenPosition([4, 3])).toEqual [3, 50] describe "when skipSoftWrapIndentation is true", -> - it "wraps positions at the end of previous soft-wrapped line", -> + it "clips positions to the beginning of the line", -> expect(displayBuffer.clipScreenPosition([4, 0], skipSoftWrapIndentation: true)).toEqual [4, 4] expect(displayBuffer.clipScreenPosition([4, 1], skipSoftWrapIndentation: true)).toEqual [4, 4] expect(displayBuffer.clipScreenPosition([4, 3], skipSoftWrapIndentation: true)).toEqual [4, 4] diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 8c5bd430e..7e08d3998 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -819,7 +819,7 @@ class DisplayBuffer extends Model # options - A hash with the following values: # wrapBeyondNewlines: if `true`, continues wrapping past newlines # wrapAtSoftNewlines: if `true`, continues wrapping past soft newlines - # skipSoftWrapIndentation: if `true`, skips soft wrap indentation + # skipSoftWrapIndentation: if `true`, skips soft wrap indentation without wrapping to the previous line # screenLine: if `true`, indicates that you're using a line number, not a row number # # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. diff --git a/src/token.coffee b/src/token.coffee index a088acac7..778ea16e6 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -21,7 +21,7 @@ class Token firstTrailingWhitespaceIndex: null hasInvisibleCharacters: false - constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab, @hasPairedCharacter, @isSoftWrapIndent}) -> + constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab, @hasPairedCharacter, @isSoftWrapIndentation}) -> @screenDelta = @value.length @bufferDelta ?= @screenDelta @hasPairedCharacter ?= textUtils.hasPairedCharacter(@value) @@ -144,13 +144,13 @@ class Token isHardTab: isHardTab ) - buildSoftWrapIndentToken: (length) -> + buildSoftWrapIndentationToken: (length) -> new Token( value: _.multiplyString(" ", length), scopes: @scopes, bufferDelta: 0, isAtomic: true, - isSoftWrapIndent: true + isSoftWrapIndentation: true ) isOnlyWhitespace: -> diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index d9a45241e..50d3356de 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -17,8 +17,8 @@ class TokenizedLine @tokens = @breakOutAtomicTokens(tokens) @text = @buildText() @bufferDelta = @buildBufferDelta() - @softWrapIndentTokens = @getSoftWrapIndentTokens() - @softWrapIndentDelta = @buildSoftWrapIndentDelta() + @softWrapIndentationTokens = @getSoftWrapIndentationTokens() + @softWrapIndentationDelta = @buildSoftWrapIndentationDelta() @id = idCounter++ @markLeadingAndTrailingWhitespaceTokens() @@ -51,7 +51,7 @@ class TokenizedLine tokenStartColumn += token.screenDelta if @isColumnInsideSoftWrapIndentation(tokenStartColumn) - @softWrapIndentDelta + @softWrapIndentationDelta else if token.isAtomic and tokenStartColumn < column if skipAtomicTokens tokenStartColumn + token.screenDelta @@ -124,7 +124,7 @@ class TokenizedLine leftTokens.push nextToken indentTokens = [0...@indentLevel].map => - leftTokens[0].buildSoftWrapIndentToken(@tabLength) + leftTokens[0].buildSoftWrapIndentationToken(@tabLength) leftFragment = new TokenizedLine( tokens: leftTokens @@ -150,23 +150,23 @@ class TokenizedLine @lineEnding is null isColumnOutsideSoftWrapIndentation: (column) -> - return true if @softWrapIndentTokens.length == 0 + return true if @softWrapIndentationTokens.length == 0 - column > @softWrapIndentDelta + column > @softWrapIndentationDelta isColumnInsideSoftWrapIndentation: (column) -> - return false if @softWrapIndentTokens.length == 0 + return false if @softWrapIndentationTokens.length == 0 - column < @softWrapIndentDelta + column < @softWrapIndentationDelta - getSoftWrapIndentTokens: -> - _.select(@tokens, (token) -> token.isSoftWrapIndent) + getSoftWrapIndentationTokens: -> + _.select(@tokens, (token) -> token.isSoftWrapIndentation) - buildSoftWrapIndentDelta: -> - _.reduce @softWrapIndentTokens, ((acc, token) -> acc + token.screenDelta), 0 + buildSoftWrapIndentationDelta: -> + _.reduce @softWrapIndentationTokens, ((acc, token) -> acc + token.screenDelta), 0 hasOnlySoftWrapIndentation: -> - @tokens.length == @softWrapIndentTokens.length + @tokens.length == @softWrapIndentationTokens.length tokenAtBufferColumn: (bufferColumn) -> @tokens[@tokenIndexAtBufferColumn(bufferColumn)] @@ -222,7 +222,7 @@ class TokenizedLine changedText = true else if invisibles.space - if token.hasLeadingWhitespace() and not token.isSoftWrapIndent + if token.hasLeadingWhitespace() and not token.isSoftWrapIndentation token.value = token.value.replace LeadingWhitespaceRegex, (leadingWhitespace) -> leadingWhitespace.replace RepeatedSpaceRegex, invisibles.space token.hasInvisibleCharacters = true From 0b777e3ccc62b2b87b1fb03b6c38860f22691ecd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Feb 2015 12:32:30 +0100 Subject: [PATCH 26/42] Take into account odd spaces as well --- src/tokenized-line.coffee | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 50d3356de..78a52bbd1 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -110,6 +110,24 @@ class TokenizedLine return maxColumn + # Calculates how many trailing spaces in this line's indentation cannot fit in a single tab. + # + # Returns a {Number} representing the odd indentation spaces in this line. + getOddIndentationSpaces: -> + oddIndentLevel = @indentLevel - Math.floor(@indentLevel) + Math.round(@tabLength * oddIndentLevel) + + buildSoftWrapIndentationTokens: (token) -> + indentTokens = [0...Math.floor(@indentLevel)].map => + token.buildSoftWrapIndentationToken(@tabLength) + + if @getOddIndentationSpaces() + indentTokens.concat( + token.buildSoftWrapIndentationToken @getOddIndentationSpaces() + ) + else + indentTokens + softWrapAt: (column) -> return [new TokenizedLine([], '', [0, 0], [0, 0]), this] if column == 0 @@ -123,8 +141,7 @@ class TokenizedLine leftTextLength += nextToken.value.length leftTokens.push nextToken - indentTokens = [0...@indentLevel].map => - leftTokens[0].buildSoftWrapIndentationToken(@tabLength) + indentationTokens = @buildSoftWrapIndentationTokens(leftTokens[0]) leftFragment = new TokenizedLine( tokens: leftTokens @@ -136,7 +153,7 @@ class TokenizedLine tabLength: @tabLength ) rightFragment = new TokenizedLine( - tokens: indentTokens.concat(rightTokens) + tokens: indentationTokens.concat(rightTokens) startBufferColumn: @bufferColumnForScreenColumn(column) ruleStack: @ruleStack invisibles: @invisibles From 49be811bee356130eaa2772c87513e60b4f6fa1b Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Mon, 23 Feb 2015 22:34:30 -0800 Subject: [PATCH 27/42] Report a deprecation warning when 'use 6to5' is used instead of 'use babel'. --- src/babel.coffee | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/babel.coffee b/src/babel.coffee index 766324ce0..d4b53c204 100644 --- a/src/babel.coffee +++ b/src/babel.coffee @@ -8,6 +8,7 @@ crypto = require 'crypto' fs = require 'fs-plus' path = require 'path' babel = null # Defer until used +Grim = null # Defer until used stats = hits: 0 @@ -127,15 +128,33 @@ transpile = (sourceCode, filePath, cachePath) -> js +isRoot = (filePath) -> + parts = path.parse(filePath) + parts.root == filePath + # Function that obeys the contract of an entry in the require.extensions map. # Returns the transpiled version of the JavaScript code at filePath, which is # either generated on the fly or pulled from cache. loadFile = (module, filePath) -> sourceCode = fs.readFileSync(filePath, 'utf8') - return module._compile(sourceCode, filePath) unless sourceCode.startsWith('"use 6to5"') or - sourceCode.startsWith("'use 6to5'") or - sourceCode.startsWith('"use babel"') or - sourceCode.startsWith("'use babel'") + if sourceCode.startsWith('"use babel"') or sourceCode.startsWith("'use babel'") + # Continue. + else if sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'") + packageName + directory = filePath + until packageName + directory = path.dirname(directory) + manifest = path.join(directory, 'package.json') + if fs.existsSync(manifest) + json = JSON.parse(fs.readFileSync(manifest)) + packageName = json.name + else if isRoot(directory) + break + + Grim ?= require 'grim' + Grim.deprecate("Use the 'use babel' pragma instead of 'use 6to5' in #{filePath}", {packageName}) + else + return module._compile(sourceCode, filePath) cachePath = getCachePath(sourceCode) js = getCachedJavaScript(cachePath) ? transpile(sourceCode, filePath, cachePath) From 6670cc8aa089cb22fc2b7cb52976013ee4b99901 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 25 Feb 2015 10:42:39 +0100 Subject: [PATCH 28/42] :racehorse: Speed up `mergeCursors` --- src/text-editor.coffee | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8badd9287..76948990d 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1797,13 +1797,12 @@ class TextEditor extends Model # Merge cursors that have the same screen position mergeCursors: -> - positions = [] - for cursor in @getCursors() - position = cursor.getBufferPosition().toString() - if position in positions - cursor.destroy() - else - positions.push(position) + [lastCursor, cursors...] = @getCursorsOrderedByBufferPosition() + lastBufferPosition = lastCursor.getBufferPosition() + for cursor in cursors + currentBufferPosition = cursor.getBufferPosition() + cursor.destroy() if lastBufferPosition.compare(currentBufferPosition) == 0 + lastBufferPosition = currentBufferPosition preserveCursorPositionOnBufferReload: -> cursorPosition = null From 4555c77e5a7ad4f486e9dc65b51a4265b5dc02f3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 25 Feb 2015 11:18:51 +0100 Subject: [PATCH 29/42] Use hashes instead, and avoid sorting :punch: --- src/text-editor.coffee | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 76948990d..2e6cfdf92 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1797,12 +1797,13 @@ class TextEditor extends Model # Merge cursors that have the same screen position mergeCursors: -> - [lastCursor, cursors...] = @getCursorsOrderedByBufferPosition() - lastBufferPosition = lastCursor.getBufferPosition() - for cursor in cursors - currentBufferPosition = cursor.getBufferPosition() - cursor.destroy() if lastBufferPosition.compare(currentBufferPosition) == 0 - lastBufferPosition = currentBufferPosition + positions = {} + for cursor in @getCursors() + position = cursor.getBufferPosition().toString() + if positions.hasOwnProperty(position) + cursor.destroy() + else + positions[position] = true preserveCursorPositionOnBufferReload: -> cursorPosition = null From 1c9dbbf3b3dabd07a585585ac9e060ffa6ea3ead Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 09:24:18 -0800 Subject: [PATCH 30/42] Craft manual deprecation to get around stack depth limit This allows the deprecation to be properly associated with the package it originates from. --- src/babel.coffee | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/babel.coffee b/src/babel.coffee index d4b53c204..01214fa1a 100644 --- a/src/babel.coffee +++ b/src/babel.coffee @@ -140,19 +140,25 @@ loadFile = (module, filePath) -> if sourceCode.startsWith('"use babel"') or sourceCode.startsWith("'use babel'") # Continue. else if sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'") - packageName - directory = filePath - until packageName - directory = path.dirname(directory) - manifest = path.join(directory, 'package.json') - if fs.existsSync(manifest) - json = JSON.parse(fs.readFileSync(manifest)) - packageName = json.name - else if isRoot(directory) - break - + # Create a manual deprecation since the stack is too deep to use Grim + # which limits the depth to 3 Grim ?= require 'grim' - Grim.deprecate("Use the 'use babel' pragma instead of 'use 6to5' in #{filePath}", {packageName}) + stack = [ + { + fileName: __filename + functionName: 'loadFile' + location: "#{__filename}:161:5" + } + { + fileName: filePath + functionName: '' + location: "#{filePath}:1:1" + } + ] + deprecation = + message: "Use the 'use babel' pragma instead of 'use 6to5'" + stacks: [stack] + Grim.addSerializedDeprecation(deprecation) else return module._compile(sourceCode, filePath) From 7a719d585db96ff7d2977db9067e1d9d4d0adf1a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 09:25:22 -0800 Subject: [PATCH 31/42] Remove unused method --- src/babel.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/babel.coffee b/src/babel.coffee index 01214fa1a..c93112e78 100644 --- a/src/babel.coffee +++ b/src/babel.coffee @@ -128,10 +128,6 @@ transpile = (sourceCode, filePath, cachePath) -> js -isRoot = (filePath) -> - parts = path.parse(filePath) - parts.root == filePath - # Function that obeys the contract of an entry in the require.extensions map. # Returns the transpiled version of the JavaScript code at filePath, which is # either generated on the fly or pulled from cache. From e51f8b298e32201e77f50eb5bd38fa54ad0842ec Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 09:37:42 -0800 Subject: [PATCH 32/42] Add specs for pragma deprecations --- spec/babel-spec.coffee | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index f9abac930..58f67fd03 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -1,7 +1,14 @@ babel = require '../src/babel' crypto = require 'crypto' +grim = require 'grim' describe "Babel transpiler support", -> + beforeEach -> + jasmine.snapshotDeprecations() + + afterEach -> + jasmine.restoreDeprecationsSnapshot() + describe "::createBabelVersionAndOptionsDigest", -> it "returns a digest for the library version and specified options", -> defaultOptions = @@ -30,21 +37,27 @@ describe "Babel transpiler support", -> it "transpiles it using babel", -> transpiled = require('./fixtures/babel/babel-single-quotes.js') expect(transpiled(3)).toBe 4 + expect(grim.getDeprecationsLength()).toBe 0 describe "when a .js file starts with 'use 6to5';", -> - it "transpiles it using 6to5", -> + it "transpiles it using babel and adds a deprecation", -> + expect(grim.getDeprecationsLength()).toBe 0 transpiled = require('./fixtures/babel/6to5-single-quotes.js') expect(transpiled(3)).toBe 4 + expect(grim.getDeprecationsLength()).toBe 1 describe 'when a .js file starts with "use babel";', -> it "transpiles it using babel", -> transpiled = require('./fixtures/babel/babel-double-quotes.js') expect(transpiled(3)).toBe 4 + expect(grim.getDeprecationsLength()).toBe 0 describe 'when a .js file starts with "use 6to5";', -> it "transpiles it using babel", -> + expect(grim.getDeprecationsLength()).toBe 0 transpiled = require('./fixtures/babel/6to5-double-quotes.js') expect(transpiled(3)).toBe 4 + expect(grim.getDeprecationsLength()).toBe 1 describe "when a .js file does not start with 'use 6to6';", -> it "does not transpile it using babel", -> From feb37c5df6874b78976c3016b5813cc8f55896a6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 09:42:49 -0800 Subject: [PATCH 33/42] :memo: Mention deprecations --- spec/babel-spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index 58f67fd03..4aec0ef8d 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -40,7 +40,7 @@ describe "Babel transpiler support", -> expect(grim.getDeprecationsLength()).toBe 0 describe "when a .js file starts with 'use 6to5';", -> - it "transpiles it using babel and adds a deprecation", -> + it "transpiles it using babel and adds a pragma deprecation", -> expect(grim.getDeprecationsLength()).toBe 0 transpiled = require('./fixtures/babel/6to5-single-quotes.js') expect(transpiled(3)).toBe 4 @@ -53,7 +53,7 @@ describe "Babel transpiler support", -> expect(grim.getDeprecationsLength()).toBe 0 describe 'when a .js file starts with "use 6to5";', -> - it "transpiles it using babel", -> + it "transpiles it using babel and adds a pragma deprecation", -> expect(grim.getDeprecationsLength()).toBe 0 transpiled = require('./fixtures/babel/6to5-double-quotes.js') expect(transpiled(3)).toBe 4 From 8537ac95ecf73c710ba37e5366987fbb6388ca50 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 10:56:38 -0800 Subject: [PATCH 34/42] Prepare 0.184 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c73a86b9..b93c17f1c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "0.183.0", + "version": "0.184.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 2afb0722c004880d0dc5b058966ae849d070c25d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 25 Feb 2015 10:57:31 -0800 Subject: [PATCH 35/42] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b93c17f1c..d1e22a4b7 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "dev-live-reload": "0.41.0", "encoding-selector": "0.18.0", "exception-reporting": "0.24.0", - "find-and-replace": "0.157.0", + "find-and-replace": "0.158.0", "fuzzy-finder": "0.68.0", "git-diff": "0.53.0", "go-to-line": "0.30.0", From bfe3bc36596fa555bc4896db3747e8112fbe3ff9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 11:16:06 -0800 Subject: [PATCH 36/42] Make package activate optional --- .../packages/package-with-no-activate/index.js | 1 + .../packages/package-with-no-activate/package.json | 4 ++++ spec/package-manager-spec.coffee | 11 +++++++++++ src/package.coffee | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/packages/package-with-no-activate/index.js create mode 100644 spec/fixtures/packages/package-with-no-activate/package.json diff --git a/spec/fixtures/packages/package-with-no-activate/index.js b/spec/fixtures/packages/package-with-no-activate/index.js new file mode 100644 index 000000000..4ba52ba2c --- /dev/null +++ b/spec/fixtures/packages/package-with-no-activate/index.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/spec/fixtures/packages/package-with-no-activate/package.json b/spec/fixtures/packages/package-with-no-activate/package.json new file mode 100644 index 000000000..4be70fd1a --- /dev/null +++ b/spec/fixtures/packages/package-with-no-activate/package.json @@ -0,0 +1,4 @@ +{ + "name": "package-with-no-activate", + "version": "1.0.0" +} diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 3d79b4631..d9898505c 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -220,6 +220,17 @@ describe "PackageManager", -> expect(console.error).not.toHaveBeenCalled() expect(console.warn).not.toHaveBeenCalled() + describe "when the package does not export an activate function", -> + it "activates the package and does not throw an exception or log a warning", -> + spyOn(console, "warn") + expect(-> atom.packages.activatePackage('package-with-no-activate')).not.toThrow() + + waitsFor -> + atom.packages.isPackageActive('package-with-no-activate') + + runs -> + expect(console.warn).not.toHaveBeenCalled() + it "passes the activate method the package's previously serialized state if it exists", -> pack = null waitsForPromise -> diff --git a/src/package.coffee b/src/package.coffee index 6a94133d6..d8ba4a1d8 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -157,7 +157,7 @@ class Package @activateConfig() @activateStylesheets() if @requireMainModule() - @mainModule.activate(atom.packages.getPackageState(@name) ? {}) + @mainModule.activate?(atom.packages.getPackageState(@name) ? {}) @mainActivated = true @activateServices() catch e From 0c1c0d475311ee34bbff50119273c5e09f1c7686 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 25 Feb 2015 11:40:33 -0800 Subject: [PATCH 37/42] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1e22a4b7..a112a2036 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "symbols-view": "0.84.0", "tabs": "0.67.0", "timecop": "0.31.0", - "tree-view": "0.161.0", + "tree-view": "0.162.0", "update-package-dependencies": "0.8.0", "welcome": "0.24.0", "whitespace": "0.29.0", From fb89f04c5d30457eb83639c4f9c903fef645ee78 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 13:03:10 -0800 Subject: [PATCH 38/42] :arrow_up: language-javascript@0.59 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a112a2036..e1cff621a 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "language-html": "0.29.0", "language-hyperlink": "0.12.2", "language-java": "0.14.0", - "language-javascript": "0.58.0", + "language-javascript": "0.59.0", "language-json": "0.12.0", "language-less": "0.25.0", "language-make": "0.13.0", From 986e8bf85fd67c03a587f8bd225ea69e073735a5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 25 Feb 2015 14:35:24 -0700 Subject: [PATCH 39/42] Allow multiple space-delimited classes for highlight decorations Fixes #5747 --- spec/text-editor-component-spec.coffee | 8 ++++++++ src/highlights-component.coffee | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 5d6e7ed85..d7b9ed2ff 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1171,6 +1171,14 @@ describe "TextEditorComponent", -> regions = componentNode.querySelectorAll('.test-highlight .region') expect(regions.length).toBe 2 + it "allows multiple space-delimited decoration classes", -> + decoration.setProperties(type: 'highlight', class: 'foo bar') + nextAnimationFrame() + expect(componentNode.querySelectorAll('.foo.bar').length).toBe 1 + decoration.setProperties(type: 'highlight', class: 'bar baz') + nextAnimationFrame() + expect(componentNode.querySelectorAll('.bar.baz').length).toBe 1 + it "renders classes on the regions directly if 'deprecatedRegionClass' option is defined", -> decoration = editor.decorateMarker(marker, type: 'highlight', class: 'test-highlight', deprecatedRegionClass: 'test-highlight-region') nextAnimationFrame() diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index 488a2236f..48d2cc70e 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -1,4 +1,5 @@ RegionStyleProperties = ['top', 'left', 'right', 'width', 'height'] +SpaceRegex = /\s+/ module.exports = class HighlightsComponent @@ -44,8 +45,17 @@ class HighlightsComponent # update class if newHighlightState.class isnt oldHighlightState.class - highlightNode.classList.remove(oldHighlightState.class) if oldHighlightState.class? - highlightNode.classList.add(newHighlightState.class) + if oldHighlightState.class? + if SpaceRegex.test(oldHighlightState.class) + highlightNode.classList.remove(oldHighlightState.class.split(SpaceRegex)...) + else + highlightNode.classList.remove(oldHighlightState.class) + + if SpaceRegex.test(newHighlightState.class) + highlightNode.classList.add(newHighlightState.class.split(SpaceRegex)...) + else + highlightNode.classList.add(newHighlightState.class) + oldHighlightState.class = newHighlightState.class @updateHighlightRegions(id, newHighlightState) From 5aaf3efa81ae32698353ffb815706e78b8b1b685 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 25 Feb 2015 14:20:02 -0800 Subject: [PATCH 40/42] :arrow_up: git-diff --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1cff621a..c2844a6ca 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "exception-reporting": "0.24.0", "find-and-replace": "0.158.0", "fuzzy-finder": "0.68.0", - "git-diff": "0.53.0", + "git-diff": "0.54.0", "go-to-line": "0.30.0", "grammar-selector": "0.45.0", "image-view": "0.49.0", From 131396a7f44d9cf5eed645da382f8d8261c5bbe8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 14:53:05 -0800 Subject: [PATCH 41/42] Prepare 0.185 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c2844a6ca..4592d79c7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "0.184.0", + "version": "0.185.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From e9134cddc825ad75b9b88cdca13a19786e57b58c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 25 Feb 2015 15:36:02 -0800 Subject: [PATCH 42/42] :arrow_up: language-php@0.21 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4592d79c7..c2e024a22 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "language-mustache": "0.11.0", "language-objective-c": "0.15.0", "language-perl": "0.10.0", - "language-php": "0.20.0", + "language-php": "0.21.0", "language-property-list": "0.8.0", "language-python": "0.32.0", "language-ruby": "0.48.0",