From e2a917fcf36ef30d84d395f5f806713fc73fe185 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 11 Oct 2012 10:25:04 -0700 Subject: [PATCH 01/13] Change tabText to tabLength --- benchmark/benchmark-suite.coffee | 2 +- spec/app/display-buffer-spec.coffee | 12 ++++---- spec/app/edit-session-spec.coffee | 41 ++++++++++++--------------- spec/app/editor-spec.coffee | 1 - spec/app/screen-line-spec.coffee | 5 ++-- spec/app/selection-spec.coffee | 2 +- spec/app/token-spec.coffee | 3 +- spec/app/tokenized-buffer-spec.coffee | 12 ++++---- src/app/display-buffer.coffee | 1 - src/app/edit-session.coffee | 9 ++++-- src/app/editor.coffee | 14 ++------- src/app/language-mode.coffee | 4 +-- src/app/project.coffee | 12 ++++---- src/app/selection.coffee | 6 ++-- src/app/token.coffee | 15 ++++++++-- src/app/tokenized-buffer.coffee | 6 ++-- 16 files changed, 72 insertions(+), 73 deletions(-) diff --git a/benchmark/benchmark-suite.coffee b/benchmark/benchmark-suite.coffee index 4e883cc78..c0b194a5a 100644 --- a/benchmark/benchmark-suite.coffee +++ b/benchmark/benchmark-suite.coffee @@ -91,7 +91,7 @@ describe "TokenizedBuffer.", -> { languageMode, buffer } = editSession benchmark "construction", 20, -> - new TokenizedBuffer(buffer, { languageMode, tabText: ' '}) + new TokenizedBuffer(buffer, { languageMode, tabLength: 2}) describe "OnigRegExp.", -> [regexes, line] = [] diff --git a/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee index 562501b2b..142fabaa6 100644 --- a/spec/app/display-buffer-spec.coffee +++ b/spec/app/display-buffer-spec.coffee @@ -2,10 +2,10 @@ DisplayBuffer = require 'display-buffer' Buffer = require 'buffer' describe "DisplayBuffer", -> - [editSession, displayBuffer, buffer, changeHandler, tabText] = [] + [editSession, displayBuffer, buffer, changeHandler, tabLength] = [] beforeEach -> - tabText = ' ' - editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabText }) + tabLength = 2 + editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabLength }) { buffer, displayBuffer } = editSession changeHandler = jasmine.createSpy 'changeHandler' displayBuffer.on 'change', changeHandler @@ -636,14 +636,14 @@ describe "DisplayBuffer", -> buffer.insert([0, 0], '\t') expect(displayBuffer.clipScreenPosition([0, 0])).toEqual [0, 0] expect(displayBuffer.clipScreenPosition([0, 1])).toEqual [0, 0] - expect(displayBuffer.clipScreenPosition([0, tabText.length])).toEqual [0, tabText.length] + expect(displayBuffer.clipScreenPosition([0, tabLength])).toEqual [0, tabLength] describe "when skipAtomicTokens is true", -> it "clips screen positions in the middle of atomic tab characters to the end of the character", -> buffer.insert([0, 0], '\t') expect(displayBuffer.clipScreenPosition([0, 0], skipAtomicTokens: true)).toEqual [0, 0] - expect(displayBuffer.clipScreenPosition([0, 1], skipAtomicTokens: true)).toEqual [0, tabText.length] - expect(displayBuffer.clipScreenPosition([0, tabText.length], skipAtomicTokens: true)).toEqual [0, tabText.length] + expect(displayBuffer.clipScreenPosition([0, 1], skipAtomicTokens: true)).toEqual [0, tabLength] + expect(displayBuffer.clipScreenPosition([0, tabLength], skipAtomicTokens: true)).toEqual [0, tabLength] describe ".maxLineLength()", -> it "returns the length of the longest screen line", -> diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 3347450e0..788859373 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -1141,8 +1141,8 @@ describe "EditSession", -> describe ".indent()", -> describe "when nothing is selected", -> describe "if 'softTabs' is true (the default)", -> - it "inserts the value of 'tabText' into the buffer", -> - tabRegex = new RegExp("^#{editSession.tabText}") + it "inserts 'tabLength' spaces into the buffer", -> + tabRegex = new RegExp("^[ ]{#{editSession.tabLength}}") expect(buffer.lineForRow(0)).not.toMatch(tabRegex) editSession.indent() expect(buffer.lineForRow(0)).toMatch(tabRegex) @@ -1151,7 +1151,7 @@ describe "EditSession", -> describe "when the preceding line opens a new level of indentation", -> it "increases the level of indentation by one", -> buffer.insert([5, 0], " \n") - editSession.tabText = " " + editSession.tabLength = 2 editSession.setCursorBufferPosition [5, 2] editSession.setAutoIndent(true) editSession.indent() @@ -1162,7 +1162,7 @@ describe "EditSession", -> describe "when there are empty lines preceding the current line", -> it "bases indentation on the first non-blank preceding line", -> buffer.insert([5, 0], "\n\n\n \n") - editSession.tabText = " " + editSession.tabLength = 2 editSession.setCursorBufferPosition [8, 2] editSession.setAutoIndent(true) editSession.indent() @@ -1172,7 +1172,7 @@ describe "EditSession", -> it "properly indents the line", -> buffer.insert([7, 0], " \n") - editSession.tabText = " " + editSession.tabLength = 2 editSession.setCursorBufferPosition [7, 2] editSession.setAutoIndent(true) editSession.indent() @@ -1182,7 +1182,7 @@ describe "EditSession", -> it "allows for additional indentation if the cursor is beyond the proper indentation point", -> buffer.insert([7, 0], " \n") - editSession.tabText = " " + editSession.tabLength = 2 editSession.setCursorBufferPosition [7, 6] editSession.setAutoIndent(true) editSession.indent() @@ -1197,12 +1197,12 @@ describe "EditSession", -> editSession.indent() expect(buffer.lineForRow(0)).toMatch(/^\t/) expect(editSession.getCursorBufferPosition()).toEqual [0, 1] - expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.tabText.length] + expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.tabLength] editSession.indent() expect(buffer.lineForRow(0)).toMatch(/^\t\t/) expect(editSession.getCursorBufferPosition()).toEqual [0, 2] - expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.tabText.length * 2] + expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.tabLength * 2] describe "pasteboard operations", -> pasteboard = null @@ -1254,24 +1254,22 @@ describe "EditSession", -> expect(buffer.lineForRow(1)).toBe " var first = function(items) {" describe ".indentSelectedRows()", -> - tabLength = null - beforeEach -> - tabLength = editSession.tabText.length + editSession.tabLength = 2 describe "when nothing is selected", -> it "indents line and retains selection", -> editSession.setSelectedBufferRange([[0,3], [0,3]]) editSession.indentSelectedRows() - expect(buffer.lineForRow(0)).toBe "#{editSession.tabText}var quicksort = function () {" - expect(editSession.getSelectedBufferRange()).toEqual [[0, 3 + tabLength], [0, 3 + tabLength]] + expect(buffer.lineForRow(0)).toBe "#{editSession.getTabText()}var quicksort = function () {" + expect(editSession.getSelectedBufferRange()).toEqual [[0, 3 + editSession.tabLength], [0, 3 + editSession.tabLength]] describe "when one line is selected", -> it "indents line and retains selection", -> editSession.setSelectedBufferRange([[0,4], [0,14]]) editSession.indentSelectedRows() - expect(buffer.lineForRow(0)).toBe "#{editSession.tabText}var quicksort = function () {" - expect(editSession.getSelectedBufferRange()).toEqual [[0, 4 + tabLength], [0, 14 + tabLength]] + expect(buffer.lineForRow(0)).toBe "#{editSession.getTabText()}var quicksort = function () {" + expect(editSession.getSelectedBufferRange()).toEqual [[0, 4 + editSession.tabLength], [0, 14 + editSession.tabLength]] describe "when multiple lines are selected", -> it "indents selected lines (that are not empty) and retains selection", -> @@ -1280,28 +1278,25 @@ describe "EditSession", -> expect(buffer.lineForRow(9)).toBe " };" expect(buffer.lineForRow(10)).toBe "" expect(buffer.lineForRow(11)).toBe " return sort(Array.apply(this, arguments));" - expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + tabLength], [11, 15 + tabLength]] + expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + editSession.tabLength], [11, 15 + editSession.tabLength]] describe ".outdentSelectedRows()", -> - tabLength = null - beforeEach -> - editSession.tabText = " " - tabLength = editSession.tabText.length + editSession.tabLength = 2 describe "when nothing is selected", -> it "outdents line and retains selection", -> editSession.setSelectedBufferRange([[1,3], [1,3]]) editSession.outdentSelectedRows() expect(buffer.lineForRow(1)).toBe "var sort = function(items) {" - expect(editSession.getSelectedBufferRange()).toEqual [[1, 3 - tabLength], [1, 3 - tabLength]] + expect(editSession.getSelectedBufferRange()).toEqual [[1, 3 - editSession.tabLength], [1, 3 - editSession.tabLength]] describe "when one line is selected", -> it "outdents line and retains editSession", -> editSession.setSelectedBufferRange([[1,4], [1,14]]) editSession.outdentSelectedRows() expect(buffer.lineForRow(1)).toBe "var sort = function(items) {" - expect(editSession.getSelectedBufferRange()).toEqual [[1, 4 - tabLength], [1, 14 - tabLength]] + expect(editSession.getSelectedBufferRange()).toEqual [[1, 4 - editSession.tabLength], [1, 14 - editSession.tabLength]] describe "when multiple lines are selected", -> it "outdents selected lines and retains editSession", -> @@ -1310,7 +1305,7 @@ describe "EditSession", -> expect(buffer.lineForRow(0)).toBe "var quicksort = function () {" expect(buffer.lineForRow(1)).toBe "var sort = function(items) {" expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;" - expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 15 - tabLength]] + expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 15 - editSession.tabLength]] describe ".toggleLineCommentsInSelection()", -> it "toggles comments on the selected lines", -> diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 93fdc36a7..0b2e246ce 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1595,7 +1595,6 @@ describe "Editor", -> expect(miniEditor.getCursorBufferPosition().row).toBe 0 expect(miniEditor.find('.line.cursor-line').length).toBe 0 - describe "gutter line highlighting", -> beforeEach -> editor.attachToDom(heightInLines: 5.5) diff --git a/spec/app/screen-line-spec.coffee b/spec/app/screen-line-spec.coffee index 4d0d9d181..9ea847856 100644 --- a/spec/app/screen-line-spec.coffee +++ b/spec/app/screen-line-spec.coffee @@ -3,11 +3,10 @@ Buffer = require 'buffer' TokenizedBuffer = require 'tokenized-buffer' describe "ScreenLine", -> - [editSession, buffer, tabText, screenLine, tokenizedBuffer] = [] + [editSession, buffer, screenLine, tokenizedBuffer] = [] beforeEach -> - tabText = '••' - editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabText } ) + editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabLength: 2 } ) { buffer, tokenizedBuffer } = editSession screenLine = tokenizedBuffer.lineForScreenRow(3) diff --git a/spec/app/selection-spec.coffee b/spec/app/selection-spec.coffee index a43ad7f1e..27427ae7a 100644 --- a/spec/app/selection-spec.coffee +++ b/spec/app/selection-spec.coffee @@ -7,7 +7,7 @@ describe "Selection", -> beforeEach -> buffer = new Buffer(require.resolve('fixtures/sample.js')) - editSession = new EditSession(buffer: buffer, tabText: ' ') + editSession = new EditSession(buffer: buffer, tabLength: 2) selection = editSession.getSelection() afterEach -> diff --git a/spec/app/token-spec.coffee b/spec/app/token-spec.coffee index 08758bf91..f96963983 100644 --- a/spec/app/token-spec.coffee +++ b/spec/app/token-spec.coffee @@ -6,8 +6,7 @@ describe "Token", -> [editSession, token] = [] beforeEach -> - tabText = ' ' - editSession = fixturesProject.buildEditSessionForPath('sample.js') + editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabLength: 2 }) { tokenizedBuffer } = editSession screenLine = tokenizedBuffer.lineForScreenRow(3) token = _.last(screenLine.tokens) diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee index c3cd3dd1a..11a188e0a 100644 --- a/spec/app/tokenized-buffer-spec.coffee +++ b/spec/app/tokenized-buffer-spec.coffee @@ -151,25 +151,25 @@ describe "TokenizedBuffer", -> expect(event.newRange).toEqual new Range([2, 0], [7, buffer.lineForRow(7).length]) describe "when the buffer contains tab characters", -> - tabText = ' ' editSession2 = null beforeEach -> - editSession2 = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', { tabText }) + tabLength = 2 + editSession2 = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', { tabLength }) { buffer, tokenizedBuffer } = editSession2 afterEach -> editSession2.destroy() - it "always renders each tab as its own atomic token containing tabText", -> + it "always renders each tab as its own atomic token with a value of size tabLength", -> screenLine0 = tokenizedBuffer.lineForScreenRow(0) - expect(screenLine0.text).toBe "# Econ 101#{tabText}" + expect(screenLine0.text).toBe "# Econ 101#{editSession2.getTabText()}" { tokens } = screenLine0 expect(tokens.length).toBe 3 expect(tokens[0].value).toBe "#" expect(tokens[1].value).toBe " Econ 101" - expect(tokens[2].value).toBe tabText + expect(tokens[2].value).toBe editSession2.getTabText() expect(tokens[2].scopes).toEqual tokens[1].scopes expect(tokens[2].isAtomic).toBeTruthy() - expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabText} buy()#{tabText}while supply > demand" + expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{editSession2.getTabText()} buy()#{editSession2.getTabText()}while supply > demand" diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index edbdc2831..346b1183c 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -20,7 +20,6 @@ class DisplayBuffer constructor: (@buffer, options={}) -> @id = @constructor.idCounter++ - options.tabText ?= ' ' @languageMode = options.languageMode @tokenizedBuffer = new TokenizedBuffer(@buffer, options) @softWrapColumn = options.softWrapColumn ? Infinity diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index e861d00ac..66ad0d8d3 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -37,12 +37,13 @@ class EditSession autoIndent: false # TODO: re-enabled auto-indent after fixing the rest of tokenization softTabs: true softWrap: false + tabLength: null - constructor: ({@project, @buffer, @tabText, @autoIndent, @softTabs, @softWrap}) -> + constructor: ({@project, @buffer, @tabLength, @autoIndent, @softTabs, @softWrap}) -> @id = @constructor.idCounter++ @softTabs ?= true @languageMode = new LanguageMode(this, @buffer.getExtension()) - @displayBuffer = new DisplayBuffer(@buffer, { @languageMode, @tabText }) + @displayBuffer = new DisplayBuffer(@buffer, { @languageMode, @tabLength }) @tokenizedBuffer = @displayBuffer.tokenizedBuffer @anchors = [] @anchorRanges = [] @@ -106,6 +107,8 @@ class EditSession getSoftWrap: -> @softWrap setSoftWrap: (@softWrap) -> + getTabText: -> new Array(@tabLength + 1).join(" ") + clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition) @@ -149,7 +152,7 @@ class EditSession currentRow = @getCursorBufferPosition().row if @getSelection().isEmpty() if @softTabs - @insertText(@tabText) + @insertText(@getTabText()) else @insertText('\t') else diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 3da6a88a1..1d6b87ac4 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -69,7 +69,7 @@ class Editor extends View editSession = new EditSession buffer: new Buffer() softWrap: false - tabText: " " + tabLength: 2 autoIndent: false softTabs: true @@ -878,16 +878,8 @@ class Editor extends View else for token in screenLine.tokens updateScopeStack(token.scopes) - line.push( - token.value - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>') - ) - - line.push("") + line.push(token.escapeValue()) + line.push('') line.join('') insertLineElements: (row, lineElements) -> diff --git a/src/app/language-mode.coffee b/src/app/language-mode.coffee index 312a0f2f5..e2fac8b42 100644 --- a/src/app/language-mode.coffee +++ b/src/app/language-mode.coffee @@ -104,7 +104,7 @@ class LanguageMode currentIndentation = @buffer.indentationForRow(bufferRow) desiredIndentation = @buffer.indentationForRow(precedingRow) - desiredIndentation += @editSession.tabText.length if increaseIndentPattern.test(precedingLine) + desiredIndentation += @editSession.tabLength if increaseIndentPattern.test(precedingLine) if desiredIndentation > currentIndentation @buffer.setIndentationForRow(bufferRow, desiredIndentation) @@ -122,7 +122,7 @@ class LanguageMode precedingLine = @buffer.lineForRow(precedingRow) desiredIndentation = @buffer.indentationForRow(precedingRow) - desiredIndentation -= @editSession.tabText.length unless increaseIndentPattern.test(precedingLine) + desiredIndentation -= @editSession.tabLength unless increaseIndentPattern.test(precedingLine) if desiredIndentation < currentIndentation @buffer.setIndentationForRow(bufferRow, desiredIndentation) diff --git a/src/app/project.coffee b/src/app/project.coffee index 1ce285833..f11013a9e 100644 --- a/src/app/project.coffee +++ b/src/app/project.coffee @@ -10,10 +10,11 @@ ChildProcess = require 'child-process' module.exports = class Project - tabText: ' ' + tabLength: 2 autoIndent: true softTabs: true softWrap: false + showInvisibles: false rootDirectory: null editSessions: null ignoredPathRegexes: null @@ -101,9 +102,6 @@ class Project relativize: (fullPath) -> fullPath.replace(@getPath(), "").replace(/^\//, '') - getTabText: -> @tabText - setTabText: (@tabText) -> - getAutoIndent: -> @autoIndent setAutoIndent: (@autoIndent) -> @@ -113,6 +111,9 @@ class Project getSoftWrap: -> @softWrap setSoftWrap: (@softWrap) -> + getShowInvisibles: -> @showInvisibles + setShowInvisibles: (@showInvisibles) -> + buildEditSessionForPath: (filePath, editSessionOptions={}) -> @buildEditSession(@bufferForPath(filePath), editSessionOptions) @@ -126,10 +127,11 @@ class Project editSession defaultEditSessionOptions: -> - tabText: @getTabText() + tabLength: @tabLength autoIndent: @getAutoIndent() softTabs: @getSoftTabs() softWrap: @getSoftWrap() + showInvisibles: @getShowInvisibles() getEditSessions: -> new Array(@editSessions...) diff --git a/src/app/selection.coffee b/src/app/selection.coffee index 0e0b711f7..4d050abba 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -208,15 +208,15 @@ class Selection indentSelectedRows: -> range = @getBufferRange() for row in [range.start.row..range.end.row] - @editSession.buffer.insert([row, 0], @editSession.tabText) unless @editSession.buffer.lineLengthForRow(row) == 0 + @editSession.buffer.insert([row, 0], @editSession.getTabText()) unless @editSession.buffer.lineLengthForRow(row) == 0 outdentSelectedRows: -> range = @getBufferRange() buffer = @editSession.buffer - leadingTabRegex = new RegExp("^#{@editSession.tabText}") + leadingTabRegex = new RegExp("^#{@editSession.getTabText()}") for row in [range.start.row..range.end.row] if leadingTabRegex.test buffer.lineForRow(row) - buffer.delete [[row, 0], [row, @editSession.tabText.length]] + buffer.delete [[row, 0], [row, @editSession.tabLength]] toggleLineComments: -> @modifySelection => diff --git a/src/app/token.coffee b/src/app/token.coffee index 80710ce9d..9fd520be6 100644 --- a/src/app/token.coffee +++ b/src/app/token.coffee @@ -21,14 +21,23 @@ class Token value2 = @value.substring(splitIndex) [new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)] - breakOutTabCharacters: (tabText) -> + breakOutTabCharacters: (tabLength) -> return [this] unless /\t/.test(@value) for substring in @value.match(/([^\t]+|\t)/g) if substring == '\t' - @buildTabToken(tabText) + @buildTabToken(tabLength) else new Token(value: substring, scopes: @scopes) - buildTabToken: (tabText) -> + buildTabToken: (tabLength) -> + tabText = new Array(tabLength + 1).join(" ") new Token(value: tabText, scopes: @scopes, bufferDelta: 1, isAtomic: true) + + escapeValue: -> + @value + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>') diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee index 02285a505..0e73574b8 100644 --- a/src/app/tokenized-buffer.coffee +++ b/src/app/tokenized-buffer.coffee @@ -10,11 +10,13 @@ class TokenizedBuffer @idCounter: 1 languageMode: null + tabLength: null buffer: null aceAdaptor: null screenLines: null - constructor: (@buffer, { @languageMode, @tabText }) -> + constructor: (@buffer, { @languageMode, @tabLength }) -> + @tabLength ?= 2 @languageMode.tokenizedBuffer = this @id = @constructor.idCounter++ @screenLines = @buildScreenLinesForRows(0, @buffer.getLastRow()) @@ -64,7 +66,7 @@ class TokenizedBuffer tokenObjects = [] for tokenProperties in tokens token = new Token(tokenProperties) - tokenObjects.push(token.breakOutTabCharacters(@tabText)...) + tokenObjects.push(token.breakOutTabCharacters(@tabLength)...) text = _.pluck(tokenObjects, 'value').join('') new ScreenLine(tokenObjects, text, [1, 0], [1, 0], { stack }) From 4478bbca9aaae942a4be0195859aa672a9f9750c Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 11 Oct 2012 11:47:55 -0700 Subject: [PATCH 02/13] When project.showInvisibles is true; spaces, tabs and newlines are visible --- spec/app/editor-spec.coffee | 19 ++++++++++++++----- src/app/edit-session.coffee | 5 +++-- src/app/editor.coffee | 13 +++++++------ src/app/token.coffee | 21 ++++++++++++++++----- 4 files changed, 40 insertions(+), 18 deletions(-) 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 From 22e009a99945cf5c2fe09084503946b4c9298db2 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 11 Oct 2012 14:18:45 -0700 Subject: [PATCH 03/13] Set invisible values for spaces and tabs when initial tokenization occurs. Also break whitespace into its own token just like tabs. --- spec/app/editor-spec.coffee | 5 +-- spec/app/token-spec.coffee | 46 +++++++++++++++++++++++++++ spec/app/tokenized-buffer-spec.coffee | 21 ++++++------ src/app/edit-session.coffee | 2 +- src/app/token.coffee | 25 +++++++-------- src/app/tokenized-buffer.coffee | 5 +-- 6 files changed, 76 insertions(+), 28 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 5446777fa..e2fb5a703 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1828,9 +1828,10 @@ describe "Editor", -> expect(editor.getCursor().getScreenPosition().row).toBe(0) expect(editor.getFirstVisibleScreenRow()).toBe(0) - describe "when showInvisibles is enabled on the editSession", -> + describe "when editSession.showInvisibles is true", -> beforeEach -> - editor.activeEditSession.showInvisibles = true + project.setShowInvisibles(true) + rootView.open() editor.attachToDom(5) it "displays spaces as •, tabs as ▸ and newlines as ¬", -> diff --git a/spec/app/token-spec.coffee b/spec/app/token-spec.coffee index f96963983..57004fd93 100644 --- a/spec/app/token-spec.coffee +++ b/spec/app/token-spec.coffee @@ -1,4 +1,5 @@ _ = require 'underscore' +Token = require 'token' Buffer = require 'buffer' TokenizedBuffer = require 'tokenized-buffer' @@ -13,3 +14,48 @@ describe "Token", -> afterEach -> editSession.destroy() + + describe ".breakOutWhitespaceCharacters(tabLength, showInvisbles)", -> + describe "when showInvisbles is false", -> + it "replaces spaces and tabs with their own tokens", -> + value = " spaces tabs\t\t\t " + scopes = ["whatever"] + isAtomic = false + bufferDelta = value.length + token = new Token({value, scopes, isAtomic, bufferDelta}) + tokens = token.breakOutWhitespaceCharacters(4, false) + + expect(tokens.length).toBe(8) + expect(tokens[0].value).toBe(" ") + expect(tokens[0].scopes).not.toContain("invisible") + expect(tokens[1].value).toBe("spaces") + expect(tokens[1].scopes).not.toContain("invisible") + expect(tokens[2].value).toBe(" ") + expect(tokens[3].value).toBe("tabs") + expect(tokens[4].value).toBe(" ") + expect(tokens[4].scopes).not.toContain("invisible") + expect(tokens[5].value).toBe(" ") + expect(tokens[6].value).toBe(" ") + expect(tokens[7].value).toBe(" ") + + describe "when showInvisbles is true", -> + it "replaces spaces and tabs with their own tokens", -> + value = " spaces tabs\t\t\t " + scopes = ["whatever"] + isAtomic = false + bufferDelta = value.length + token = new Token({value, scopes, isAtomic, bufferDelta}) + tokens = token.breakOutWhitespaceCharacters(4, true) + + expect(tokens.length).toBe(8) + expect(tokens[0].value).toBe("•") + expect(tokens[0].scopes).toContain("invisible") + expect(tokens[1].value).toBe("spaces") + expect(tokens[1].scopes).not.toContain("invisible") + expect(tokens[2].value).toBe("•••") + expect(tokens[3].value).toBe("tabs") + expect(tokens[4].value).toBe("▸ ") + expect(tokens[4].scopes).toContain("invisible") + expect(tokens[5].value).toBe("▸ ") + expect(tokens[6].value).toBe("▸ ") + expect(tokens[7].value).toBe("••") diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee index 11a188e0a..c40740571 100644 --- a/spec/app/tokenized-buffer-spec.coffee +++ b/spec/app/tokenized-buffer-spec.coffee @@ -81,12 +81,12 @@ describe "TokenizedBuffer", -> # previous line 3 should be combined with input to form line 1 expect(tokenizedBuffer.lineForScreenRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js']) - expect(tokenizedBuffer.lineForScreenRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(1).tokens[8]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) - # lines below deleted regions should be shifted upward + # # lines below deleted regions should be shifted upward expect(tokenizedBuffer.lineForScreenRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js']) - expect(tokenizedBuffer.lineForScreenRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) - expect(tokenizedBuffer.lineForScreenRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(3).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(4).tokens[3]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js']) expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] @@ -125,7 +125,7 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) # previous line 3 is pushed down to become line 5 - expect(tokenizedBuffer.lineForScreenRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(5).tokens[5]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] @@ -165,11 +165,12 @@ describe "TokenizedBuffer", -> screenLine0 = tokenizedBuffer.lineForScreenRow(0) expect(screenLine0.text).toBe "# Econ 101#{editSession2.getTabText()}" { tokens } = screenLine0 - expect(tokens.length).toBe 3 + expect(tokens.length).toBe 6 expect(tokens[0].value).toBe "#" - expect(tokens[1].value).toBe " Econ 101" - expect(tokens[2].value).toBe editSession2.getTabText() - expect(tokens[2].scopes).toEqual tokens[1].scopes - expect(tokens[2].isAtomic).toBeTruthy() + expect(tokens[2].value).toBe "Econ" + expect(tokens[4].value).toBe "101" + expect(tokens[5].value).toBe editSession2.getTabText() + expect(tokens[5].scopes).toEqual tokens[1].scopes + expect(tokens[5].isAtomic).toBeTruthy() expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{editSession2.getTabText()} buy()#{editSession2.getTabText()}while supply > demand" diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 34dea2477..12e1f6ead 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -44,7 +44,7 @@ class EditSession @id = @constructor.idCounter++ @softTabs ?= true @languageMode = new LanguageMode(this, @buffer.getExtension()) - @displayBuffer = new DisplayBuffer(@buffer, { @languageMode, @tabLength }) + @displayBuffer = new DisplayBuffer(@buffer, { @languageMode, @tabLength, @showInvisibles }) @tokenizedBuffer = @displayBuffer.tokenizedBuffer @anchors = [] @anchorRanges = [] diff --git a/src/app/token.coffee b/src/app/token.coffee index af1ece5a2..598a55e1a 100644 --- a/src/app/token.coffee +++ b/src/app/token.coffee @@ -5,7 +5,6 @@ class Token value: null scopes: null isAtomic: null - isTab: null constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @fold, @isTab}) -> @screenDelta = @value.length @@ -22,13 +21,19 @@ class Token value2 = @value.substring(splitIndex) [new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)] - breakOutTabCharacters: (tabLength) -> - return [this] unless /\t/.test(@value) + breakOutWhitespaceCharacters: (tabLength, showInvisibles) -> + return [this] unless /\t| /.test(@value) - tabText = new Array(tabLength + 1).join(" ") - for substring in @value.match(/([^\t]+|\t)/g) - if substring == '\t' - new Token(value: tabText, scopes: @scopes, bufferDelta: 1, isAtomic: true, isTab: true) + for substring in @value.match(/([^\t ]+|(\t| +))/g) + scopesForInvisibles = if showInvisibles then @scopes.concat("invisible") else @scopes + + if substring == "\t" + value = new Array(tabLength + 1).join(" ") + value = "▸" + value[1..] if showInvisibles + new Token(value: value, scopes: scopesForInvisibles, bufferDelta: 1, isAtomic: true) + else if /^ +$/.test(substring) + value = if showInvisibles then substring.replace(/[ ]/g, "•") else substring + new Token(value: value, scopes: scopesForInvisibles) else new Token(value: substring, scopes: @scopes) @@ -45,10 +50,4 @@ class Token .replace(//g, '>') - if showInvisibles - if @isTab - value = "▸" + value[1..] - else - value = value.replace(/[ ]+/g, "•") - value diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee index 0e73574b8..b2776454e 100644 --- a/src/app/tokenized-buffer.coffee +++ b/src/app/tokenized-buffer.coffee @@ -11,11 +11,12 @@ class TokenizedBuffer languageMode: null tabLength: null + showInvisibles: null buffer: null aceAdaptor: null screenLines: null - constructor: (@buffer, { @languageMode, @tabLength }) -> + constructor: (@buffer, { @languageMode, @tabLength, @showInvisibles }) -> @tabLength ?= 2 @languageMode.tokenizedBuffer = this @id = @constructor.idCounter++ @@ -66,7 +67,7 @@ class TokenizedBuffer tokenObjects = [] for tokenProperties in tokens token = new Token(tokenProperties) - tokenObjects.push(token.breakOutTabCharacters(@tabLength)...) + tokenObjects.push(token.breakOutWhitespaceCharacters(@tabLength, @showInvisibles)...) text = _.pluck(tokenObjects, 'value').join('') new ScreenLine(tokenObjects, text, [1, 0], [1, 0], { stack }) From d3c52ae96d1b4436f8aefe2ac24ea35100eeb94e Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 11 Oct 2012 14:29:00 -0700 Subject: [PATCH 04/13] Make invisible coloring less obnoxious --- static/editor.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/editor.css b/static/editor.css index 70087e223..6984b979a 100644 --- a/static/editor.css +++ b/static/editor.css @@ -123,3 +123,7 @@ .editor .fold.selected { background-color: #244; } + +.editor .invisible { + opacity: 0.2; +} \ No newline at end of file From 1ffbff7d99b3aaa167acb1ac6d8c7c3a9e96a841 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 11 Oct 2012 14:29:21 -0700 Subject: [PATCH 05/13] Fix newline invisible symbol --- src/app/editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index bf1080284..c5a3d60ee 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -878,7 +878,7 @@ class Editor extends View line.push(token.escapeValue(@activeEditSession.showInvisibles)) if @activeEditSession.showInvisibles - line.push("") + line.push("") line.push('') line.join('') From d7bc03112b912ca7e158912c1fd8e98eb6a0b072 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 11 Oct 2012 17:06:34 -0700 Subject: [PATCH 06/13] Empty lines require a special case for invisibles. --- src/app/editor.coffee | 9 ++++++--- src/app/token.coffee | 6 +----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index c5a3d60ee..0059d7cb3 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -873,12 +873,15 @@ class Editor extends View attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of lineAttributes line.push("
")
 
