diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee
index 6b8719add..a41bbd7f1 100644
--- a/spec/app/editor-spec.coffee
+++ b/spec/app/editor-spec.coffee
@@ -1804,6 +1804,51 @@ describe "Editor", ->
expect(editor.renderedLines.find('.line:eq(1)').text()).toBe "that#{cr}#{eol}"
expect(editor.renderedLines.find('.line:last').text()).toBe "#{eol}"
+ describe "when config.editor.showIndentGuide is set to true", ->
+ it "adds an indent-guide class to each leading whitespace span", ->
+ editor.attachToDom()
+
+ expect(config.get("editor.showIndentGuide")).toBeFalsy()
+ config.set("editor.showIndentGuide", true)
+ expect(editor.showIndentGuide).toBeTruthy()
+
+ expect(editor.renderedLines.find('.line:eq(0) .indent-guide').length).toBe 0
+
+ expect(editor.renderedLines.find('.line:eq(1) .indent-guide').length).toBe 1
+ expect(editor.renderedLines.find('.line:eq(1) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(2) .indent-guide').length).toBe 2
+ expect(editor.renderedLines.find('.line:eq(2) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(3) .indent-guide').length).toBe 2
+ expect(editor.renderedLines.find('.line:eq(3) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(4) .indent-guide').length).toBe 2
+ expect(editor.renderedLines.find('.line:eq(4) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(5) .indent-guide').length).toBe 3
+ expect(editor.renderedLines.find('.line:eq(5) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(6) .indent-guide').length).toBe 3
+ expect(editor.renderedLines.find('.line:eq(6) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(7) .indent-guide').length).toBe 2
+ expect(editor.renderedLines.find('.line:eq(7) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(8) .indent-guide').length).toBe 2
+ expect(editor.renderedLines.find('.line:eq(8) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(9) .indent-guide').length).toBe 1
+ expect(editor.renderedLines.find('.line:eq(9) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(10) .indent-guide').length).toBe 1
+ expect(editor.renderedLines.find('.line:eq(10) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(11) .indent-guide').length).toBe 1
+ expect(editor.renderedLines.find('.line:eq(11) .indent-guide').text()).toBe ' '
+
+ expect(editor.renderedLines.find('.line:eq(12) .indent-guide').length).toBe 0
+
describe "gutter rendering", ->
beforeEach ->
editor.attachToDom(heightInLines: 5.5)
diff --git a/src/app/editor.coffee b/src/app/editor.coffee
index 842539291..47f676d89 100644
--- a/src/app/editor.coffee
+++ b/src/app/editor.coffee
@@ -15,6 +15,7 @@ class Editor extends View
@configDefaults:
fontSize: 20
showInvisibles: false
+ showIndentGuide: false
autosave: false
autoIndent: true
autoIndentOnPaste: false
@@ -191,6 +192,7 @@ class Editor extends View
'editor:move-line-down': @moveLineDown
'editor:duplicate-line': @duplicateLine
'editor:undo-close-session': @undoDestroySession
+ 'editor:toggle-indent-guide': => config.set('editor.showIndentGuide', !config.get('editor.showIndentGuide'))
documentation = {}
for name, method of editorBindings
@@ -330,6 +332,11 @@ class Editor extends View
cr: '\u00a4'
@resetDisplay()
+ setShowIndentGuide: (showIndentGuide) ->
+ return if showIndentGuide == @showIndentGuide
+ @showIndentGuide = showIndentGuide
+ @resetDisplay()
+
checkoutHead: -> @getBuffer().checkoutHead()
setText: (text) -> @getBuffer().setText(text)
getText: -> @getBuffer().getText()
@@ -346,6 +353,7 @@ class Editor extends View
configure: ->
@observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles)
+ @observeConfig 'editor.showIndentGuide', (showIndentGuide) => @setShowIndentGuide(showIndentGuide)
@observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles)
@observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize)
@observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily)
@@ -1110,13 +1118,34 @@ class Editor extends View
buildLineElementsForScreenRows: (startRow, endRow) ->
div = document.createElement('div')
- div.innerHTML = @buildLinesHtml(@activeEditSession.linesForScreenRows(startRow, endRow))
+ div.innerHTML = @buildLinesHtml(startRow, endRow)
new Array(div.children...)
- buildLinesHtml: (screenLines) ->
- screenLines.map((line) => @buildLineHtml(line)).join('\n\n')
+ buildLinesHtml: (startRow, endRow) ->
+ lines = @activeEditSession.linesForScreenRows(startRow, endRow)
+ htmlLines = []
+ screenRow = startRow
+ for line in @activeEditSession.linesForScreenRows(startRow, endRow)
+ htmlLines.push(@buildLineHtml(line, screenRow++))
+ htmlLines.join('\n\n')
- buildLineHtml: (screenLine) ->
+ buildEmptyLineHtml: (screenRow) ->
+ if not @mini and @showIndentGuide
+ indentation = 0
+ while --screenRow >= 0
+ bufferRow = @activeEditSession.bufferPositionForScreenPosition([screenRow]).row
+ bufferLine = @activeEditSession.lineForBufferRow(bufferRow)
+ unless bufferLine is ''
+ indentation = Math.ceil(@activeEditSession.indentLevelForLine(bufferLine))
+ break
+
+ if indentation > 0
+ indentationHtml = "#{_.multiplyString(' ', @activeEditSession.getTabLength())}"
+ return _.multiplyString(indentationHtml, indentation)
+
+ return ' ' unless @showInvisibles
+
+ buildLineHtml: (screenLine, screenRow) ->
scopeStack = []
line = []
@@ -1153,7 +1182,8 @@ class Editor extends View
invisibles = @invisibles if @showInvisibles
if screenLine.text == ''
- line.push(" ") unless @showInvisibles
+ html = @buildEmptyLineHtml(screenRow)
+ line.push(html) if html
else
firstNonWhitespacePosition = screenLine.text.search(/\S/)
firstTrailingWhitespacePosition = screenLine.text.search(/\s*$/)
@@ -1164,6 +1194,7 @@ class Editor extends View
invisibles: invisibles
hasLeadingWhitespace: position < firstNonWhitespacePosition
hasTrailingWhitespace: position + token.value.length > firstTrailingWhitespacePosition
+ hasIndentGuide: @showIndentGuide
))
position += token.value.length
diff --git a/src/app/token.coffee b/src/app/token.coffee
index 5a7c677ed..fa3f0dc6b 100644
--- a/src/app/token.coffee
+++ b/src/app/token.coffee
@@ -62,7 +62,8 @@ class Token
isOnlyWhitespace: ->
not /\S/.test(@value)
- getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace})->
+ getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})->
+ invisibles ?= {}
html = @value
.replace(/&/g, '&')
.replace(/"/g, '"')
@@ -70,26 +71,29 @@ class Token
.replace(//g, '>')
- if invisibles
- if @isHardTab and invisibles.tab
- html = html.replace(/^./, "#{invisibles.tab}")
- else if invisibles.space
- if hasLeadingWhitespace
- html = html.replace /^[ ]+/, (match) ->
- "#{match.replace(/./g, invisibles.space)}"
- if hasTrailingWhitespace
- html = html.replace /[ ]+$/, (match) ->
- "#{match.replace(/./g, invisibles.space)}"
+ if @isHardTab
+ html = html.replace /^./, (match) ->
+ classes = []
+ classes.push('invisible') if invisibles.tab
+ classes.push('indent-guide') if hasIndentGuide
+ classes.push('hard-tab')
+ match = invisibles.tab ? match
+ "#{match}"
else
- if @isHardTab
- html = html.replace /^./, (match) ->
- "#{match}"
- else
- if hasLeadingWhitespace
- html = html.replace /^[ ]+/, (match) ->
- "#{match}"
- if hasTrailingWhitespace
- html = html.replace /[ ]+$/, (match) ->
- "#{match}"
+ if hasLeadingWhitespace
+ html = html.replace /^[ ]+/, (match) ->
+ classes = []
+ classes.push('invisible') if invisibles.space
+ classes.push('indent-guide') if hasIndentGuide
+ classes.push('leading-whitespace')
+ match = match.replace(/./g, invisibles.space) if invisibles.space
+ "#{match}"
+ if hasTrailingWhitespace
+ html = html.replace /[ ]+$/, (match) ->
+ classes = []
+ classes.push('invisible') if invisibles.space
+ classes.push('trailing-whitespace')
+ match = match.replace(/./g, invisibles.space) if invisibles.space
+ "#{match}"
html
diff --git a/static/editor.css b/static/editor.css
index 13ed33b71..b75743fb0 100644
--- a/static/editor.css
+++ b/static/editor.css
@@ -90,6 +90,12 @@
font-style: normal !important;
}
+.editor .indent-guide {
+ opacity: 0.2;
+ display: inline-block;
+ box-shadow: inset 1px 0px;
+}
+
.editor .vertical-scrollbar {
position: absolute;
right: 0;