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