diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 0b2e246ce..5446777fa 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -214,7 +214,7 @@ describe "Editor", -> expect(editor.lineElementForScreenRow(0).text()).toBe 'abc' editor.edit(otherEditSession) - expect(editor.lineElementForScreenRow(0).html()).toBe ' ' + expect(editor.lineElementForScreenRow(0).html()).toBe ' ' editor.insertText("def\n") expect(editor.lineElementForScreenRow(0).text()).toBe 'def' @@ -992,7 +992,7 @@ describe "Editor", -> # renders empty lines with a non breaking space expect(buffer.lineForRow(10)).toBe '' - expect(editor.renderedLines.find('.line:eq(10)').html()).toBe ' ' + expect(editor.renderedLines.find('.line:eq(10) span').html()).toBe ' ' it "syntax highlights code based on the file type", -> line0 = editor.renderedLines.find('.line:first') @@ -1198,7 +1198,7 @@ describe "Editor", -> editor.scrollTop(editor.lineHeight * 3.5) # first visible row will be 3, last will be 8 expect(editor.renderedLines.find('.line').length).toBe 10 expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(1) - expect(editor.renderedLines.find('.line:last').html()).toBe ' ' # line 10 is blank + expect(editor.renderedLines.find('.line:last span').html()).toBe ' ' # line 10 is blank expect(editor.gutter.find('.line-number:first').text()).toBe '2' expect(editor.gutter.find('.line-number:last').text()).toBe '11' @@ -1206,7 +1206,7 @@ describe "Editor", -> editor.scrollTop(editor.lineHeight * 5.5) # first visible row will be 5, last will be 10 expect(editor.renderedLines.find('.line').length).toBe 10 expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(1) - expect(editor.renderedLines.find('.line:last').html()).toBe ' ' # line 10 is blank + expect(editor.renderedLines.find('.line:last span').html()).toBe ' ' # line 10 is blank expect(editor.gutter.find('.line-number:first').text()).toBe '2' expect(editor.gutter.find('.line-number:last').text()).toBe '11' @@ -1218,7 +1218,7 @@ describe "Editor", -> editor.scrollTop(editor.lineHeight * 3.5) # first visible row will be 3, last will be 8 expect(editor.renderedLines.find('.line').length).toBe 10 expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(1) - expect(editor.renderedLines.find('.line:last').html()).toBe ' ' # line 10 is blank + expect(editor.renderedLines.find('.line:last span').html()).toBe ' ' # line 10 is blank editor.scrollTop(0) expect(editor.renderedLines.find('.line').length).toBe 8 @@ -1827,3 +1827,12 @@ describe "Editor", -> editor.pageUp() expect(editor.getCursor().getScreenPosition().row).toBe(0) expect(editor.getFirstVisibleScreenRow()).toBe(0) + + describe "when showInvisibles is enabled on the editSession", -> + beforeEach -> + editor.activeEditSession.showInvisibles = true + editor.attachToDom(5) + + it "displays spaces as •, tabs as ▸ and newlines as ¬", -> + editor.setText " a line with tabs\tand spaces " + expect(editor.find('.line').text()).toBe "•a•line•with•tabs▸ and•spaces•¬" diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 66ad0d8d3..34dea2477 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -35,11 +35,12 @@ class EditSession cursors: null selections: null autoIndent: false # TODO: re-enabled auto-indent after fixing the rest of tokenization + tabLength: null softTabs: true softWrap: false - tabLength: null + showInvisibles: false - constructor: ({@project, @buffer, @tabLength, @autoIndent, @softTabs, @softWrap}) -> + constructor: ({@project, @buffer, @tabLength, @autoIndent, @softTabs, @softWrap, @showInvisibles}) -> @id = @constructor.idCounter++ @softTabs ?= true @languageMode = new LanguageMode(this, @buffer.getExtension()) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 1d6b87ac4..bf1080284 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -873,12 +873,13 @@ class Editor extends View attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of lineAttributes line.push("
")
- if screenLine.text == ''
- line.push(' ')
- else
- for token in screenLine.tokens
- updateScopeStack(token.scopes)
- line.push(token.escapeValue())
+ for token in screenLine.tokens
+ updateScopeStack(token.scopes)
+ line.push(token.escapeValue(@activeEditSession.showInvisibles))
+
+ if @activeEditSession.showInvisibles
+ line.push("¬
")
+
line.push('')
line.join('')
diff --git a/src/app/token.coffee b/src/app/token.coffee
index 9fd520be6..af1ece5a2 100644
--- a/src/app/token.coffee
+++ b/src/app/token.coffee
@@ -5,8 +5,9 @@ class Token
value: null
scopes: null
isAtomic: null
+ isTab: null
- constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @fold}) ->
+ constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @fold, @isTab}) ->
@screenDelta = @value.length
@bufferDelta ?= @screenDelta
@@ -24,20 +25,30 @@ class Token
breakOutTabCharacters: (tabLength) ->
return [this] unless /\t/.test(@value)
+ tabText = new Array(tabLength + 1).join(" ")
for substring in @value.match(/([^\t]+|\t)/g)
if substring == '\t'
- @buildTabToken(tabLength)
+ new Token(value: tabText, scopes: @scopes, bufferDelta: 1, isAtomic: true, isTab: true)
else
new Token(value: substring, scopes: @scopes)
buildTabToken: (tabLength) ->
tabText = new Array(tabLength + 1).join(" ")
- new Token(value: tabText, scopes: @scopes, bufferDelta: 1, isAtomic: true)
- escapeValue: ->
- @value
+ escapeValue: (showInvisibles)->
+ return " " if @value == ""
+
+ value = @value
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(//g, '>')
+
+ if showInvisibles
+ if @isTab
+ value = "▸" + value[1..]
+ else
+ value = value.replace(/[ ]+/g, "•")
+
+ value