-    for token in screenLine.tokens
-      updateScopeStack(token.scopes)
-      line.push(token.escapeValue(@activeEditSession.showInvisibles))
+    unless screenLine.text == ''
+      for token in screenLine.tokens
+        updateScopeStack(token.scopes)
+        line.push(token.escapeValue(@activeEditSession.showInvisibles))
 
     if @activeEditSession.showInvisibles
       line.push("")
+    else if screenLine.text == ''
+      line.push(" ")
 
     line.push('
') line.join('') diff --git a/src/app/token.coffee b/src/app/token.coffee index 598a55e1a..636d94c93 100644 --- a/src/app/token.coffee +++ b/src/app/token.coffee @@ -41,13 +41,9 @@ class Token tabText = new Array(tabLength + 1).join(" ") escapeValue: (showInvisibles)-> - return " " if @value == "" - - value = @value + @value .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>') - - value From c9db576a1c900fdd3ec8b2641934b15038dcf409 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 11 Oct 2012 17:17:40 -0700 Subject: [PATCH 07/13] Fix specs --- spec/app/editor-spec.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index e2fb5a703..d55d54d37 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) span').html()).toBe ' ' + expect(editor.renderedLines.find('.line:eq(10)').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 span').html()).toBe ' ' # line 10 is blank + expect(editor.renderedLines.find('.line:last').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 span').html()).toBe ' ' # line 10 is blank + expect(editor.renderedLines.find('.line:last').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 span').html()).toBe ' ' # line 10 is blank + expect(editor.renderedLines.find('.line:last').html()).toBe ' ' # line 10 is blank editor.scrollTop(0) expect(editor.renderedLines.find('.line').length).toBe 8 From 53534e61cf5029607fd5c642489adef23943a584 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Fri, 12 Oct 2012 09:45:52 -0700 Subject: [PATCH 08/13] Remove double comment --- spec/app/tokenized-buffer-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee index c40740571..5cb716d85 100644 --- a/spec/app/tokenized-buffer-spec.coffee +++ b/spec/app/tokenized-buffer-spec.coffee @@ -83,7 +83,7 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.lineForScreenRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js']) expect(tokenizedBuffer.lineForScreenRow(1).tokens[8]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) - # # lines below deleted regions should be shifted upward + # lines below deleted regions should be shifted upward expect(tokenizedBuffer.lineForScreenRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js']) expect(tokenizedBuffer.lineForScreenRow(3).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) expect(tokenizedBuffer.lineForScreenRow(4).tokens[3]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js']) From dd5a10e82e358f6c9eef9a9d5968f024ebc079d1 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 18 Oct 2012 10:32:59 -0700 Subject: [PATCH 09/13] :lipstick: --- src/app/editor.coffee | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 0059d7cb3..5ecb3dbfd 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -873,16 +873,14 @@ class Editor extends View attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of lineAttributes line.push("
")
 
