mirror of
https://github.com/atom/atom.git
synced 2026-01-22 21:38:10 -05:00
Add indent guide to editor
The guide displays a continuous vertical line across lines with the same indent levels. Closes #50
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 = "<span class='indent-guide'>#{_.multiplyString(' ', @activeEditSession.getTabLength())}</span>"
|
||||
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
|
||||
|
||||
@@ -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, '<')
|
||||
.replace(/>/g, '>')
|
||||
|
||||
if invisibles
|
||||
if @isHardTab and invisibles.tab
|
||||
html = html.replace(/^./, "<span class='invisible hard-tab'>#{invisibles.tab}</span>")
|
||||
else if invisibles.space
|
||||
if hasLeadingWhitespace
|
||||
html = html.replace /^[ ]+/, (match) ->
|
||||
"<span class='invisible leading-whitespace'>#{match.replace(/./g, invisibles.space)}</span>"
|
||||
if hasTrailingWhitespace
|
||||
html = html.replace /[ ]+$/, (match) ->
|
||||
"<span class='invisible trailing-whitespace'>#{match.replace(/./g, invisibles.space)}</span>"
|
||||
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
|
||||
"<span class='#{classes.join(' ')}'>#{match}</span>"
|
||||
else
|
||||
if @isHardTab
|
||||
html = html.replace /^./, (match) ->
|
||||
"<span class='hard-tab'>#{match}</span>"
|
||||
else
|
||||
if hasLeadingWhitespace
|
||||
html = html.replace /^[ ]+/, (match) ->
|
||||
"<span class='leading-whitespace'>#{match}</span>"
|
||||
if hasTrailingWhitespace
|
||||
html = html.replace /[ ]+$/, (match) ->
|
||||
"<span class='trailing-whitespace'>#{match}</span>"
|
||||
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
|
||||
"<span class='#{classes.join(' ')}'>#{match}</span>"
|
||||
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
|
||||
"<span class='#{classes.join(' ')}'>#{match}</span>"
|
||||
|
||||
html
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user