-    unless screenLine.text == ''
+    if screenLine.text == ''
+      line.push(" ") unless @activeEditSession.showInvisibles
+    else
       for token in screenLine.tokens
         updateScopeStack(token.scopes)
         line.push(token.escapeValue(@activeEditSession.showInvisibles))
 
-    if @activeEditSession.showInvisibles
-      line.push("")
-    else if screenLine.text == ''
-      line.push(" ")
-
+    line.push("") if @activeEditSession.showInvisibles
     line.push('
') line.join('') From b33bbbfc0d36bdf3a98c129a5072e52cb188aadf Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 18 Oct 2012 11:43:17 -0700 Subject: [PATCH 10/13] Handle invisible character rendering when building HTML for lines. Not during creation of tokens. --- spec/app/editor-spec.coffee | 20 ++++----- spec/app/token-spec.coffee | 61 --------------------------- spec/app/tokenized-buffer-spec.coffee | 19 ++++----- src/app/editor.coffee | 11 ++++- src/app/token.coffee | 41 ++++++++++++------ src/app/tokenized-buffer.coffee | 2 +- 6 files changed, 57 insertions(+), 97 deletions(-) delete mode 100644 spec/app/token-spec.coffee diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index d55d54d37..55d5c6937 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1472,6 +1472,16 @@ describe "Editor", -> expect(editor.renderedLines.find('.line:eq(14)').text()).toBe 'B' expect(editor.renderedLines.find('.line:eq(15)')).not.toExist() + describe "when editSession.showInvisibles is true", -> + beforeEach -> + project.setShowInvisibles(true) + rootView.open() + 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•¬" + describe "gutter rendering", -> beforeEach -> editor.attachToDom(heightInLines: 5.5) @@ -1827,13 +1837,3 @@ describe "Editor", -> editor.pageUp() expect(editor.getCursor().getScreenPosition().row).toBe(0) expect(editor.getFirstVisibleScreenRow()).toBe(0) - - describe "when editSession.showInvisibles is true", -> - beforeEach -> - project.setShowInvisibles(true) - rootView.open() - 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/spec/app/token-spec.coffee b/spec/app/token-spec.coffee deleted file mode 100644 index 57004fd93..000000000 --- a/spec/app/token-spec.coffee +++ /dev/null @@ -1,61 +0,0 @@ -_ = require 'underscore' -Token = require 'token' -Buffer = require 'buffer' -TokenizedBuffer = require 'tokenized-buffer' - -describe "Token", -> - [editSession, token] = [] - - beforeEach -> - editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabLength: 2 }) - { tokenizedBuffer } = editSession - screenLine = tokenizedBuffer.lineForScreenRow(3) - token = _.last(screenLine.tokens) - - afterEach -> - editSession.destroy() - - describe ".breakOutWhitespaceCharacters(tabLength, showInvisbles)", -> - describe "when showInvisbles is false", -> - it "replaces spaces and tabs with their own tokens", -> - value = " spaces tabs\t\t\t " - scopes = ["whatever"] - isAtomic = false - bufferDelta = value.length - token = new Token({value, scopes, isAtomic, bufferDelta}) - tokens = token.breakOutWhitespaceCharacters(4, false) - - expect(tokens.length).toBe(8) - expect(tokens[0].value).toBe(" ") - expect(tokens[0].scopes).not.toContain("invisible") - expect(tokens[1].value).toBe("spaces") - expect(tokens[1].scopes).not.toContain("invisible") - expect(tokens[2].value).toBe(" ") - expect(tokens[3].value).toBe("tabs") - expect(tokens[4].value).toBe(" ") - expect(tokens[4].scopes).not.toContain("invisible") - expect(tokens[5].value).toBe(" ") - expect(tokens[6].value).toBe(" ") - expect(tokens[7].value).toBe(" ") - - describe "when showInvisbles is true", -> - it "replaces spaces and tabs with their own tokens", -> - value = " spaces tabs\t\t\t " - scopes = ["whatever"] - isAtomic = false - bufferDelta = value.length - token = new Token({value, scopes, isAtomic, bufferDelta}) - tokens = token.breakOutWhitespaceCharacters(4, true) - - expect(tokens.length).toBe(8) - expect(tokens[0].value).toBe("•") - expect(tokens[0].scopes).toContain("invisible") - expect(tokens[1].value).toBe("spaces") - expect(tokens[1].scopes).not.toContain("invisible") - expect(tokens[2].value).toBe("•••") - expect(tokens[3].value).toBe("tabs") - expect(tokens[4].value).toBe("▸ ") - expect(tokens[4].scopes).toContain("invisible") - expect(tokens[5].value).toBe("▸ ") - expect(tokens[6].value).toBe("▸ ") - expect(tokens[7].value).toBe("••") diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee index 5cb716d85..11a188e0a 100644 --- a/spec/app/tokenized-buffer-spec.coffee +++ b/spec/app/tokenized-buffer-spec.coffee @@ -81,12 +81,12 @@ describe "TokenizedBuffer", -> # previous line 3 should be combined with input to form line 1 expect(tokenizedBuffer.lineForScreenRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js']) - expect(tokenizedBuffer.lineForScreenRow(1).tokens[8]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) # lines below deleted regions should be shifted upward expect(tokenizedBuffer.lineForScreenRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js']) - expect(tokenizedBuffer.lineForScreenRow(3).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) - expect(tokenizedBuffer.lineForScreenRow(4).tokens[3]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js']) expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] @@ -125,7 +125,7 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) # previous line 3 is pushed down to become line 5 - expect(tokenizedBuffer.lineForScreenRow(5).tokens[5]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) + expect(tokenizedBuffer.lineForScreenRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js']) expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] @@ -165,12 +165,11 @@ describe "TokenizedBuffer", -> screenLine0 = tokenizedBuffer.lineForScreenRow(0) expect(screenLine0.text).toBe "# Econ 101#{editSession2.getTabText()}" { tokens } = screenLine0 - expect(tokens.length).toBe 6 + expect(tokens.length).toBe 3 expect(tokens[0].value).toBe "#" - expect(tokens[2].value).toBe "Econ" - expect(tokens[4].value).toBe "101" - expect(tokens[5].value).toBe editSession2.getTabText() - expect(tokens[5].scopes).toEqual tokens[1].scopes - expect(tokens[5].isAtomic).toBeTruthy() + expect(tokens[1].value).toBe " Econ 101" + expect(tokens[2].value).toBe editSession2.getTabText() + expect(tokens[2].scopes).toEqual tokens[1].scopes + expect(tokens[2].isAtomic).toBeTruthy() expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{editSession2.getTabText()} buy()#{editSession2.getTabText()}while supply > demand" diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 5ecb3dbfd..c4fc11c91 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -876,9 +876,18 @@ class Editor extends View if screenLine.text == '' line.push(" ") unless @activeEditSession.showInvisibles else + firstNonWhitespacePosition = screenLine.text.search(/\S/) + firstTrailingWhitespacePosition = screenLine.text.search(/\s*$/) + position = 0 for token in screenLine.tokens updateScopeStack(token.scopes) - line.push(token.escapeValue(@activeEditSession.showInvisibles)) + line.push(token.getValueAsHtml( + showInvisibles: @activeEditSession.showInvisibles + hasLeadingWhitespace: position < firstNonWhitespacePosition + hasTrailingWhitespace: position + token.value.length > firstTrailingWhitespacePosition + )) + + position += token.value.length line.push("") if @activeEditSession.showInvisibles line.push('') diff --git a/src/app/token.coffee b/src/app/token.coffee index 636d94c93..a413cd521 100644 --- a/src/app/token.coffee +++ b/src/app/token.coffee @@ -5,6 +5,7 @@ class Token value: null scopes: null isAtomic: null + isTab: null constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @fold, @isTab}) -> @screenDelta = @value.length @@ -21,29 +22,41 @@ class Token value2 = @value.substring(splitIndex) [new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)] - breakOutWhitespaceCharacters: (tabLength, showInvisibles) -> - return [this] unless /\t| /.test(@value) - - for substring in @value.match(/([^\t ]+|(\t| +))/g) - scopesForInvisibles = if showInvisibles then @scopes.concat("invisible") else @scopes + breakOutTabCharacters: (tabLength, showInvisibles) -> + return [this] unless /\t/.test(@value) + for substring in @value.match(/[^\t]+|\t/g) if substring == "\t" - value = new Array(tabLength + 1).join(" ") - value = "▸" + value[1..] if showInvisibles - new Token(value: value, scopes: scopesForInvisibles, bufferDelta: 1, isAtomic: true) - else if /^ +$/.test(substring) - value = if showInvisibles then substring.replace(/[ ]/g, "•") else substring - new Token(value: value, scopes: scopesForInvisibles) + @buildTabToken(tabLength) else new Token(value: substring, scopes: @scopes) buildTabToken: (tabLength) -> - tabText = new Array(tabLength + 1).join(" ") + new Token( + value: new Array(tabLength + 1).join(" ") + scopes: @scopes + bufferDelta: 1 + isAtomic: true + isTab: true + ) - escapeValue: (showInvisibles)-> - @value + getValueAsHtml: ({showInvisibles, hasLeadingWhitespace, hasTrailingWhitespace})-> + html = @value .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>') + + if showInvisibles + if @isTab + html = html.replace(/^./, "") + else + if hasLeadingWhitespace + html = html.replace /^[ ]+/, (match) -> + "" + if hasTrailingWhitespace + html = html.replace /[ ]+$/, (match) -> + "" + + html diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee index b2776454e..19faf3ab7 100644 --- a/src/app/tokenized-buffer.coffee +++ b/src/app/tokenized-buffer.coffee @@ -67,7 +67,7 @@ class TokenizedBuffer tokenObjects = [] for tokenProperties in tokens token = new Token(tokenProperties) - tokenObjects.push(token.breakOutWhitespaceCharacters(@tabLength, @showInvisibles)...) + tokenObjects.push(token.breakOutTabCharacters(@tabLength, @showInvisibles)...) text = _.pluck(tokenObjects, 'value').join('') new ScreenLine(tokenObjects, text, [1, 0], [1, 0], { stack }) From ae0a60f4a9081cc50ae5dec40531e4ffee60958f Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 18 Oct 2012 11:52:51 -0700 Subject: [PATCH 11/13] Eliminate unused showInvisibles parameter. --- src/app/edit-session.coffee | 2 +- src/app/tokenized-buffer.coffee | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 12e1f6ead..34dea2477 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -44,7 +44,7 @@ class EditSession @id = @constructor.idCounter++ @softTabs ?= true @languageMode = new LanguageMode(this, @buffer.getExtension()) - @displayBuffer = new DisplayBuffer(@buffer, { @languageMode, @tabLength, @showInvisibles }) + @displayBuffer = new DisplayBuffer(@buffer, { @languageMode, @tabLength }) @tokenizedBuffer = @displayBuffer.tokenizedBuffer @anchors = [] @anchorRanges = [] diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee index 19faf3ab7..0e73574b8 100644 --- a/src/app/tokenized-buffer.coffee +++ b/src/app/tokenized-buffer.coffee @@ -11,12 +11,11 @@ class TokenizedBuffer languageMode: null tabLength: null - showInvisibles: null buffer: null aceAdaptor: null screenLines: null - constructor: (@buffer, { @languageMode, @tabLength, @showInvisibles }) -> + constructor: (@buffer, { @languageMode, @tabLength }) -> @tabLength ?= 2 @languageMode.tokenizedBuffer = this @id = @constructor.idCounter++ @@ -67,7 +66,7 @@ class TokenizedBuffer tokenObjects = [] for tokenProperties in tokens token = new Token(tokenProperties) - tokenObjects.push(token.breakOutTabCharacters(@tabLength, @showInvisibles)...) + tokenObjects.push(token.breakOutTabCharacters(@tabLength)...) text = _.pluck(tokenObjects, 'value').join('') new ScreenLine(tokenObjects, text, [1, 0], [1, 0], { stack }) From a72d0399f7c21b77db95bd1d3fcc4a159e650154 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 18 Oct 2012 14:10:36 -0700 Subject: [PATCH 12/13] Add Editor.setShowInvisibles and remove showInvisibles from EditSession --- spec/app/editor-spec.coffee | 15 ++++++++------- src/app/edit-session.coffee | 3 +-- src/app/editor.coffee | 17 +++++++++++------ src/app/project.coffee | 5 ----- src/app/root-view.coffee | 3 ++- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 55d5c6937..c544a5b01 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1472,15 +1472,16 @@ describe "Editor", -> expect(editor.renderedLines.find('.line:eq(14)').text()).toBe 'B' expect(editor.renderedLines.find('.line:eq(15)')).not.toExist() - describe "when editSession.showInvisibles is true", -> - beforeEach -> - project.setShowInvisibles(true) - rootView.open() - editor.attachToDom(5) - - it "displays spaces as •, tabs as ▸ and newlines as ¬", -> + describe "when editor.setShowInvisibles is called", -> + it "displays spaces as •, tabs as ▸ and newlines as ¬ when true", -> + editor.attachToDom() editor.setText " a line with tabs\tand spaces " + expect(editor.showInvisibles).toBeFalsy() + expect(editor.find('.line').text()).toBe " a line with tabs and spaces " + editor.setShowInvisibles(true) expect(editor.find('.line').text()).toBe "•a line with tabs▸ and spaces•¬" + editor.setShowInvisibles(false) + expect(editor.find('.line').text()).toBe " a line with tabs and spaces " describe "gutter rendering", -> beforeEach -> diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 34dea2477..10ba9e04c 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -38,9 +38,8 @@ class EditSession tabLength: null softTabs: true softWrap: false - showInvisibles: false - constructor: ({@project, @buffer, @tabLength, @autoIndent, @softTabs, @softWrap, @showInvisibles}) -> + constructor: ({@project, @buffer, @tabLength, @autoIndent, @softTabs, @softWrap }) -> @id = @constructor.idCounter++ @softTabs ?= true @languageMode = new LanguageMode(this, @buffer.getExtension()) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index c4fc11c91..00020f82c 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -46,12 +46,12 @@ class Editor extends View @deserialize: (state, rootView) -> editSessions = state.editSessions.map (state) -> EditSession.deserialize(state, rootView.project) - editor = new Editor(editSession: editSessions[state.activeEditSessionIndex], mini: state.mini) + editor = new Editor(editSession: editSessions[state.activeEditSessionIndex], mini: state.mini, showInvisibles: rootView.showInvisibles) editor.editSessions = editSessions editor.isFocused = state.isFocused editor - initialize: ({editSession, @mini} = {}) -> + initialize: ({editSession, @mini, @showInvisibles} = {}) -> requireStylesheet 'editor.css' @id = Editor.idCounter++ @@ -265,6 +265,11 @@ class Editor extends View getPageRows: -> Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight)) + setShowInvisibles: (showInvisibles) -> + return if showInvisibles == @showInvisibles + @showInvisibles = showInvisibles + @renderLines() + setText: (text) -> @getBuffer().setText(text) getText: -> @getBuffer().getText() getPath: -> @getBuffer().getPath() @@ -569,7 +574,7 @@ class Editor extends View @updateRenderedLines() newSplitEditor: -> - new Editor { editSession: @activeEditSession.copy() } + new Editor { editSession: @activeEditSession.copy(), @showInvisibles } splitLeft: -> @pane()?.splitLeft(@newSplitEditor()).wrappedView @@ -874,7 +879,7 @@ class Editor extends View line.push("
")
 
     if screenLine.text == ''
-      line.push(" ") unless @activeEditSession.showInvisibles
+      line.push(" ") unless @showInvisibles
     else
       firstNonWhitespacePosition = screenLine.text.search(/\S/)
       firstTrailingWhitespacePosition = screenLine.text.search(/\s*$/)
@@ -882,14 +887,14 @@ class Editor extends View
       for token in screenLine.tokens
         updateScopeStack(token.scopes)
         line.push(token.getValueAsHtml(
-          showInvisibles: @activeEditSession.showInvisibles
+          showInvisibles: @showInvisibles
           hasLeadingWhitespace: position < firstNonWhitespacePosition
           hasTrailingWhitespace: position + token.value.length > firstTrailingWhitespacePosition
         ))
 
         position += token.value.length
 
-    line.push("") if @activeEditSession.showInvisibles
+    line.push("") if @showInvisibles
     line.push('
') line.join('') diff --git a/src/app/project.coffee b/src/app/project.coffee index f11013a9e..dfe28a56d 100644 --- a/src/app/project.coffee +++ b/src/app/project.coffee @@ -14,7 +14,6 @@ class Project autoIndent: true softTabs: true softWrap: false - showInvisibles: false rootDirectory: null editSessions: null ignoredPathRegexes: null @@ -111,9 +110,6 @@ class Project getSoftWrap: -> @softWrap setSoftWrap: (@softWrap) -> - getShowInvisibles: -> @showInvisibles - setShowInvisibles: (@showInvisibles) -> - buildEditSessionForPath: (filePath, editSessionOptions={}) -> @buildEditSession(@bufferForPath(filePath), editSessionOptions) @@ -131,7 +127,6 @@ class Project autoIndent: @getAutoIndent() softTabs: @getSoftTabs() softWrap: @getSoftWrap() - showInvisibles: @getShowInvisibles() getEditSessions: -> new Array(@editSessions...) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 38cb4f1e9..0f5c00d56 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -31,6 +31,7 @@ class RootView extends View extensions: null extensionStates: null fontSize: 20 + showInvisibles: false initialize: (pathToOpen, { @extensionStates, suppressOpen } = {}) -> window.rootView = this @@ -117,7 +118,7 @@ class RootView extends View unless editSession = @openInExistingEditor(path, allowActiveEditorChange, changeFocus) editSession = @project.buildEditSessionForPath(path) - editor = new Editor({editSession}) + editor = new Editor({editSession, @showInvisibles}) pane = new Pane(editor) @panes.append(pane) if changeFocus From 93cbe2bb220d18e934ac6d0d0122f2b6b3294b25 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 18 Oct 2012 14:44:21 -0700 Subject: [PATCH 13/13] Create root-view:toggle-invisibles event --- spec/app/root-view-spec.coffee | 24 ++++++++++++++++++++++++ src/app/root-view.coffee | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 8e1c2ca81..31e612115 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -690,3 +690,27 @@ describe "RootView", -> expect(fs.read(buffer1.getPath())).toBe("edited1") expect(buffer2.isModified()).toBe(false) expect(fs.read(buffer2.getPath())).toBe("edited2") + + describe "window:toggle-invisibles event", -> + it "shows/hides invisibles in all open and future editors", -> + rootView.height(200) + rootView.attachToDom() + rightEditor = rootView.getActiveEditor() + rightEditor.setText(" \t ") + leftEditor = rightEditor.splitLeft() + expect(rightEditor.find(".line:first").text()).toBe " " + expect(leftEditor.find(".line:first").text()).toBe " " + + rootView.trigger "root-view:toggle-invisibles" + expect(rightEditor.find(".line:first").text()).toBe "•▸ •¬" + expect(leftEditor.find(".line:first").text()).toBe "•▸ •¬" + + lowerLeftEditor = leftEditor.splitDown() + expect(lowerLeftEditor.find(".line:first").text()).toBe "•▸ •¬" + + rootView.trigger "root-view:toggle-invisibles" + expect(rightEditor.find(".line:first").text()).toBe " " + expect(leftEditor.find(".line:first").text()).toBe " " + + lowerRightEditor = rightEditor.splitDown() + expect(lowerRightEditor.find(".line:first").text()).toBe " " diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 0f5c00d56..3f98d2d0f 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -78,6 +78,7 @@ class RootView extends View @on 'root-view:decrease-font-size', => @setFontSize(@getFontSize() - 1) @on 'root-view:focus-next-pane', => @focusNextPane() @on 'root-view:save-all', => @saveAll() + @on 'root-view:toggle-invisibles', => @setShowInvisibles(not @showInvisibles) afterAttach: (onDom) -> @focus() if onDom @@ -171,6 +172,11 @@ class RootView extends View setTitle: (title='untitled') -> document.title = title + setShowInvisibles: (showInvisibles) -> + return if @showInvisibles == showInvisibles + @showInvisibles = showInvisibles + editor.setShowInvisibles(@showInvisibles) for editor in @getEditors() + getEditors: -> @panes.find('.pane > .editor').map(-> $(this).view()).toArray()