From 55a7508287407671ec8350cecc7f06554e52a216 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 11:38:54 +0200 Subject: [PATCH 01/93] wip --- src/lines-presenter.coffee | 41 ++++++++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 30 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/lines-presenter.coffee diff --git a/src/lines-presenter.coffee b/src/lines-presenter.coffee new file mode 100644 index 000000000..c1e3676ba --- /dev/null +++ b/src/lines-presenter.coffee @@ -0,0 +1,41 @@ +module.exports = +class LinesPresenter + startRow: null + endRow: null + lineHeight: null + + constructor: (@presenter) -> + @lines = {} + + getState: -> + visibleLineIds = {} + row = @startRow + while row < @endRow + line = @presenter.model.tokenizedLineForScreenRow(row) + unless line? + throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") + + visibleLineIds[line.id] = true + if @lines.hasOwnProperty(line.id) + lineState = @lines[line.id] + lineState.screenRow = row + lineState.top = (row - @startRow) * @lineHeight + lineState.decorationClasses = @presenter.lineDecorationClassesForRow(row) + else + @lines[line.id] = + screenRow: row + text: line.text + tokens: line.tokens + isOnlyWhitespace: line.isOnlyWhitespace() + endOfLineInvisibles: line.endOfLineInvisibles + indentLevel: line.indentLevel + tabLength: line.tabLength + fold: line.fold + top: (row - @startRow) * @lineHeight + decorationClasses: @presenter.lineDecorationClassesForRow(row) + row++ + + for id, line of @lines + delete @lines[id] unless visibleLineIds.hasOwnProperty(id) + + @lines diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ac6d0c363..e9cba9dec 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -2,6 +2,7 @@ {Point, Range} = require 'text-buffer' _ = require 'underscore-plus' Decoration = require './decoration' +LinesPresenter = require './lines-presenter' module.exports = class TextEditorPresenter @@ -20,6 +21,8 @@ class TextEditorPresenter @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 + @tileCount ?= 3 + @linesPresentersByTileIndex = {} @disposables = new CompositeDisposable @emitter = new Emitter @@ -74,6 +77,7 @@ class TextEditorPresenter @updateHiddenInputState() if @shouldUpdateHiddenInputState @updateContentState() if @shouldUpdateContentState @updateDecorations() if @shouldUpdateDecorations + @updateTilesState() if @shouldUpdateLinesState @updateLinesState() if @shouldUpdateLinesState @updateCursorsState() if @shouldUpdateCursorsState @updateOverlaysState() if @shouldUpdateOverlaysState @@ -205,6 +209,7 @@ class TextEditorPresenter content: scrollingVertically: false cursorsVisible: false + tiles: {} lines: {} highlights: {} overlays: {} @@ -229,6 +234,7 @@ class TextEditorPresenter @updateHiddenInputState() @updateContentState() @updateDecorations() + @updateTilesState() @updateLinesState() @updateCursorsState() @updateOverlaysState() @@ -298,6 +304,30 @@ class TextEditorPresenter @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null + updateTilesState: -> + return unless @startRow? and @endRow? and @lineHeight? + + linesPerTile = @height / @lineHeight / @tileCount + + startIndex = Math.floor(@startRow / linesPerTile) + endIndex = Math.ceil(@endRow / linesPerTile) + visibleIndexesRange = [startIndex..endIndex] + + for index, tile of @state.content.tiles + unless index in visibleIndexesRange + delete @state.content.tiles[index] + delete @linesPresentersByTileIndex[index] + + for index in visibleIndexesRange + presenter = @linesPresentersByTileIndex[index] ?= new LinesPresenter(@) + presenter.startRow = Math.floor(index * linesPerTile) + presenter.endRow = Math.ceil(Math.min(@endRow, (index + 1) * linesPerTile)) + presenter.lineHeight = @lineHeight + + tile = @state.content.tiles[index] ?= {} + tile.top = (index * linesPerTile * @lineHeight) - @scrollTop + tile.lines = presenter.getState() + updateLinesState: -> return unless @startRow? and @endRow? and @lineHeight? From ee85abc2ebc21bf46f730d0fdd278099b974405b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 11:41:09 +0200 Subject: [PATCH 02/93] :fire: Delete line handling from TextEditorPresenter --- src/text-editor-presenter.coffee | 76 ++++++-------------------------- 1 file changed, 14 insertions(+), 62 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index e9cba9dec..835a89182 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -77,8 +77,7 @@ class TextEditorPresenter @updateHiddenInputState() if @shouldUpdateHiddenInputState @updateContentState() if @shouldUpdateContentState @updateDecorations() if @shouldUpdateDecorations - @updateTilesState() if @shouldUpdateLinesState - @updateLinesState() if @shouldUpdateLinesState + @updateTilesState() if @shouldUpdateTilesState @updateCursorsState() if @shouldUpdateCursorsState @updateOverlaysState() if @shouldUpdateOverlaysState @updateLineNumberGutterState() if @shouldUpdateLineNumberGutterState @@ -100,7 +99,7 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = false @shouldUpdateContentState = false @shouldUpdateDecorations = false - @shouldUpdateLinesState = false + @shouldUpdateTilesState = false @shouldUpdateCursorsState = false @shouldUpdateOverlaysState = false @shouldUpdateLineNumberGutterState = false @@ -118,7 +117,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateLineNumberGutterState = true @shouldUpdateLineNumbersState = true @shouldUpdateGutterOrderState = true @@ -134,7 +133,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateLineNumberGutterState = true @shouldUpdateLineNumbersState = true @shouldUpdateGutterOrderState = true @@ -210,7 +209,6 @@ class TextEditorPresenter scrollingVertically: false cursorsVisible: false tiles: {} - lines: {} highlights: {} overlays: {} gutters: @@ -235,7 +233,6 @@ class TextEditorPresenter @updateContentState() @updateDecorations() @updateTilesState() - @updateLinesState() @updateCursorsState() @updateOverlaysState() @updateLineNumberGutterState() @@ -328,51 +325,6 @@ class TextEditorPresenter tile.top = (index * linesPerTile * @lineHeight) - @scrollTop tile.lines = presenter.getState() - updateLinesState: -> - return unless @startRow? and @endRow? and @lineHeight? - - visibleLineIds = {} - row = @startRow - while row < @endRow - line = @model.tokenizedLineForScreenRow(row) - unless line? - throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") - - visibleLineIds[line.id] = true - if @state.content.lines.hasOwnProperty(line.id) - @updateLineState(row, line) - else - @buildLineState(row, line) - row++ - - if @mouseWheelScreenRow? - if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow) - visibleLineIds[preservedLine.id] = true - - for id, line of @state.content.lines - unless visibleLineIds.hasOwnProperty(id) - delete @state.content.lines[id] - return - - updateLineState: (row, line) -> - lineState = @state.content.lines[line.id] - lineState.screenRow = row - lineState.top = row * @lineHeight - lineState.decorationClasses = @lineDecorationClassesForRow(row) - - buildLineState: (row, line) -> - @state.content.lines[line.id] = - screenRow: row - text: line.text - tokens: line.tokens - isOnlyWhitespace: line.isOnlyWhitespace() - endOfLineInvisibles: line.endOfLineInvisibles - indentLevel: line.indentLevel - tabLength: line.tabLength - fold: line.fold - top: row * @lineHeight - decorationClasses: @lineDecorationClassesForRow(row) - updateCursorsState: -> @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation @@ -748,7 +700,7 @@ class TextEditorPresenter @shouldUpdateVerticalScrollState = true @shouldUpdateHiddenInputState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateCursorsState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -771,7 +723,7 @@ class TextEditorPresenter @state.content.scrollingVertically = false if @mouseWheelScreenRow? @mouseWheelScreenRow = null - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -834,7 +786,7 @@ class TextEditorPresenter @shouldUpdateVerticalScrollState = true @shouldUpdateScrollbarsState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateCursorsState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -862,7 +814,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateCursorsState = true unless oldContentFrameWidth? @emitDidUpdateState() @@ -928,7 +880,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateHiddenInputState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateCursorsState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -980,7 +932,7 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true @shouldUpdateCursorsState = true @shouldUpdateOverlaysState = true @@ -1072,7 +1024,7 @@ class TextEditorPresenter intersectsVisibleRowRange = true if intersectsVisibleRowRange - @shouldUpdateLinesState = true if decoration.isType('line') + @shouldUpdateTilesState = true if decoration.isType('line') if decoration.isType('line-number') @shouldUpdateLineNumbersState = true else if decoration.isType('gutter') @@ -1097,7 +1049,7 @@ class TextEditorPresenter decoration.getMarker().getScreenRange()) @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) if decoration.isType('line') or Decoration.isType(oldProperties, 'line') - @shouldUpdateLinesState = true + @shouldUpdateTilesState = true if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number') @shouldUpdateLineNumbersState = true if (decoration.isType('gutter') and not decoration.isType('line-number')) or @@ -1113,7 +1065,7 @@ class TextEditorPresenter didDestroyDecoration: (decoration) -> if decoration.isType('line') or decoration.isType('gutter') @removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) - @shouldUpdateLinesState = true if decoration.isType('line') + @shouldUpdateTilesState = true if decoration.isType('line') if decoration.isType('line-number') @shouldUpdateLineNumbersState = true else if decoration.isType('gutter') @@ -1138,7 +1090,7 @@ class TextEditorPresenter if decoration.isType('line') or decoration.isType('gutter') @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) - @shouldUpdateLinesState = true if decoration.isType('line') + @shouldUpdateTilesState = true if decoration.isType('line') if decoration.isType('line-number') @shouldUpdateLineNumbersState = true else if decoration.isType('gutter') From 3d3d5d00b4a476eea27800b6be94d6969b1775c7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 14:24:58 +0200 Subject: [PATCH 03/93] wip --- spec/text-editor-presenter-spec.coffee | 159 +++++++++++-------------- src/text-editor-presenter.coffee | 22 ++-- 2 files changed, 82 insertions(+), 99 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index de2dd780d..b2c40fc36 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -657,123 +657,96 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.setPlaceholderText("new-placeholder-text") expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text" - describe ".lines", -> - lineStateForScreenRow = (presenter, screenRow) -> - presenter.getState().content.lines[presenter.model.tokenizedLineForScreenRow(screenRow).id] + fdescribe ".tiles", -> + it "contains states for tiles that are visible on screen, plus and minus the overdraw margin", -> + presenter = buildPresenter(explicitHeight: 3, scrollTop: 1, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) - it "contains states for lines that are visible on screen, plus and minus the overdraw margin", -> - presenter = buildPresenter(explicitHeight: 15, scrollTop: 50, lineHeight: 10, lineOverdrawMargin: 1) - - expect(lineStateForScreenRow(presenter, 3)).toBeUndefined() - - line4 = editor.tokenizedLineForScreenRow(4) - expectValues lineStateForScreenRow(presenter, 4), { - screenRow: 4 - text: line4.text - tokens: line4.tokens - top: 10 * 4 + expectValues presenter.getState().content.tiles[0], { + top: -1 } - - line5 = editor.tokenizedLineForScreenRow(5) - expectValues lineStateForScreenRow(presenter, 5), { - screenRow: 5 - text: line5.text - tokens: line5.tokens - top: 10 * 5 + expectValues presenter.getState().content.tiles[1], { + top: 0 } - - line6 = editor.tokenizedLineForScreenRow(6) - expectValues lineStateForScreenRow(presenter, 6), { - screenRow: 6 - text: line6.text - tokens: line6.tokens - top: 10 * 6 + expectValues presenter.getState().content.tiles[2], { + top: 1 } - - line7 = editor.tokenizedLineForScreenRow(7) - expectValues lineStateForScreenRow(presenter, 7), { - screenRow: 7 - text: line7.text - tokens: line7.tokens - top: 10 * 7 + expectValues presenter.getState().content.tiles[3], { + top: 2 } - - line8 = editor.tokenizedLineForScreenRow(8) - expectValues lineStateForScreenRow(presenter, 8), { - screenRow: 8 - text: line8.text - tokens: line8.tokens - top: 10 * 8 + expectValues presenter.getState().content.tiles[4], { + top: 3 } - - expect(lineStateForScreenRow(presenter, 9)).toBeUndefined() + expectValues presenter.getState().content.tiles[5], { + top: 4 + } + expect(presenter.getState().content.tiles[6]).toBeUndefined() it "does not overdraw above the first row", -> - presenter = buildPresenter(explicitHeight: 15, scrollTop: 10, lineHeight: 10, lineOverdrawMargin: 2) - expect(lineStateForScreenRow(presenter, 0)).toBeDefined() - expect(lineStateForScreenRow(presenter, 1)).toBeDefined() - expect(lineStateForScreenRow(presenter, 2)).toBeDefined() - expect(lineStateForScreenRow(presenter, 3)).toBeDefined() - expect(lineStateForScreenRow(presenter, 4)).toBeDefined() - expect(lineStateForScreenRow(presenter, 5)).toBeDefined() - expect(lineStateForScreenRow(presenter, 6)).toBeUndefined() + presenter = buildPresenter(explicitHeight: 3, scrollTop: 0, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) + expect(presenter.getState().content.tiles[-1]).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[1]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeDefined() + expect(presenter.getState().content.tiles[3]).toBeDefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[5]).toBeUndefined() it "does not overdraw below the last row", -> - presenter = buildPresenter(explicitHeight: 25, scrollTop: 105, lineHeight: 10, lineOverdrawMargin: 2) - expect(lineStateForScreenRow(presenter, 7)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 8)).toBeDefined() - expect(lineStateForScreenRow(presenter, 9)).toBeDefined() - expect(lineStateForScreenRow(presenter, 10)).toBeDefined() - expect(lineStateForScreenRow(presenter, 11)).toBeDefined() - expect(lineStateForScreenRow(presenter, 12)).toBeDefined() + presenter = buildPresenter(explicitHeight: 3, scrollTop: 10, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) + expect(presenter.getState().content.tiles[8]).toBeUndefined() + expect(presenter.getState().content.tiles[9]).toBeDefined() + expect(presenter.getState().content.tiles[10]).toBeDefined() + expect(presenter.getState().content.tiles[11]).toBeDefined() + expect(presenter.getState().content.tiles[12]).toBeDefined() + expect(presenter.getState().content.tiles[13]).toBeUndefined() - it "includes state for all lines if no external ::explicitHeight is assigned", -> - presenter = buildPresenter(explicitHeight: null) - expect(lineStateForScreenRow(presenter, 0)).toBeDefined() - expect(lineStateForScreenRow(presenter, 12)).toBeDefined() + xit "includes state for all tiles if no external ::explicitHeight is assigned", -> + presenter = buildPresenter(explicitHeight: null, tileCount: 12, tileOverdrawMargin: 1) + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[12]).toBeDefined() it "is empty until all of the required measurements are assigned", -> - presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null) - expect(presenter.getState().content.lines).toEqual({}) + presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, tileCount: 3, tileOverdrawMargin: 1) + expect(presenter.getState().content.tiles).toEqual({}) presenter.setExplicitHeight(25) - expect(presenter.getState().content.lines).toEqual({}) + expect(presenter.getState().content.tiles).toEqual({}) presenter.setLineHeight(10) - expect(presenter.getState().content.lines).toEqual({}) + expect(presenter.getState().content.tiles).toEqual({}) presenter.setScrollTop(0) - expect(presenter.getState().content.lines).not.toEqual({}) + expect(presenter.getState().content.tiles).not.toEqual({}) it "updates when ::scrollTop changes", -> - presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1) + presenter = buildPresenter(explicitHeight: 3, scrollTop: 0, lineHeight: 1, tileOverdrawMargin: 1, tileCount: 3) - expect(lineStateForScreenRow(presenter, 0)).toBeDefined() - expect(lineStateForScreenRow(presenter, 4)).toBeDefined() - expect(lineStateForScreenRow(presenter, 5)).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[5]).toBeUndefined() - expectStateUpdate presenter, -> presenter.setScrollTop(25) + expectStateUpdate presenter, -> presenter.setScrollTop(2) - expect(lineStateForScreenRow(presenter, 0)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 1)).toBeDefined() - expect(lineStateForScreenRow(presenter, 6)).toBeDefined() - expect(lineStateForScreenRow(presenter, 7)).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeUndefined() + expect(presenter.getState().content.tiles[1]).toBeDefined() + expect(presenter.getState().content.tiles[6]).toBeDefined() + expect(presenter.getState().content.tiles[7]).toBeUndefined() - it "updates when ::explicitHeight changes", -> - presenter = buildPresenter(explicitHeight: 15, scrollTop: 15, lineHeight: 10, lineOverdrawMargin: 1) + xit "updates when ::explicitHeight changes", -> + presenter = buildPresenter(explicitHeight: 3, scrollTop: 0, lineHeight: 1, tileOverdrawMargin: 1, tileCount: 3) line5 = editor.tokenizedLineForScreenRow(5) - expect(lineStateForScreenRow(presenter, 4)).toBeDefined() - expect(lineStateForScreenRow(presenter, 5)).toBeUndefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[5]).toBeUndefined() expectStateUpdate presenter, -> presenter.setExplicitHeight(35) - expect(lineStateForScreenRow(presenter, 5)).toBeDefined() - expect(lineStateForScreenRow(presenter, 6)).toBeDefined() - expect(lineStateForScreenRow(presenter, 7)).toBeUndefined() + expect(presenter.getState().content.tiles[5]).toBeDefined() + expect(presenter.getState().content.tiles[6]).toBeDefined() + expect(presenter.getState().content.tiles[7]).toBeUndefined() - it "updates when ::lineHeight changes", -> + xit "updates when ::lineHeight changes", -> presenter = buildPresenter(explicitHeight: 15, scrollTop: 10, lineHeight: 10, lineOverdrawMargin: 0) expect(lineStateForScreenRow(presenter, 0)).toBeUndefined() @@ -789,29 +762,35 @@ describe "TextEditorPresenter", -> expect(lineStateForScreenRow(presenter, 5)).toBeDefined() expect(lineStateForScreenRow(presenter, 6)).toBeUndefined() - it "updates when the editor's content changes", -> - presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10) + fffit "updates when the editor's content changes", -> + lineStateForScreenRow = (presenter, tile, row) -> + lineId = presenter.model.tokenizedLineForScreenRow(row).id + presenter.getState().content.tiles[tile].lines[lineId] + + presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10, tileCount: 3, tileOverdrawMargin: 1) expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n") line1 = editor.tokenizedLineForScreenRow(1) - expectValues lineStateForScreenRow(presenter, 1), { + expectValues lineStateForScreenRow(presenter, 1, 1), { text: line1.text tokens: line1.tokens } line2 = editor.tokenizedLineForScreenRow(2) - expectValues lineStateForScreenRow(presenter, 2), { + expectValues lineStateForScreenRow(presenter, 2, 2), { text: line2.text tokens: line2.tokens } line3 = editor.tokenizedLineForScreenRow(3) - expectValues lineStateForScreenRow(presenter, 3), { + expectValues lineStateForScreenRow(presenter, 3, 3), { text: line3.text tokens: line3.tokens } + xdescribe ".lines", -> + it "does not remove out-of-view lines corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", -> presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 835a89182..fb762e758 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -16,12 +16,11 @@ class TextEditorPresenter constructor: (params) -> {@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params {horizontalScrollbarHeight, verticalScrollbarWidth} = params - {@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params + {@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor, @tileCount, @tileOverdrawMargin} = params {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileCount ?= 3 @linesPresentersByTileIndex = {} @disposables = new CompositeDisposable @@ -306,16 +305,21 @@ class TextEditorPresenter linesPerTile = @height / @lineHeight / @tileCount - startIndex = Math.floor(@startRow / linesPerTile) - endIndex = Math.ceil(@endRow / linesPerTile) - visibleIndexesRange = [startIndex..endIndex] + startIndex = Math.max( + 0, Math.floor(@startRow / linesPerTile) - @tileOverdrawMargin + ) + endIndex = Math.min( + Math.ceil(@model.getScreenLineCount() / linesPerTile), + Math.ceil(@endRow / linesPerTile) + @tileOverdrawMargin + ) + visibleTilesIndexes = [startIndex...endIndex] for index, tile of @state.content.tiles - unless index in visibleIndexesRange + unless index in visibleTilesIndexes delete @state.content.tiles[index] delete @linesPresentersByTileIndex[index] - for index in visibleIndexesRange + for index in visibleTilesIndexes presenter = @linesPresentersByTileIndex[index] ?= new LinesPresenter(@) presenter.startRow = Math.floor(index * linesPerTile) presenter.endRow = Math.ceil(Math.min(@endRow, (index + 1) * linesPerTile)) @@ -514,7 +518,7 @@ class TextEditorPresenter updateStartRow: -> return unless @scrollTop? and @lineHeight? - startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin + startRow = Math.floor(@scrollTop / @lineHeight) @startRow = Math.max(0, startRow) updateEndRow: -> @@ -522,7 +526,7 @@ class TextEditorPresenter startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight)) visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 - endRow = startRow + visibleLinesCount + @lineOverdrawMargin + endRow = startRow + visibleLinesCount @endRow = Math.min(@model.getScreenLineCount(), endRow) updateScrollWidth: -> From 3e78fe3019b8c539b0b30fb27c311808e18c1864 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 16:21:28 +0200 Subject: [PATCH 04/93] wip --- src/lines-component.coffee | 196 +++----------------- src/text-editor-presenter.coffee | 5 +- src/tile-component.coffee | 301 +++++++++++++++++++++++++++++++ 3 files changed, 328 insertions(+), 174 deletions(-) create mode 100644 src/tile-component.coffee diff --git a/src/lines-component.coffee b/src/lines-component.coffee index fbec40b79..951d5499a 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -4,6 +4,7 @@ _ = require 'underscore-plus' CursorsComponent = require './cursors-component' HighlightsComponent = require './highlights-component' +TileComponent = require './tile-component' DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} @@ -19,11 +20,7 @@ class LinesComponent placeholderTextDiv: null constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> - @measuredLines = new Set - @lineNodesByLineId = {} - @screenRowsByLineId = {} - @lineIdsByScreenRow = {} - @renderedDecorationsByLineId = {} + @tileComponentsByTileId = {} @domNode = document.createElement('div') @domNode.classList.add('lines') @@ -44,15 +41,14 @@ class LinesComponent updateSync: (state) -> @newState = state.content - @oldState ?= {lines: {}} + @oldState ?= {tiles: {}} if @newState.scrollHeight isnt @oldState.scrollHeight @domNode.style.height = @newState.scrollHeight + 'px' @oldState.scrollHeight = @newState.scrollHeight - if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft - @domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, #{-@newState.scrollTop}px, 0px)" - @oldState.scrollTop = @newState.scrollTop + if @newState.scrollLeft isnt @oldState.scrollLeft + @domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, 0, 0px)" @oldState.scrollLeft = @newState.scrollLeft if @newState.backgroundColor isnt @oldState.backgroundColor @@ -67,8 +63,8 @@ class LinesComponent @placeholderTextDiv.textContent = @newState.placeholderText @domNode.appendChild(@placeholderTextDiv) - @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible - @updateLineNodes() + @removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible + @updateTileNodes() if @newState.scrollWidth isnt @oldState.scrollWidth @domNode.style.width = @newState.scrollWidth + 'px' @@ -80,177 +76,31 @@ class LinesComponent @oldState.indentGuidesVisible = @newState.indentGuidesVisible @oldState.scrollWidth = @newState.scrollWidth - removeLineNodes: -> - @removeLineNode(id) for id of @oldState.lines + removeTileNodes: -> + @removeTileNode(id) for id of @oldState.tiles return - removeLineNode: (id) -> - @lineNodesByLineId[id].remove() - delete @lineNodesByLineId[id] - delete @lineIdsByScreenRow[@screenRowsByLineId[id]] - delete @screenRowsByLineId[id] - delete @oldState.lines[id] + removeTileNode: (id) -> + @tileComponentsByTileId[id].getDomNode().remove() + delete @tileComponentsByTileId[id] + delete @oldState.tiles[id] - updateLineNodes: -> - for id of @oldState.lines - unless @newState.lines.hasOwnProperty(id) - @removeLineNode(id) + updateTileNodes: -> + for id of @oldState.tiles + unless @newState.tiles.hasOwnProperty(id) + @removeTileNode(id) - newLineIds = null - newLinesHTML = null + for id, tileState of @newState.tiles + tileComponent = @tileComponentsByTileId[id] ?= new TileComponent({id, @presenter}) - for id, lineState of @newState.lines - if @oldState.lines.hasOwnProperty(id) - @updateLineNode(id) - else - newLineIds ?= [] - newLinesHTML ?= "" - newLineIds.push(id) - newLinesHTML += @buildLineHTML(id) - @screenRowsByLineId[id] = lineState.screenRow - @lineIdsByScreenRow[lineState.screenRow] = id - @oldState.lines[id] = cloneObject(lineState) + unless @oldState.tiles.hasOwnProperty(id) + @domNode.appendChild(tileComponent.getDomNode()) + @oldState.tiles[id] = cloneObject(tileState) - return unless newLineIds? - - WrapperDiv.innerHTML = newLinesHTML - newLineNodes = _.toArray(WrapperDiv.children) - for id, i in newLineIds - lineNode = newLineNodes[i] - @lineNodesByLineId[id] = lineNode - @domNode.appendChild(lineNode) + tileComponent.updateSync(@newState) return - buildLineHTML: (id) -> - {scrollWidth} = @newState - {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id] - - classes = '' - if decorationClasses? - for decorationClass in decorationClasses - classes += decorationClass + ' ' - classes += 'line' - - lineHTML = "
" - - if text is "" - lineHTML += @buildEmptyLineInnerHTML(id) - else - lineHTML += @buildLineInnerHTML(id) - - lineHTML += '' if fold - lineHTML += "
" - lineHTML - - buildEmptyLineInnerHTML: (id) -> - {indentGuidesVisible} = @newState - {indentLevel, tabLength, endOfLineInvisibles} = @newState.lines[id] - - if indentGuidesVisible and indentLevel > 0 - invisibleIndex = 0 - lineHTML = '' - for i in [0...indentLevel] - lineHTML += "" - for j in [0...tabLength] - if invisible = endOfLineInvisibles?[invisibleIndex++] - lineHTML += "#{invisible}" - else - lineHTML += ' ' - lineHTML += "" - - while invisibleIndex < endOfLineInvisibles?.length - lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}" - - lineHTML - else - @buildEndOfLineHTML(id) or ' ' - - buildLineInnerHTML: (id) -> - {indentGuidesVisible} = @newState - {tokens, text, isOnlyWhitespace} = @newState.lines[id] - innerHTML = "" - - scopeStack = [] - for token in tokens - innerHTML += @updateScopeStack(scopeStack, token.scopes) - hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace)) - innerHTML += token.getValueAsHtml({hasIndentGuide}) - - innerHTML += @popScope(scopeStack) while scopeStack.length > 0 - innerHTML += @buildEndOfLineHTML(id) - innerHTML - - buildEndOfLineHTML: (id) -> - {endOfLineInvisibles} = @newState.lines[id] - - html = '' - if endOfLineInvisibles? - for invisible in endOfLineInvisibles - html += "#{invisible}" - html - - updateScopeStack: (scopeStack, desiredScopeDescriptor) -> - html = "" - - # Find a common prefix - for scope, i in desiredScopeDescriptor - break unless scopeStack[i] is desiredScopeDescriptor[i] - - # Pop scopeDescriptor until we're at the common prefx - until scopeStack.length is i - html += @popScope(scopeStack) - - # Push onto common prefix until scopeStack equals desiredScopeDescriptor - for j in [i...desiredScopeDescriptor.length] - html += @pushScope(scopeStack, desiredScopeDescriptor[j]) - - html - - popScope: (scopeStack) -> - scopeStack.pop() - "" - - pushScope: (scopeStack, scope) -> - scopeStack.push(scope) - "" - - updateLineNode: (id) -> - oldLineState = @oldState.lines[id] - newLineState = @newState.lines[id] - - lineNode = @lineNodesByLineId[id] - - if @newState.scrollWidth isnt @oldState.scrollWidth - lineNode.style.width = @newState.scrollWidth + 'px' - - newDecorationClasses = newLineState.decorationClasses - oldDecorationClasses = oldLineState.decorationClasses - - if oldDecorationClasses? - for decorationClass in oldDecorationClasses - unless newDecorationClasses? and decorationClass in newDecorationClasses - lineNode.classList.remove(decorationClass) - - if newDecorationClasses? - for decorationClass in newDecorationClasses - unless oldDecorationClasses? and decorationClass in oldDecorationClasses - lineNode.classList.add(decorationClass) - - oldLineState.decorationClasses = newLineState.decorationClasses - - if newLineState.top isnt oldLineState.top - lineNode.style.top = newLineState.top + 'px' - oldLineState.top = newLineState.cop - - if newLineState.screenRow isnt oldLineState.screenRow - lineNode.dataset.screenRow = newLineState.screenRow - oldLineState.screenRow = newLineState.screenRow - @lineIdsByScreenRow[newLineState.screenRow] = id - - lineNodeForScreenRow: (screenRow) -> - @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] - measureLineHeightAndDefaultCharWidth: -> @domNode.appendChild(DummyLineNode) lineHeightInPixels = DummyLineNode.getBoundingClientRect().height diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index fb762e758..ac7c33609 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -21,6 +21,8 @@ class TextEditorPresenter @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 + @tileOverdrawMargin ?= 0 + @tileCount ?= 4 @linesPresentersByTileIndex = {} @disposables = new CompositeDisposable @@ -303,7 +305,7 @@ class TextEditorPresenter updateTilesState: -> return unless @startRow? and @endRow? and @lineHeight? - linesPerTile = @height / @lineHeight / @tileCount + linesPerTile = Math.floor(@height / @lineHeight / @tileCount) startIndex = Math.max( 0, Math.floor(@startRow / linesPerTile) - @tileOverdrawMargin @@ -328,6 +330,7 @@ class TextEditorPresenter tile = @state.content.tiles[index] ?= {} tile.top = (index * linesPerTile * @lineHeight) - @scrollTop tile.lines = presenter.getState() + tile.height = linesPerTile * @lineHeight updateCursorsState: -> @state.content.cursors = {} diff --git a/src/tile-component.coffee b/src/tile-component.coffee new file mode 100644 index 000000000..017856368 --- /dev/null +++ b/src/tile-component.coffee @@ -0,0 +1,301 @@ +_ = require 'underscore-plus' +{toArray} = require 'underscore-plus' +{$$} = require 'space-pen' + +CursorsComponent = require './cursors-component' +HighlightsComponent = require './highlights-component' + +DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] +AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} +WrapperDiv = document.createElement('div') + +cloneObject = (object) -> + clone = {} + clone[key] = value for key, value of object + clone + +module.exports = +class TileComponent + placeholderTextDiv: null + + constructor: ({@presenter, @id}) -> + @measuredLines = new Set + @lineNodesByLineId = {} + @screenRowsByLineId = {} + @lineIdsByScreenRow = {} + @domNode = document.createElement("div") + @domNode.style.position = "absolute" + @domNode.classList.add("tile") + + getDomNode: -> + @domNode + + updateSync: (state) -> + @newState = state + unless @oldState + @oldState = {tiles: {}} + @oldState.tiles[@id] = { lines: {}} + + if @newState.tiles[@id].height isnt @oldState.tiles[@id]?.height + @domNode.style.height = @newState.tiles[@id].height + 'px' + @oldState.tiles[@id]?.height = @newState.tiles[@id].height + + if @newState.tiles[@id].top isnt @oldState.tiles[@id]?.top + @domNode.style['-webkit-transform'] = "translate3d(0, #{@newState.tiles[@id].top}px, 0px)" + @oldState.tiles[@id]?.top = @newState.tiles[@id].top + + if @newState.backgroundColor isnt @oldState.backgroundColor + @domNode.style.backgroundColor = @newState.backgroundColor + @oldState.backgroundColor = @newState.backgroundColor + + @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible + @updateLineNodes() + + if @newState.scrollWidth isnt @oldState.scrollWidth + @domNode.style.width = @newState.scrollWidth + 'px' + @oldState.scrollWidth = @newState.scrollWidth + + @oldState.indentGuidesVisible = @newState.indentGuidesVisible + @oldState.scrollWidth = @newState.scrollWidth + + removeLineNodes: -> + @removeLineNode(id) for id of @oldState.tiles[@id]?.lines + return + + removeLineNode: (id) -> + @lineNodesByLineId[id].remove() + delete @lineNodesByLineId[id] + delete @lineIdsByScreenRow[@screenRowsByLineId[id]] + delete @screenRowsByLineId[id] + delete @oldState.tiles[@id]?.lines[id] + + updateLineNodes: -> + for id of @oldState.tiles[@id]?.lines + unless @newState.tiles[@id].lines.hasOwnProperty(id) + @removeLineNode(id) + + newLineIds = null + newLinesHTML = null + + for id, lineState of @newState.tiles[@id].lines + if @oldState.tiles[@id]?.lines.hasOwnProperty(id) + @updateLineNode(id) + else + newLineIds ?= [] + newLinesHTML ?= "" + newLineIds.push(id) + newLinesHTML += @buildLineHTML(id) + @screenRowsByLineId[id] = lineState.screenRow + @lineIdsByScreenRow[lineState.screenRow] = id + @oldState.tiles[@id]?.lines[id] = cloneObject(lineState) + + return unless newLineIds? + + WrapperDiv.innerHTML = newLinesHTML + newLineNodes = _.toArray(WrapperDiv.children) + for id, i in newLineIds + lineNode = newLineNodes[i] + @lineNodesByLineId[id] = lineNode + @domNode.appendChild(lineNode) + + return + + buildLineHTML: (id) -> + {scrollWidth} = @newState + {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.tiles[@id].lines[id] + + classes = '' + if decorationClasses? + for decorationClass in decorationClasses + classes += decorationClass + ' ' + classes += 'line' + + lineHTML = "
" + + if text is "" + lineHTML += @buildEmptyLineInnerHTML(id) + else + lineHTML += @buildLineInnerHTML(id) + + lineHTML += '' if fold + lineHTML += "
" + lineHTML + + buildEmptyLineInnerHTML: (id) -> + {indentGuidesVisible} = @newState + {indentLevel, tabLength, endOfLineInvisibles} = @newState.tiles[@id].lines[id] + + if indentGuidesVisible and indentLevel > 0 + invisibleIndex = 0 + lineHTML = '' + for i in [0...indentLevel] + lineHTML += "" + for j in [0...tabLength] + if invisible = endOfLineInvisibles?[invisibleIndex++] + lineHTML += "#{invisible}" + else + lineHTML += ' ' + lineHTML += "" + + while invisibleIndex < endOfLineInvisibles?.length + lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}" + + lineHTML + else + @buildEndOfLineHTML(id) or ' ' + + buildLineInnerHTML: (id) -> + {indentGuidesVisible} = @newState + {tokens, text, isOnlyWhitespace} = @newState.tiles[@id].lines[id] + innerHTML = "" + + scopeStack = [] + for token in tokens + innerHTML += @updateScopeStack(scopeStack, token.scopes) + hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace)) + innerHTML += token.getValueAsHtml({hasIndentGuide}) + + innerHTML += @popScope(scopeStack) while scopeStack.length > 0 + innerHTML += @buildEndOfLineHTML(id) + innerHTML + + buildEndOfLineHTML: (id) -> + {endOfLineInvisibles} = @newState.tiles[@id].lines[id] + + html = '' + if endOfLineInvisibles? + for invisible in endOfLineInvisibles + html += "#{invisible}" + html + + updateScopeStack: (scopeStack, desiredScopeDescriptor) -> + html = "" + + # Find a common prefix + for scope, i in desiredScopeDescriptor + break unless scopeStack[i] is desiredScopeDescriptor[i] + + # Pop scopeDescriptor until we're at the common prefx + until scopeStack.length is i + html += @popScope(scopeStack) + + # Push onto common prefix until scopeStack equals desiredScopeDescriptor + for j in [i...desiredScopeDescriptor.length] + html += @pushScope(scopeStack, desiredScopeDescriptor[j]) + + html + + popScope: (scopeStack) -> + scopeStack.pop() + "
" + + pushScope: (scopeStack, scope) -> + scopeStack.push(scope) + "" + + updateLineNode: (id) -> + oldLineState = @oldState.tiles[@id]?.lines[id] + newLineState = @newState.tiles[@id].lines[id] + + lineNode = @lineNodesByLineId[id] + + if @newState.scrollWidth isnt @oldState.scrollWidth + lineNode.style.width = @newState.scrollWidth + 'px' + + newDecorationClasses = newLineState.decorationClasses + oldDecorationClasses = oldLineState.decorationClasses + + if oldDecorationClasses? + for decorationClass in oldDecorationClasses + unless newDecorationClasses? and decorationClass in newDecorationClasses + lineNode.classList.remove(decorationClass) + + if newDecorationClasses? + for decorationClass in newDecorationClasses + unless oldDecorationClasses? and decorationClass in oldDecorationClasses + lineNode.classList.add(decorationClass) + + oldLineState.decorationClasses = newLineState.decorationClasses + + if newLineState.top isnt oldLineState.top + lineNode.style.top = newLineState.top + 'px' + oldLineState.top = newLineState.cop + + if newLineState.screenRow isnt oldLineState.screenRow + lineNode.dataset.screenRow = newLineState.screenRow + oldLineState.screenRow = newLineState.screenRow + @lineIdsByScreenRow[newLineState.screenRow] = id + + lineNodeForScreenRow: (screenRow) -> + @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] + + measureLineHeightAndDefaultCharWidth: -> + @domNode.appendChild(DummyLineNode) + lineHeightInPixels = DummyLineNode.getBoundingClientRect().height + charWidth = DummyLineNode.firstChild.getBoundingClientRect().width + @domNode.removeChild(DummyLineNode) + + @presenter.setLineHeight(lineHeightInPixels) + @presenter.setBaseCharacterWidth(charWidth) + + remeasureCharacterWidths: -> + return unless @presenter.baseCharacterWidth + + @clearScopedCharWidths() + @measureCharactersInNewLines() + + measureCharactersInNewLines: -> + @presenter.batchCharacterMeasurement => + for id, lineState of @oldState.tiles[@id]?.lines + unless @measuredLines.has(id) + lineNode = @lineNodesByLineId[id] + @measureCharactersInLine(id, lineState, lineNode) + return + + measureCharactersInLine: (lineId, tokenizedLine, lineNode) -> + rangeForMeasurement = null + iterator = null + charIndex = 0 + + for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens + charWidths = @presenter.getScopedCharacterWidths(scopes) + + valueIndex = 0 + while valueIndex < value.length + if hasPairedCharacter + char = value.substr(valueIndex, 2) + charLength = 2 + valueIndex += 2 + else + char = value[valueIndex] + charLength = 1 + valueIndex++ + + continue if char is '\0' + + unless charWidths[char]? + unless textNode? + rangeForMeasurement ?= document.createRange() + iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter) + textNode = iterator.nextNode() + textNodeIndex = 0 + nextTextNodeIndex = textNode.textContent.length + + while nextTextNodeIndex <= charIndex + textNode = iterator.nextNode() + textNodeIndex = nextTextNodeIndex + nextTextNodeIndex = textNodeIndex + textNode.textContent.length + + i = charIndex - textNodeIndex + rangeForMeasurement.setStart(textNode, i) + rangeForMeasurement.setEnd(textNode, i + charLength) + charWidth = rangeForMeasurement.getBoundingClientRect().width + @presenter.setScopedCharacterWidth(scopes, char, charWidth) + + charIndex += charLength + + @measuredLines.add(lineId) + + clearScopedCharWidths: -> + @measuredLines.clear() + @presenter.clearScopedCharacterWidths() From 414d5d1c8737fc3d0eb8c5f91b383a1a0e13d8c0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 18:10:21 +0200 Subject: [PATCH 05/93] wip --- src/text-editor-presenter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ac7c33609..c8d74b428 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -324,7 +324,7 @@ class TextEditorPresenter for index in visibleTilesIndexes presenter = @linesPresentersByTileIndex[index] ?= new LinesPresenter(@) presenter.startRow = Math.floor(index * linesPerTile) - presenter.endRow = Math.ceil(Math.min(@endRow, (index + 1) * linesPerTile)) + presenter.endRow = Math.ceil(Math.min(@model.getScreenLineCount(), (index + 1) * linesPerTile)) presenter.lineHeight = @lineHeight tile = @state.content.tiles[index] ?= {} From de53e1dccc911089f5daea6fba37b6acee269b57 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 18:36:10 +0200 Subject: [PATCH 06/93] Fix char measurement --- src/lines-component.coffee | 55 +++++--------------------------------- src/tile-component.coffee | 34 +++++------------------ 2 files changed, 13 insertions(+), 76 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 951d5499a..547b54e56 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -118,56 +118,13 @@ class LinesComponent measureCharactersInNewLines: -> @presenter.batchCharacterMeasurement => - for id, lineState of @oldState.lines - unless @measuredLines.has(id) - lineNode = @lineNodesByLineId[id] - @measureCharactersInLine(id, lineState, lineNode) + for id, component of @tileComponentsByTileId + component.measureCharactersInNewLines() + return - measureCharactersInLine: (lineId, tokenizedLine, lineNode) -> - rangeForMeasurement = null - iterator = null - charIndex = 0 - - for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens - charWidths = @presenter.getScopedCharacterWidths(scopes) - - valueIndex = 0 - while valueIndex < value.length - if hasPairedCharacter - char = value.substr(valueIndex, 2) - charLength = 2 - valueIndex += 2 - else - char = value[valueIndex] - charLength = 1 - valueIndex++ - - continue if char is '\0' - - unless charWidths[char]? - unless textNode? - rangeForMeasurement ?= document.createRange() - iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter) - textNode = iterator.nextNode() - textNodeIndex = 0 - nextTextNodeIndex = textNode.textContent.length - - while nextTextNodeIndex <= charIndex - textNode = iterator.nextNode() - textNodeIndex = nextTextNodeIndex - nextTextNodeIndex = textNodeIndex + textNode.textContent.length - - i = charIndex - textNodeIndex - rangeForMeasurement.setStart(textNode, i) - rangeForMeasurement.setEnd(textNode, i + charLength) - charWidth = rangeForMeasurement.getBoundingClientRect().width - @presenter.setScopedCharacterWidth(scopes, char, charWidth) - - charIndex += charLength - - @measuredLines.add(lineId) - clearScopedCharWidths: -> - @measuredLines.clear() + for id, component of @tileComponentsByTileId + component.clearMeasurements() + @presenter.clearScopedCharacterWidths() diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 017856368..0b97467c7 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -2,9 +2,6 @@ _ = require 'underscore-plus' {toArray} = require 'underscore-plus' {$$} = require 'space-pen' -CursorsComponent = require './cursors-component' -HighlightsComponent = require './highlights-component' - DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} WrapperDiv = document.createElement('div') @@ -219,7 +216,7 @@ class TileComponent if newLineState.top isnt oldLineState.top lineNode.style.top = newLineState.top + 'px' - oldLineState.top = newLineState.cop + oldLineState.top = newLineState.top if newLineState.screenRow isnt oldLineState.screenRow lineNode.dataset.screenRow = newLineState.screenRow @@ -229,28 +226,12 @@ class TileComponent lineNodeForScreenRow: (screenRow) -> @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] - measureLineHeightAndDefaultCharWidth: -> - @domNode.appendChild(DummyLineNode) - lineHeightInPixels = DummyLineNode.getBoundingClientRect().height - charWidth = DummyLineNode.firstChild.getBoundingClientRect().width - @domNode.removeChild(DummyLineNode) - - @presenter.setLineHeight(lineHeightInPixels) - @presenter.setBaseCharacterWidth(charWidth) - - remeasureCharacterWidths: -> - return unless @presenter.baseCharacterWidth - - @clearScopedCharWidths() - @measureCharactersInNewLines() - measureCharactersInNewLines: -> - @presenter.batchCharacterMeasurement => - for id, lineState of @oldState.tiles[@id]?.lines - unless @measuredLines.has(id) - lineNode = @lineNodesByLineId[id] - @measureCharactersInLine(id, lineState, lineNode) - return + for id, lineState of @oldState.tiles[@id]?.lines + unless @measuredLines.has(id) + lineNode = @lineNodesByLineId[id] + @measureCharactersInLine(id, lineState, lineNode) + return measureCharactersInLine: (lineId, tokenizedLine, lineNode) -> rangeForMeasurement = null @@ -296,6 +277,5 @@ class TileComponent @measuredLines.add(lineId) - clearScopedCharWidths: -> + clearMeasurements: -> @measuredLines.clear() - @presenter.clearScopedCharacterWidths() From 40cde496d72d9de89e3fe36381705bfc806e5cc5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 19:03:13 +0200 Subject: [PATCH 07/93] Draw highlight components appropriately --- src/text-editor-presenter.coffee | 8 ++++---- src/tile-component.coffee | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c8d74b428..9f31c2a54 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1218,7 +1218,7 @@ class TextEditorPresenter if spannedRows is 1 [ - top: startPixelPosition.top + top: startPixelPosition.top - @scrollTop height: lineHeightInPixels left: startPixelPosition.left width: endPixelPosition.left - startPixelPosition.left @@ -1228,7 +1228,7 @@ class TextEditorPresenter # First row, extending from selection start to the right side of screen regions.push( - top: startPixelPosition.top + top: startPixelPosition.top - @scrollTop left: startPixelPosition.left height: lineHeightInPixels right: 0 @@ -1237,7 +1237,7 @@ class TextEditorPresenter # Middle rows, extending from left side to right side of screen if spannedRows > 2 regions.push( - top: startPixelPosition.top + lineHeightInPixels + top: startPixelPosition.top + lineHeightInPixels - @scrollTop height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels left: 0 right: 0 @@ -1246,7 +1246,7 @@ class TextEditorPresenter # Last row, extending from left side of screen to selection end if screenRange.end.column > 0 regions.push( - top: endPixelPosition.top + top: endPixelPosition.top - @scrollTop height: lineHeightInPixels left: 0 width: endPixelPosition.left diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 0b97467c7..550e2c272 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -41,10 +41,6 @@ class TileComponent @domNode.style['-webkit-transform'] = "translate3d(0, #{@newState.tiles[@id].top}px, 0px)" @oldState.tiles[@id]?.top = @newState.tiles[@id].top - if @newState.backgroundColor isnt @oldState.backgroundColor - @domNode.style.backgroundColor = @newState.backgroundColor - @oldState.backgroundColor = @newState.backgroundColor - @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible @updateLineNodes() From 936133cfdcb70a159c9d3e144ca311d02281452e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 19:06:37 +0200 Subject: [PATCH 08/93] Always subtract @scrollTop --- src/text-editor-presenter.coffee | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 9f31c2a54..f0fbe7f7a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -977,11 +977,12 @@ class TextEditorPresenter charLength = 1 valueIndex++ - return {top, left} if column is targetColumn + break if column is targetColumn left += characterWidths[char] ? baseCharacterWidth unless char is '\0' column += charLength - {top, left} + + {top: top - @scrollTop, left} hasPixelRectRequirements: -> @hasPixelPositionRequirements() and @scrollWidth? @@ -1218,7 +1219,7 @@ class TextEditorPresenter if spannedRows is 1 [ - top: startPixelPosition.top - @scrollTop + top: startPixelPosition.top height: lineHeightInPixels left: startPixelPosition.left width: endPixelPosition.left - startPixelPosition.left @@ -1228,7 +1229,7 @@ class TextEditorPresenter # First row, extending from selection start to the right side of screen regions.push( - top: startPixelPosition.top - @scrollTop + top: startPixelPosition.top left: startPixelPosition.left height: lineHeightInPixels right: 0 @@ -1237,7 +1238,7 @@ class TextEditorPresenter # Middle rows, extending from left side to right side of screen if spannedRows > 2 regions.push( - top: startPixelPosition.top + lineHeightInPixels - @scrollTop + top: startPixelPosition.top + lineHeightInPixels height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels left: 0 right: 0 @@ -1246,7 +1247,7 @@ class TextEditorPresenter # Last row, extending from left side of screen to selection end if screenRange.end.column > 0 regions.push( - top: endPixelPosition.top - @scrollTop + top: endPixelPosition.top height: lineHeightInPixels left: 0 width: endPixelPosition.left From 3a1bb898c33160f3d2c8cfe37b584bb5ab6f241f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 19:09:48 +0200 Subject: [PATCH 09/93] Fix clicking on line --- src/text-editor-component.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 4c6480510..df88d0706 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -763,6 +763,7 @@ class TextEditorComponent screenPositionForMouseEvent: (event) -> pixelPosition = @pixelPositionForMouseEvent(event) + pixelPosition.top += @presenter.scrollTop @editor.screenPositionForPixelPosition(pixelPosition) pixelPositionForMouseEvent: (event) -> From 9aaa8b483388896ab9c5ad6ef0e680fd7f555214 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 19:21:34 +0200 Subject: [PATCH 10/93] Better defaults --- src/text-editor-presenter.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index f0fbe7f7a..2cf2188d7 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -21,8 +21,8 @@ class TextEditorPresenter @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileOverdrawMargin ?= 0 - @tileCount ?= 4 + @tileOverdrawMargin ?= 1 + @tileCount ?= 3 @linesPresentersByTileIndex = {} @disposables = new CompositeDisposable From 216b757ac0aa5aa96f4fb6a65f17eacd841fc86b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 May 2015 19:49:49 +0200 Subject: [PATCH 11/93] Keep scrolling tile around for mousewheel --- src/text-editor-component.coffee | 1 - src/text-editor-presenter.coffee | 18 ++++++++++-------- src/tile-component.coffee | 4 ++++ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index df88d0706..71ca96d87 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -354,7 +354,6 @@ class TextEditorComponent event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft() else # Scrolling vertically - @presenter.setMouseWheelScreenRow(@screenRowForNode(event.target)) previousScrollTop = @presenter.getScrollTop() @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) event.preventDefault() unless previousScrollTop is @presenter.getScrollTop() diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2cf2188d7..1411470c1 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -317,9 +317,11 @@ class TextEditorPresenter visibleTilesIndexes = [startIndex...endIndex] for index, tile of @state.content.tiles - unless index in visibleTilesIndexes - delete @state.content.tiles[index] - delete @linesPresentersByTileIndex[index] + continue if index is @scrollingTile + continue if index in visibleTilesIndexes + + delete @state.content.tiles[index] + delete @linesPresentersByTileIndex[index] for index in visibleTilesIndexes presenter = @linesPresentersByTileIndex[index] ?= new LinesPresenter(@) @@ -728,8 +730,8 @@ class TextEditorPresenter didStopScrolling: -> @state.content.scrollingVertically = false - if @mouseWheelScreenRow? - @mouseWheelScreenRow = null + if @scrollingTile? + @scrollingTile = null @shouldUpdateTilesState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -895,9 +897,9 @@ class TextEditorPresenter @emitDidUpdateState() - setMouseWheelScreenRow: (mouseWheelScreenRow) -> - unless @mouseWheelScreenRow is mouseWheelScreenRow - @mouseWheelScreenRow = mouseWheelScreenRow + setScrollingTile: (tileId) -> + if @scrollingTile isnt tileId + @scrollingTile = tileId @didStartScrolling() setBaseCharacterWidth: (baseCharacterWidth) -> diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 550e2c272..ff2d7eb46 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -21,9 +21,13 @@ class TileComponent @screenRowsByLineId = {} @lineIdsByScreenRow = {} @domNode = document.createElement("div") + @domNode.addEventListener("mousewheel", @onMouseWheel) @domNode.style.position = "absolute" @domNode.classList.add("tile") + onMouseWheel: => + @presenter.setScrollingTile(@id) + getDomNode: -> @domNode From daa4b33e649842475475f3aab985bbff505c9934 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 6 May 2015 08:55:53 +0200 Subject: [PATCH 12/93] wip --- spec/text-editor-presenter-spec.coffee | 4 ++-- src/text-editor-presenter.coffee | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index b2c40fc36..6db369f69 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -657,7 +657,7 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.setPlaceholderText("new-placeholder-text") expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text" - fdescribe ".tiles", -> + describe ".tiles", -> it "contains states for tiles that are visible on screen, plus and minus the overdraw margin", -> presenter = buildPresenter(explicitHeight: 3, scrollTop: 1, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) @@ -762,7 +762,7 @@ describe "TextEditorPresenter", -> expect(lineStateForScreenRow(presenter, 5)).toBeDefined() expect(lineStateForScreenRow(presenter, 6)).toBeUndefined() - fffit "updates when the editor's content changes", -> + it "updates when the editor's content changes", -> lineStateForScreenRow = (presenter, tile, row) -> lineId = presenter.model.tokenizedLineForScreenRow(row).id presenter.getState().content.tiles[tile].lines[lineId] diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 1411470c1..071195ec3 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -21,7 +21,7 @@ class TextEditorPresenter @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileOverdrawMargin ?= 1 + @tileOverdrawMargin ?= 0 @tileCount ?= 3 @linesPresentersByTileIndex = {} From 02838ad193c6e3d06f3f32e5426476e2b9127ecf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 6 May 2015 09:46:47 +0200 Subject: [PATCH 13/93] Recycle tiles --- src/lines-component.coffee | 15 +++++++++++---- src/text-editor-presenter.coffee | 4 +++- src/tile-component.coffee | 28 ++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 547b54e56..fda77a76a 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -21,6 +21,7 @@ class LinesComponent constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> @tileComponentsByTileId = {} + @freeDomNodes = [] @domNode = document.createElement('div') @domNode.classList.add('lines') @@ -81,7 +82,10 @@ class LinesComponent return removeTileNode: (id) -> - @tileComponentsByTileId[id].getDomNode().remove() + node = @tileComponentsByTileId[id].getDomNode() + + node.style.display = "none" + @freeDomNodes.push(node) delete @tileComponentsByTileId[id] delete @oldState.tiles[id] @@ -91,10 +95,13 @@ class LinesComponent @removeTileNode(id) for id, tileState of @newState.tiles - tileComponent = @tileComponentsByTileId[id] ?= new TileComponent({id, @presenter}) + if @oldState.tiles.hasOwnProperty(id) + tileComponent = @tileComponentsByTileId[id] + else + domNode = @freeDomNodes.pop() + tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter, domNode}) - unless @oldState.tiles.hasOwnProperty(id) - @domNode.appendChild(tileComponent.getDomNode()) + @domNode.appendChild(tileComponent.getDomNode()) unless domNode? @oldState.tiles[id] = cloneObject(tileState) tileComponent.updateSync(@newState) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 071195ec3..4ff358cec 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -318,7 +318,7 @@ class TextEditorPresenter for index, tile of @state.content.tiles continue if index is @scrollingTile - continue if index in visibleTilesIndexes + continue if startIndex <= index <= endIndex delete @state.content.tiles[index] delete @linesPresentersByTileIndex[index] @@ -329,10 +329,12 @@ class TextEditorPresenter presenter.endRow = Math.ceil(Math.min(@model.getScreenLineCount(), (index + 1) * linesPerTile)) presenter.lineHeight = @lineHeight + isNewTile = not @state.content.tiles.hasOwnProperty(index) tile = @state.content.tiles[index] ?= {} tile.top = (index * linesPerTile * @lineHeight) - @scrollTop tile.lines = presenter.getState() tile.height = linesPerTile * @lineHeight + tile.newlyCreated = isNewTile updateCursorsState: -> @state.content.cursors = {} diff --git a/src/tile-component.coffee b/src/tile-component.coffee index ff2d7eb46..023f9a369 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -15,14 +15,15 @@ module.exports = class TileComponent placeholderTextDiv: null - constructor: ({@presenter, @id}) -> + constructor: ({@presenter, @id, @domNode}) -> @measuredLines = new Set @lineNodesByLineId = {} @screenRowsByLineId = {} @lineIdsByScreenRow = {} - @domNode = document.createElement("div") + @domNode ?= document.createElement("div") @domNode.addEventListener("mousewheel", @onMouseWheel) @domNode.style.position = "absolute" + @domNode.style.display = "block" @domNode.classList.add("tile") onMouseWheel: => @@ -45,8 +46,27 @@ class TileComponent @domNode.style['-webkit-transform'] = "translate3d(0, #{@newState.tiles[@id].top}px, 0px)" @oldState.tiles[@id]?.top = @newState.tiles[@id].top - @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible - @updateLineNodes() + if @newState.tiles[@id].newlyCreated + newLineIds = [] + newLinesHTML = "" + + for id, lineState of @newState.tiles[@id].lines + newLineIds.push(id) + newLinesHTML += @buildLineHTML(id) + @screenRowsByLineId[id] = lineState.screenRow + @lineIdsByScreenRow[lineState.screenRow] = id + @oldState.tiles[@id]?.lines[id] = cloneObject(lineState) + + return if newLineIds.length is 0 + + @domNode.innerHTML = newLinesHTML + newLineNodes = _.toArray(@domNode.children) + for id, i in newLineIds + lineNode = newLineNodes[i] + @lineNodesByLineId[id] = lineNode + else + @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible + @updateLineNodes() if @newState.scrollWidth isnt @oldState.scrollWidth @domNode.style.width = @newState.scrollWidth + 'px' From c99ffa04f11307fab3593bf8ad3087744a4e2ede Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 6 May 2015 10:03:49 +0200 Subject: [PATCH 14/93] Hide scrolling tile --- src/text-editor-presenter.coffee | 24 +++++++++++++++--------- src/tile-component.coffee | 4 ++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 4ff358cec..59ff21afe 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -314,16 +314,9 @@ class TextEditorPresenter Math.ceil(@model.getScreenLineCount() / linesPerTile), Math.ceil(@endRow / linesPerTile) + @tileOverdrawMargin ) - visibleTilesIndexes = [startIndex...endIndex] - for index, tile of @state.content.tiles - continue if index is @scrollingTile - continue if startIndex <= index <= endIndex - - delete @state.content.tiles[index] - delete @linesPresentersByTileIndex[index] - - for index in visibleTilesIndexes + visibleTiles = {} + for index in [startIndex...endIndex] presenter = @linesPresentersByTileIndex[index] ?= new LinesPresenter(@) presenter.startRow = Math.floor(index * linesPerTile) presenter.endRow = Math.ceil(Math.min(@model.getScreenLineCount(), (index + 1) * linesPerTile)) @@ -335,6 +328,19 @@ class TextEditorPresenter tile.lines = presenter.getState() tile.height = linesPerTile * @lineHeight tile.newlyCreated = isNewTile + tile.display = "block" + + visibleTiles[index] = true + + for index, tile of @state.content.tiles + continue if visibleTiles.hasOwnProperty(index) + + if index is @scrollingTile + tile.display = "none" + else + delete @state.content.tiles[index] + delete @linesPresentersByTileIndex[index] + updateCursorsState: -> @state.content.cursors = {} diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 023f9a369..0f366dbdc 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -38,6 +38,10 @@ class TileComponent @oldState = {tiles: {}} @oldState.tiles[@id] = { lines: {}} + if @newState.tiles[@id].display isnt @oldState.tiles[@id]?.display + @domNode.style.display = @newState.tiles[@id].display + @oldState.tiles[@id]?.display = @newState.tiles[@id].display + if @newState.tiles[@id].height isnt @oldState.tiles[@id]?.height @domNode.style.height = @newState.tiles[@id].height + 'px' @oldState.tiles[@id]?.height = @newState.tiles[@id].height From a6e0fa6e9142d71fc5cd9c5ab79ff546d5a107f1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 11 May 2015 09:42:04 +0200 Subject: [PATCH 15/93] Scroll exactly as we did before tiling Except that now we store the scrolling tile, instead of the scrolling row. --- src/text-editor-component.coffee | 8 +++++--- src/text-editor-presenter.coffee | 2 +- src/tile-component.coffee | 5 +---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 71ca96d87..8c497bd98 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -354,6 +354,7 @@ class TextEditorComponent event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft() else # Scrolling vertically + @presenter.setScrollingTileId(@tileIdForNode(event.target)) previousScrollTop = @presenter.getScrollTop() @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) event.preventDefault() unless previousScrollTop is @presenter.getScrollTop() @@ -728,11 +729,12 @@ class TextEditorComponent lineNumberNodeForScreenRow: (screenRow) -> @gutterContainerComponent.getLineNumberGutterComponent().lineNumberNodeForScreenRow(screenRow) - screenRowForNode: (node) -> + tileIdForNode: (node) -> while node? - if screenRow = node.dataset.screenRow - return parseInt(screenRow) + if tileId = node.dataset.tileId + return tileId node = node.parentElement + null getFontSize: -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 59ff21afe..12044bff0 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -905,7 +905,7 @@ class TextEditorPresenter @emitDidUpdateState() - setScrollingTile: (tileId) -> + setScrollingTileId: (tileId) -> if @scrollingTile isnt tileId @scrollingTile = tileId @didStartScrolling() diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 0f366dbdc..5bd299d79 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -21,14 +21,11 @@ class TileComponent @screenRowsByLineId = {} @lineIdsByScreenRow = {} @domNode ?= document.createElement("div") - @domNode.addEventListener("mousewheel", @onMouseWheel) + @domNode.dataset.tileId = @id @domNode.style.position = "absolute" @domNode.style.display = "block" @domNode.classList.add("tile") - onMouseWheel: => - @presenter.setScrollingTile(@id) - getDomNode: -> @domNode From 8aaac870dcb94696cbc8174a1d92a311e07cefa3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 11 May 2015 09:45:55 +0200 Subject: [PATCH 16/93] Ensure we have at least one line per tile This will prevent, for example, a mini editor (which has no `@height` by default) from not showing characters. --- src/text-editor-presenter.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 12044bff0..0111c7a8a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -306,6 +306,7 @@ class TextEditorPresenter return unless @startRow? and @endRow? and @lineHeight? linesPerTile = Math.floor(@height / @lineHeight / @tileCount) + linesPerTile = Math.max(1, linesPerTile) startIndex = Math.max( 0, Math.floor(@startRow / linesPerTile) - @tileOverdrawMargin From a6dab4860c7ced496f9fd4c5ffc6e3c448c5f8ff Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 11 May 2015 10:01:02 +0200 Subject: [PATCH 17/93] Position overlays absolutely We did so by introducing an `absolute` (optional) parameter in `pixelPositionForScreenPosition`. --- src/text-editor-presenter.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 0111c7a8a..e9944afce 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -375,7 +375,7 @@ class TextEditorPresenter else screenPosition = decoration.getMarker().getHeadScreenPosition() - pixelPosition = @pixelPositionForScreenPosition(screenPosition) + pixelPosition = @pixelPositionForScreenPosition(screenPosition, true, absolute: true) {scrollTop, scrollLeft} = @state.content @@ -963,7 +963,7 @@ class TextEditorPresenter hasPixelPositionRequirements: -> @lineHeight? and @baseCharacterWidth? - pixelPositionForScreenPosition: (screenPosition, clip=true) -> + pixelPositionForScreenPosition: (screenPosition, clip=true, {absolute}={}) -> screenPosition = Point.fromObject(screenPosition) screenPosition = @model.clipScreenPosition(screenPosition) if clip @@ -993,7 +993,8 @@ class TextEditorPresenter left += characterWidths[char] ? baseCharacterWidth unless char is '\0' column += charLength - {top: top - @scrollTop, left} + top -= @scrollTop unless absolute + {top, left} hasPixelRectRequirements: -> @hasPixelPositionRequirements() and @scrollWidth? From 19a95651dcbbba12550be80991b3b9086d89f3d5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 May 2015 09:25:31 +0200 Subject: [PATCH 18/93] Relativize `pixelPositionForScreenPosition`.`top` This commit makes the method return a `top` value which is relative to the viewport. However, we still need to return an absolute value for `left` because lines are moved horizontally through a single transform on their parent node. I would prefer being consistent (i.e. make everything relative), but moving every single tile to simulate scrolling, on the other hand, seems overkill and it's not really intuitive given that we are not tiling horizontally. /cc: @nathansobo --- src/text-editor-presenter.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index e9944afce..c7bf282ed 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -375,11 +375,11 @@ class TextEditorPresenter else screenPosition = decoration.getMarker().getHeadScreenPosition() - pixelPosition = @pixelPositionForScreenPosition(screenPosition, true, absolute: true) + pixelPosition = @pixelPositionForScreenPosition(screenPosition, true) - {scrollTop, scrollLeft} = @state.content + {scrollLeft} = @state.content - top = pixelPosition.top + @lineHeight - scrollTop + top = pixelPosition.top + @lineHeight left = pixelPosition.left + @gutterWidth - scrollLeft if overlayDimensions = @overlayDimensions[decoration.id] @@ -963,7 +963,7 @@ class TextEditorPresenter hasPixelPositionRequirements: -> @lineHeight? and @baseCharacterWidth? - pixelPositionForScreenPosition: (screenPosition, clip=true, {absolute}={}) -> + pixelPositionForScreenPosition: (screenPosition, clip=true) -> screenPosition = Point.fromObject(screenPosition) screenPosition = @model.clipScreenPosition(screenPosition) if clip @@ -993,7 +993,7 @@ class TextEditorPresenter left += characterWidths[char] ? baseCharacterWidth unless char is '\0' column += charLength - top -= @scrollTop unless absolute + top -= @scrollTop {top, left} hasPixelRectRequirements: -> From 63e2764b7f455f6e590dfe1ee8ab1a57335bc449 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 10:45:42 +0200 Subject: [PATCH 19/93] :art: Inline LinesPresenter --- src/lines-presenter.coffee | 41 ------------------------------ src/text-editor-presenter.coffee | 43 ++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 48 deletions(-) delete mode 100644 src/lines-presenter.coffee diff --git a/src/lines-presenter.coffee b/src/lines-presenter.coffee deleted file mode 100644 index c1e3676ba..000000000 --- a/src/lines-presenter.coffee +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = -class LinesPresenter - startRow: null - endRow: null - lineHeight: null - - constructor: (@presenter) -> - @lines = {} - - getState: -> - visibleLineIds = {} - row = @startRow - while row < @endRow - line = @presenter.model.tokenizedLineForScreenRow(row) - unless line? - throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") - - visibleLineIds[line.id] = true - if @lines.hasOwnProperty(line.id) - lineState = @lines[line.id] - lineState.screenRow = row - lineState.top = (row - @startRow) * @lineHeight - lineState.decorationClasses = @presenter.lineDecorationClassesForRow(row) - else - @lines[line.id] = - screenRow: row - text: line.text - tokens: line.tokens - isOnlyWhitespace: line.isOnlyWhitespace() - endOfLineInvisibles: line.endOfLineInvisibles - indentLevel: line.indentLevel - tabLength: line.tabLength - fold: line.fold - top: (row - @startRow) * @lineHeight - decorationClasses: @presenter.lineDecorationClassesForRow(row) - row++ - - for id, line of @lines - delete @lines[id] unless visibleLineIds.hasOwnProperty(id) - - @lines diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c7bf282ed..9d8c22c3c 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -23,7 +23,6 @@ class TextEditorPresenter @gutterWidth ?= 0 @tileOverdrawMargin ?= 0 @tileCount ?= 3 - @linesPresentersByTileIndex = {} @disposables = new CompositeDisposable @emitter = new Emitter @@ -318,19 +317,18 @@ class TextEditorPresenter visibleTiles = {} for index in [startIndex...endIndex] - presenter = @linesPresentersByTileIndex[index] ?= new LinesPresenter(@) - presenter.startRow = Math.floor(index * linesPerTile) - presenter.endRow = Math.ceil(Math.min(@model.getScreenLineCount(), (index + 1) * linesPerTile)) - presenter.lineHeight = @lineHeight + startRow = Math.floor(index * linesPerTile) + endRow = Math.ceil(Math.min(@model.getScreenLineCount(), (index + 1) * linesPerTile)) isNewTile = not @state.content.tiles.hasOwnProperty(index) tile = @state.content.tiles[index] ?= {} tile.top = (index * linesPerTile * @lineHeight) - @scrollTop - tile.lines = presenter.getState() tile.height = linesPerTile * @lineHeight tile.newlyCreated = isNewTile tile.display = "block" + @updateLinesState(tile, startRow, endRow) + visibleTiles[index] = true for index, tile of @state.content.tiles @@ -340,9 +338,40 @@ class TextEditorPresenter tile.display = "none" else delete @state.content.tiles[index] - delete @linesPresentersByTileIndex[index] + updateLinesState: (tileState, startRow, endRow) -> + tileState.lines ?= {} + visibleLineIds = {} + row = startRow + while row < endRow + line = @model.tokenizedLineForScreenRow(row) + unless line? + throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") + + visibleLineIds[line.id] = true + if tileState.lines.hasOwnProperty(line.id) + lineState = tileState.lines[line.id] + lineState.screenRow = row + lineState.top = (row - startRow) * @lineHeight + lineState.decorationClasses = @lineDecorationClassesForRow(row) + else + tileState.lines[line.id] = + screenRow: row + text: line.text + tokens: line.tokens + isOnlyWhitespace: line.isOnlyWhitespace() + endOfLineInvisibles: line.endOfLineInvisibles + indentLevel: line.indentLevel + tabLength: line.tabLength + fold: line.fold + top: (row - startRow) * @lineHeight + decorationClasses: @lineDecorationClassesForRow(row) + row++ + + for id, line of tileState.lines + delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) + updateCursorsState: -> @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation From 74a0ce0720860cbca2970d1a54e8606d6c837c8d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 10:59:50 +0200 Subject: [PATCH 20/93] Remove require to LinesPresenter --- src/text-editor-presenter.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 9d8c22c3c..28d677734 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -2,7 +2,6 @@ {Point, Range} = require 'text-buffer' _ = require 'underscore-plus' Decoration = require './decoration' -LinesPresenter = require './lines-presenter' module.exports = class TextEditorPresenter From 4c2683a378d7a3089d1c435369ffb9276dd313a4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 11:14:21 +0200 Subject: [PATCH 21/93] :art: Clarify `@updateTilesState` --- src/text-editor-presenter.coffee | 59 +++++++++++++++++--------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 28d677734..37b5daee1 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -300,43 +300,48 @@ class TextEditorPresenter @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null + # REFACTOR: This should be a @field rather than a function. + linesPerTile: -> + linesPerTile = Math.floor(@height / @lineHeight / @tileCount) + Math.max(1, linesPerTile) + + tileForRow: (row) -> + row - (row % @linesPerTile()) + + getVisibleTileRange: -> + startTileRow = Math.max(0, @tileForRow(@startRow) - @tileOverdrawMargin) + endTileRow = Math.min( + @tileForRow(@model.getScreenLineCount()), + @tileForRow(@endRow) + @tileOverdrawMargin + ) + + [startTileRow..endTileRow] + updateTilesState: -> return unless @startRow? and @endRow? and @lineHeight? - linesPerTile = Math.floor(@height / @lineHeight / @tileCount) - linesPerTile = Math.max(1, linesPerTile) - - startIndex = Math.max( - 0, Math.floor(@startRow / linesPerTile) - @tileOverdrawMargin - ) - endIndex = Math.min( - Math.ceil(@model.getScreenLineCount() / linesPerTile), - Math.ceil(@endRow / linesPerTile) + @tileOverdrawMargin - ) - visibleTiles = {} - for index in [startIndex...endIndex] - startRow = Math.floor(index * linesPerTile) - endRow = Math.ceil(Math.min(@model.getScreenLineCount(), (index + 1) * linesPerTile)) + for startRow in @getVisibleTileRange() by @linesPerTile() + endRow = Math.min(@model.getScreenLineCount(), startRow + @linesPerTile()) - isNewTile = not @state.content.tiles.hasOwnProperty(index) - tile = @state.content.tiles[index] ?= {} - tile.top = (index * linesPerTile * @lineHeight) - @scrollTop - tile.height = linesPerTile * @lineHeight + isNewTile = not @state.content.tiles.hasOwnProperty(startRow) + tile = @state.content.tiles[startRow] ?= {} + tile.top = startRow * @lineHeight - @scrollTop + tile.height = @linesPerTile() * @lineHeight tile.newlyCreated = isNewTile tile.display = "block" @updateLinesState(tile, startRow, endRow) - visibleTiles[index] = true + visibleTiles[startRow] = true - for index, tile of @state.content.tiles - continue if visibleTiles.hasOwnProperty(index) + for id, tile of @state.content.tiles + continue if visibleTiles.hasOwnProperty(id) - if index is @scrollingTile + if id is @scrollingTileId tile.display = "none" else - delete @state.content.tiles[index] + delete @state.content.tiles[id] updateLinesState: (tileState, startRow, endRow) -> @@ -767,8 +772,8 @@ class TextEditorPresenter didStopScrolling: -> @state.content.scrollingVertically = false - if @scrollingTile? - @scrollingTile = null + if @scrollingTileId? + @scrollingTileId = null @shouldUpdateTilesState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -935,8 +940,8 @@ class TextEditorPresenter @emitDidUpdateState() setScrollingTileId: (tileId) -> - if @scrollingTile isnt tileId - @scrollingTile = tileId + if @scrollingTileId isnt tileId + @scrollingTileId = tileId @didStartScrolling() setBaseCharacterWidth: (baseCharacterWidth) -> From 948b8f95339b3f335b79724c476b6374cf5c5de4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 11:22:02 +0200 Subject: [PATCH 22/93] Remember to fix scrolling for line-numbers --- src/text-editor-presenter.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 37b5daee1..e5eadbbcd 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -550,6 +550,7 @@ class TextEditorPresenter @state.gutters.lineNumberGutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} visibleLineNumberIds[id] = true + # FIXME: We should either rely on @mouseWheelScreenRow or convert this to use tiles. if @mouseWheelScreenRow? bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow) wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow) From 94373e434c5e52f64a8890bb7ee1fa90dd4f057d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 11:26:02 +0200 Subject: [PATCH 23/93] Make `tileSize` a field rather than a fn --- src/text-editor-presenter.coffee | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index e5eadbbcd..f5fb1257a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -67,6 +67,7 @@ class TextEditorPresenter @updateScrollbarDimensions() @updateStartRow() @updateEndRow() + @updateTileSize() @updateFocusedState() if @shouldUpdateFocusedState @updateHeightState() if @shouldUpdateHeightState @@ -300,15 +301,10 @@ class TextEditorPresenter @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null - # REFACTOR: This should be a @field rather than a function. - linesPerTile: -> - linesPerTile = Math.floor(@height / @lineHeight / @tileCount) - Math.max(1, linesPerTile) - tileForRow: (row) -> - row - (row % @linesPerTile()) + row - (row % @tileSize) - getVisibleTileRange: -> + getVisibleTilesRange: -> startTileRow = Math.max(0, @tileForRow(@startRow) - @tileOverdrawMargin) endTileRow = Math.min( @tileForRow(@model.getScreenLineCount()), @@ -321,13 +317,13 @@ class TextEditorPresenter return unless @startRow? and @endRow? and @lineHeight? visibleTiles = {} - for startRow in @getVisibleTileRange() by @linesPerTile() - endRow = Math.min(@model.getScreenLineCount(), startRow + @linesPerTile()) + for startRow in @getVisibleTilesRange() by @tileSize + endRow = Math.min(@model.getScreenLineCount(), startRow + @tileSize) isNewTile = not @state.content.tiles.hasOwnProperty(startRow) tile = @state.content.tiles[startRow] ?= {} tile.top = startRow * @lineHeight - @scrollTop - tile.height = @linesPerTile() * @lineHeight + tile.height = @tileSize * @lineHeight tile.newlyCreated = isNewTile tile.display = "block" @@ -343,7 +339,6 @@ class TextEditorPresenter else delete @state.content.tiles[id] - updateLinesState: (tileState, startRow, endRow) -> tileState.lines ?= {} visibleLineIds = {} @@ -577,6 +572,12 @@ class TextEditorPresenter endRow = startRow + visibleLinesCount @endRow = Math.min(@model.getScreenLineCount(), endRow) + updateTileSize: -> + return unless @height? and @lineHeight? and @tileCount? + + @tileSize = Math.floor(@height / @lineHeight / @tileCount) + @tileSize = Math.max(1, @tileSize) + updateScrollWidth: -> return unless @contentWidth? and @clientWidth? From 11a0fa7a1246901810cbf3fcedc00739c94931c0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 11:39:28 +0200 Subject: [PATCH 24/93] updateTileSize on start --- spec/text-editor-presenter-spec.coffee | 18 ++++++++---------- src/text-editor-presenter.coffee | 1 + 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 6db369f69..98c4caecb 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -658,28 +658,26 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text" describe ".tiles", -> - it "contains states for tiles that are visible on screen, plus and minus the overdraw margin", -> - presenter = buildPresenter(explicitHeight: 3, scrollTop: 1, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) + fffit "contains states for tiles that are visible on screen", -> + presenter = buildPresenter(explicitHeight: 3, scrollTop: 1, lineHeight: 1, tileCount: 3) + console.log JSON.stringify(presenter.getState().content.tiles) expectValues presenter.getState().content.tiles[0], { - top: -1 - } - expectValues presenter.getState().content.tiles[1], { top: 0 } - expectValues presenter.getState().content.tiles[2], { + expectValues presenter.getState().content.tiles[1], { top: 1 } - expectValues presenter.getState().content.tiles[3], { + expectValues presenter.getState().content.tiles[2], { top: 2 } - expectValues presenter.getState().content.tiles[4], { + expectValues presenter.getState().content.tiles[3], { top: 3 } - expectValues presenter.getState().content.tiles[5], { + expectValues presenter.getState().content.tiles[4], { top: 4 } - expect(presenter.getState().content.tiles[6]).toBeUndefined() + expect(presenter.getState().content.tiles[5]).toBeUndefined() it "does not overdraw above the first row", -> presenter = buildPresenter(explicitHeight: 3, scrollTop: 0, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index f5fb1257a..a8d845c72 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -223,6 +223,7 @@ class TextEditorPresenter @updateScrollbarDimensions() @updateStartRow() @updateEndRow() + @updateTileSize() @updateFocusedState() @updateHeightState() From 29c6e9d89cbef26cd55ce4fa0de89bfae17e4f00 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 11:57:38 +0200 Subject: [PATCH 25/93] :green_heart: Start fixing presenter specs --- spec/text-editor-presenter-spec.coffee | 105 +++++++++++-------------- 1 file changed, 46 insertions(+), 59 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 98c4caecb..e0fbda5ae 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -658,53 +658,32 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text" describe ".tiles", -> - fffit "contains states for tiles that are visible on screen", -> - presenter = buildPresenter(explicitHeight: 3, scrollTop: 1, lineHeight: 1, tileCount: 3) + it "contains states for tiles that are visible on screen", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) - console.log JSON.stringify(presenter.getState().content.tiles) expectValues presenter.getState().content.tiles[0], { top: 0 } - expectValues presenter.getState().content.tiles[1], { - top: 1 - } expectValues presenter.getState().content.tiles[2], { top: 2 } - expectValues presenter.getState().content.tiles[3], { - top: 3 - } expectValues presenter.getState().content.tiles[4], { top: 4 } - expect(presenter.getState().content.tiles[5]).toBeUndefined() + expectValues presenter.getState().content.tiles[6], { + top: 6 + } - it "does not overdraw above the first row", -> - presenter = buildPresenter(explicitHeight: 3, scrollTop: 0, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) - expect(presenter.getState().content.tiles[-1]).toBeUndefined() - expect(presenter.getState().content.tiles[0]).toBeDefined() - expect(presenter.getState().content.tiles[1]).toBeDefined() - expect(presenter.getState().content.tiles[2]).toBeDefined() - expect(presenter.getState().content.tiles[3]).toBeDefined() - expect(presenter.getState().content.tiles[4]).toBeDefined() - expect(presenter.getState().content.tiles[5]).toBeUndefined() - - it "does not overdraw below the last row", -> - presenter = buildPresenter(explicitHeight: 3, scrollTop: 10, lineHeight: 1, tileCount: 3, tileOverdrawMargin: 1) expect(presenter.getState().content.tiles[8]).toBeUndefined() - expect(presenter.getState().content.tiles[9]).toBeDefined() - expect(presenter.getState().content.tiles[10]).toBeDefined() - expect(presenter.getState().content.tiles[11]).toBeDefined() - expect(presenter.getState().content.tiles[12]).toBeDefined() - expect(presenter.getState().content.tiles[13]).toBeUndefined() - xit "includes state for all tiles if no external ::explicitHeight is assigned", -> + + it "includes state for all tiles if no external ::explicitHeight is assigned", -> presenter = buildPresenter(explicitHeight: null, tileCount: 12, tileOverdrawMargin: 1) expect(presenter.getState().content.tiles[0]).toBeDefined() expect(presenter.getState().content.tiles[12]).toBeDefined() it "is empty until all of the required measurements are assigned", -> - presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, tileCount: 3, tileOverdrawMargin: 1) + presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, tileCount: 3) expect(presenter.getState().content.tiles).toEqual({}) presenter.setExplicitHeight(25) @@ -717,48 +696,57 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles).not.toEqual({}) it "updates when ::scrollTop changes", -> - presenter = buildPresenter(explicitHeight: 3, scrollTop: 0, lineHeight: 1, tileOverdrawMargin: 1, tileCount: 3) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeDefined() expect(presenter.getState().content.tiles[4]).toBeDefined() - expect(presenter.getState().content.tiles[5]).toBeUndefined() + expect(presenter.getState().content.tiles[6]).toBeDefined() + expect(presenter.getState().content.tiles[8]).toBeUndefined() expectStateUpdate presenter, -> presenter.setScrollTop(2) expect(presenter.getState().content.tiles[0]).toBeUndefined() - expect(presenter.getState().content.tiles[1]).toBeDefined() - expect(presenter.getState().content.tiles[6]).toBeDefined() - expect(presenter.getState().content.tiles[7]).toBeUndefined() - - xit "updates when ::explicitHeight changes", -> - presenter = buildPresenter(explicitHeight: 3, scrollTop: 0, lineHeight: 1, tileOverdrawMargin: 1, tileCount: 3) - - line5 = editor.tokenizedLineForScreenRow(5) - + expect(presenter.getState().content.tiles[2]).toBeDefined() expect(presenter.getState().content.tiles[4]).toBeDefined() - expect(presenter.getState().content.tiles[5]).toBeUndefined() - - expectStateUpdate presenter, -> presenter.setExplicitHeight(35) - - expect(presenter.getState().content.tiles[5]).toBeDefined() expect(presenter.getState().content.tiles[6]).toBeDefined() - expect(presenter.getState().content.tiles[7]).toBeUndefined() + expect(presenter.getState().content.tiles[8]).toBeDefined() + expect(presenter.getState().content.tiles[10]).toBeUndefined() - xit "updates when ::lineHeight changes", -> - presenter = buildPresenter(explicitHeight: 15, scrollTop: 10, lineHeight: 10, lineOverdrawMargin: 0) + it "updates when ::explicitHeight changes", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) - expect(lineStateForScreenRow(presenter, 0)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 1)).toBeDefined() - expect(lineStateForScreenRow(presenter, 2)).toBeDefined() - expect(lineStateForScreenRow(presenter, 4)).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeDefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[6]).toBeDefined() + expect(presenter.getState().content.tiles[8]).toBeUndefined() - expectStateUpdate presenter, -> presenter.setLineHeight(5) + expectStateUpdate presenter, -> presenter.setExplicitHeight(8) - expect(lineStateForScreenRow(presenter, 0)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 1)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 2)).toBeDefined() - expect(lineStateForScreenRow(presenter, 5)).toBeDefined() - expect(lineStateForScreenRow(presenter, 6)).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeDefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[6]).toBeDefined() + expect(presenter.getState().content.tiles[8]).toBeDefined() + expect(presenter.getState().content.tiles[10]).toBeUndefined() + + + it "updates when ::lineHeight changes", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) + + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeDefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[6]).toBeDefined() + expect(presenter.getState().content.tiles[8]).toBeUndefined() + + expectStateUpdate presenter, -> presenter.setLineHeight(2) + + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeDefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[6]).toBeUndefined() it "updates when the editor's content changes", -> lineStateForScreenRow = (presenter, tile, row) -> @@ -788,7 +776,6 @@ describe "TextEditorPresenter", -> } xdescribe ".lines", -> - it "does not remove out-of-view lines corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", -> presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200) From 49c48234f2d094f64d6da19fd7b7bae267cbd770 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 12:26:20 +0200 Subject: [PATCH 26/93] :green_heart: Finish fixing tiles/lines specs --- spec/text-editor-presenter-spec.coffee | 76 ++++++++++++-------------- src/text-editor-presenter.coffee | 19 ++++--- 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index e0fbda5ae..7c5c9d6a3 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -658,6 +658,11 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text" describe ".tiles", -> + lineStateForScreenRow = (presenter, row) -> + lineId = presenter.model.tokenizedLineForScreenRow(row).id + tileRow = presenter.tileForRow(row) + presenter.getState().content.tiles[tileRow].lines[lineId] + it "contains states for tiles that are visible on screen", -> presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) @@ -676,7 +681,6 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[8]).toBeUndefined() - it "includes state for all tiles if no external ::explicitHeight is assigned", -> presenter = buildPresenter(explicitHeight: null, tileCount: 12, tileOverdrawMargin: 1) expect(presenter.getState().content.tiles[0]).toBeDefined() @@ -749,80 +753,68 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[6]).toBeUndefined() it "updates when the editor's content changes", -> - lineStateForScreenRow = (presenter, tile, row) -> - lineId = presenter.model.tokenizedLineForScreenRow(row).id - presenter.getState().content.tiles[tile].lines[lineId] - presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10, tileCount: 3, tileOverdrawMargin: 1) expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n") line1 = editor.tokenizedLineForScreenRow(1) - expectValues lineStateForScreenRow(presenter, 1, 1), { + expectValues lineStateForScreenRow(presenter, 1), { text: line1.text tokens: line1.tokens } line2 = editor.tokenizedLineForScreenRow(2) - expectValues lineStateForScreenRow(presenter, 2, 2), { + expectValues lineStateForScreenRow(presenter, 2), { text: line2.text tokens: line2.tokens } line3 = editor.tokenizedLineForScreenRow(3) - expectValues lineStateForScreenRow(presenter, 3, 3), { + expectValues lineStateForScreenRow(presenter, 3), { text: line3.text tokens: line3.tokens } - xdescribe ".lines", -> - it "does not remove out-of-view lines corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", -> - presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200) + it "does not remove out-of-view tiles corresponding to ::scrollingTileId until ::stoppedScrollingDelay elapses", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3, stoppedScrollingDelay: 200) - expect(lineStateForScreenRow(presenter, 0)).toBeDefined() - expect(lineStateForScreenRow(presenter, 4)).toBeDefined() - expect(lineStateForScreenRow(presenter, 5)).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[6]).toBeDefined() + expect(presenter.getState().content.tiles[8]).toBeUndefined() - presenter.setMouseWheelScreenRow(0) - expectStateUpdate presenter, -> presenter.setScrollTop(35) + presenter.setScrollingTileId(0) + expectStateUpdate presenter, -> presenter.setScrollTop(4) - expect(lineStateForScreenRow(presenter, 0)).toBeDefined() - expect(lineStateForScreenRow(presenter, 1)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 7)).toBeDefined() - expect(lineStateForScreenRow(presenter, 8)).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeUndefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[12]).toBeUndefined() expectStateUpdate presenter, -> advanceClock(200) - expect(lineStateForScreenRow(presenter, 0)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 1)).toBeUndefined() - expect(lineStateForScreenRow(presenter, 2)).toBeDefined() - expect(lineStateForScreenRow(presenter, 7)).toBeDefined() - expect(lineStateForScreenRow(presenter, 8)).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeUndefined() + expect(presenter.getState().content.tiles[2]).toBeUndefined() + expect(presenter.getState().content.tiles[4]).toBeDefined() + expect(presenter.getState().content.tiles[12]).toBeUndefined() + # should clear ::mouseWheelScreenRow after stoppedScrollingDelay elapses even if we don't scroll first - presenter.setMouseWheelScreenRow(2) + presenter.setScrollingTileId(4) advanceClock(200) - expectStateUpdate presenter, -> presenter.setScrollTop(45) - expect(lineStateForScreenRow(presenter, 2)).toBeUndefined() + expectStateUpdate presenter, -> presenter.setScrollTop(6) + expect(presenter.getState().content.tiles[4]).toBeUndefined() - it "does not preserve on-screen lines even if they correspond to ::mouseWheelScreenRow", -> - presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200) - oldLine3 = editor.tokenizedLineForScreenRow(6) + it "does not preserve deleted on-screen tiles even if they correspond to ::scrollingTileId", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3, stoppedScrollingDelay: 200) - presenter.setMouseWheelScreenRow(3) + presenter.setScrollingTileId(2) - expectStateUpdate presenter, -> editor.getBuffer().insert([3, Infinity], 'xyz') - newLine3 = editor.tokenizedLineForScreenRow(3) + expectStateUpdate presenter, -> editor.setText("") - expect(presenter.getState().content.lines[oldLine3.id]).toBeUndefined() - expect(presenter.getState().content.lines[newLine3.id]).toBeDefined() + expect(presenter.getState().content.tiles[2]).toBeUndefined() + expect(presenter.getState().content.tiles[0]).toBeDefined() - it "does not attempt to preserve lines corresponding to ::mouseWheelScreenRow if they have been deleted", -> - presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200) - presenter.setMouseWheelScreenRow(10) - editor.setText('') - - describe "[lineId]", -> # line state objects + describe "[tileId].lines[lineId]", -> # line state objects it "includes the .endOfLineInvisibles if the editor.showInvisibles config option is true", -> editor.setText("hello\nworld\r\n") presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index a8d845c72..7ca810cba 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -15,12 +15,11 @@ class TextEditorPresenter constructor: (params) -> {@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params {horizontalScrollbarHeight, verticalScrollbarWidth} = params - {@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor, @tileCount, @tileOverdrawMargin} = params + {@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor, @tileCount} = params {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileOverdrawMargin ?= 0 @tileCount ?= 3 @disposables = new CompositeDisposable @@ -306,10 +305,9 @@ class TextEditorPresenter row - (row % @tileSize) getVisibleTilesRange: -> - startTileRow = Math.max(0, @tileForRow(@startRow) - @tileOverdrawMargin) + startTileRow = Math.max(0, @tileForRow(@startRow)) endTileRow = Math.min( - @tileForRow(@model.getScreenLineCount()), - @tileForRow(@endRow) + @tileOverdrawMargin + @tileForRow(@model.getScreenLineCount()), @tileForRow(@endRow) ) [startTileRow..endTileRow] @@ -332,13 +330,15 @@ class TextEditorPresenter visibleTiles[startRow] = true + if @scrollingTileId? + if @scrollingTileId <= @tileForRow(@model.getLastScreenRow()) + visibleTiles[@scrollingTileId] = true + @state.content.tiles[@scrollingTileId].display = "none" + for id, tile of @state.content.tiles continue if visibleTiles.hasOwnProperty(id) - if id is @scrollingTileId - tile.display = "none" - else - delete @state.content.tiles[id] + delete @state.content.tiles[id] updateLinesState: (tileState, startRow, endRow) -> tileState.lines ?= {} @@ -943,6 +943,7 @@ class TextEditorPresenter @emitDidUpdateState() setScrollingTileId: (tileId) -> + tileId = tileId.toString() if @scrollingTileId isnt tileId @scrollingTileId = tileId @didStartScrolling() From 6d38d1c4e0649ec3c564f576b25ceafb5e6265e7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 12:30:44 +0200 Subject: [PATCH 27/93] :fire: Remove leftover code --- src/text-editor-presenter.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 7ca810cba..7d060f367 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -943,7 +943,6 @@ class TextEditorPresenter @emitDidUpdateState() setScrollingTileId: (tileId) -> - tileId = tileId.toString() if @scrollingTileId isnt tileId @scrollingTileId = tileId @didStartScrolling() From 467f4a30d7b8d8c4e1fe4b68f55208dac805bbc0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 12:39:32 +0200 Subject: [PATCH 28/93] :art: Simplify conditional --- src/text-editor-presenter.coffee | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 7d060f367..82c050314 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -304,6 +304,11 @@ class TextEditorPresenter tileForRow: (row) -> row - (row % @tileSize) + isValidTile: (tileId) -> + return false unless tileId? + + tileId <= @tileForRow(@model.getLastScreenRow()) + getVisibleTilesRange: -> startTileRow = Math.max(0, @tileForRow(@startRow)) endTileRow = Math.min( @@ -330,10 +335,9 @@ class TextEditorPresenter visibleTiles[startRow] = true - if @scrollingTileId? - if @scrollingTileId <= @tileForRow(@model.getLastScreenRow()) - visibleTiles[@scrollingTileId] = true - @state.content.tiles[@scrollingTileId].display = "none" + if @isValidTile(@scrollingTileId) and not visibleTiles[@scrollingTileId]? + @state.content.tiles[@scrollingTileId].display = "none" + visibleTiles[@scrollingTileId] = true for id, tile of @state.content.tiles continue if visibleTiles.hasOwnProperty(id) From 25acaf26c19bdc0414104eacda604bb900baf396 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 May 2015 12:44:06 +0200 Subject: [PATCH 29/93] :art: `tileId` -> `tileRow` --- spec/text-editor-presenter-spec.coffee | 6 +++--- src/text-editor-component.coffee | 4 ++-- src/text-editor-presenter.coffee | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 7c5c9d6a3..95f941fde 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -782,7 +782,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[6]).toBeDefined() expect(presenter.getState().content.tiles[8]).toBeUndefined() - presenter.setScrollingTileId(0) + presenter.setScrollingTileRow(0) expectStateUpdate presenter, -> presenter.setScrollTop(4) expect(presenter.getState().content.tiles[0]).toBeDefined() @@ -799,7 +799,7 @@ describe "TextEditorPresenter", -> # should clear ::mouseWheelScreenRow after stoppedScrollingDelay elapses even if we don't scroll first - presenter.setScrollingTileId(4) + presenter.setScrollingTileRow(4) advanceClock(200) expectStateUpdate presenter, -> presenter.setScrollTop(6) expect(presenter.getState().content.tiles[4]).toBeUndefined() @@ -807,7 +807,7 @@ describe "TextEditorPresenter", -> it "does not preserve deleted on-screen tiles even if they correspond to ::scrollingTileId", -> presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3, stoppedScrollingDelay: 200) - presenter.setScrollingTileId(2) + presenter.setScrollingTileRow(2) expectStateUpdate presenter, -> editor.setText("") diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 8c497bd98..c97fb11a0 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -354,7 +354,7 @@ class TextEditorComponent event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft() else # Scrolling vertically - @presenter.setScrollingTileId(@tileIdForNode(event.target)) + @presenter.setScrollingTileRow(@tileRowForNode(event.target)) previousScrollTop = @presenter.getScrollTop() @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) event.preventDefault() unless previousScrollTop is @presenter.getScrollTop() @@ -729,7 +729,7 @@ class TextEditorComponent lineNumberNodeForScreenRow: (screenRow) -> @gutterContainerComponent.getLineNumberGutterComponent().lineNumberNodeForScreenRow(screenRow) - tileIdForNode: (node) -> + tileRowForNode: (node) -> while node? if tileId = node.dataset.tileId return tileId diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 82c050314..6b38f0114 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -304,10 +304,10 @@ class TextEditorPresenter tileForRow: (row) -> row - (row % @tileSize) - isValidTile: (tileId) -> - return false unless tileId? + isValidTile: (tileRow) -> + return false unless tileRow? - tileId <= @tileForRow(@model.getLastScreenRow()) + tileRow <= @tileForRow(@model.getLastScreenRow()) getVisibleTilesRange: -> startTileRow = Math.max(0, @tileForRow(@startRow)) @@ -335,9 +335,9 @@ class TextEditorPresenter visibleTiles[startRow] = true - if @isValidTile(@scrollingTileId) and not visibleTiles[@scrollingTileId]? - @state.content.tiles[@scrollingTileId].display = "none" - visibleTiles[@scrollingTileId] = true + if @isValidTile(@scrollingTileRow) and not visibleTiles[@scrollingTileRow]? + @state.content.tiles[@scrollingTileRow].display = "none" + visibleTiles[@scrollingTileRow] = true for id, tile of @state.content.tiles continue if visibleTiles.hasOwnProperty(id) @@ -779,8 +779,8 @@ class TextEditorPresenter didStopScrolling: -> @state.content.scrollingVertically = false - if @scrollingTileId? - @scrollingTileId = null + if @scrollingTileRow? + @scrollingTileRow = null @shouldUpdateTilesState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -946,9 +946,9 @@ class TextEditorPresenter @emitDidUpdateState() - setScrollingTileId: (tileId) -> - if @scrollingTileId isnt tileId - @scrollingTileId = tileId + setScrollingTileRow: (tileRow) -> + if @scrollingTileRow isnt tileRow + @scrollingTileRow = tileRow @didStartScrolling() setBaseCharacterWidth: (baseCharacterWidth) -> From 6be88fd645a199e1b68e59e46a549f2371f69160 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 11:36:20 +0200 Subject: [PATCH 30/93] Scroll every single tile left/right --- src/lines-component.coffee | 4 ---- src/text-editor-component.coffee | 5 ++--- src/text-editor-presenter.coffee | 10 ++++++---- src/tile-component.coffee | 5 +++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index fda77a76a..c4341bbbf 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -48,10 +48,6 @@ class LinesComponent @domNode.style.height = @newState.scrollHeight + 'px' @oldState.scrollHeight = @newState.scrollHeight - if @newState.scrollLeft isnt @oldState.scrollLeft - @domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, 0, 0px)" - @oldState.scrollLeft = @newState.scrollLeft - if @newState.backgroundColor isnt @oldState.backgroundColor @domNode.style.backgroundColor = @newState.backgroundColor @oldState.backgroundColor = @newState.backgroundColor diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index c97fb11a0..ce17243e4 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -764,15 +764,14 @@ class TextEditorComponent screenPositionForMouseEvent: (event) -> pixelPosition = @pixelPositionForMouseEvent(event) - pixelPosition.top += @presenter.scrollTop @editor.screenPositionForPixelPosition(pixelPosition) pixelPositionForMouseEvent: (event) -> {clientX, clientY} = event linesClientRect = @linesComponent.getDomNode().getBoundingClientRect() - top = clientY - linesClientRect.top - left = clientX - linesClientRect.left + top = clientY - linesClientRect.top + @presenter.scrollTop + left = clientX - linesClientRect.left + @presenter.scrollLeft {top, left} getModel: -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 6b38f0114..0418fb6c2 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -327,6 +327,7 @@ class TextEditorPresenter isNewTile = not @state.content.tiles.hasOwnProperty(startRow) tile = @state.content.tiles[startRow] ?= {} tile.top = startRow * @lineHeight - @scrollTop + tile.left = -@scrollLeft tile.height = @tileSize * @lineHeight tile.newlyCreated = isNewTile tile.display = "block" @@ -410,10 +411,8 @@ class TextEditorPresenter pixelPosition = @pixelPositionForScreenPosition(screenPosition, true) - {scrollLeft} = @state.content - top = pixelPosition.top + @lineHeight - left = pixelPosition.left + @gutterWidth - scrollLeft + left = pixelPosition.left + @gutterWidth if overlayDimensions = @overlayDimensions[decoration.id] {itemWidth, itemHeight, contentMargin} = overlayDimensions @@ -795,8 +794,10 @@ class TextEditorPresenter @model.setScrollLeft(scrollLeft) @shouldUpdateHorizontalScrollState = true @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true unless oldScrollLeft? + @shouldUpdateCursorsState = true @shouldUpdateOverlaysState = true + @shouldUpdateDecorations = true + @shouldUpdateTilesState = true @emitDidUpdateState() @@ -1034,6 +1035,7 @@ class TextEditorPresenter column += charLength top -= @scrollTop + left -= @scrollLeft {top, left} hasPixelRectRequirements: -> diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 5bd299d79..197a9adce 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -43,9 +43,10 @@ class TileComponent @domNode.style.height = @newState.tiles[@id].height + 'px' @oldState.tiles[@id]?.height = @newState.tiles[@id].height - if @newState.tiles[@id].top isnt @oldState.tiles[@id]?.top - @domNode.style['-webkit-transform'] = "translate3d(0, #{@newState.tiles[@id].top}px, 0px)" + if (@newState.tiles[@id].top isnt @oldState.tiles[@id]?.top) or (@newState.tiles[@id].left isnt @oldState.tiles[@id]?.left) + @domNode.style['-webkit-transform'] = "translate3d(#{@newState.tiles[@id].left}px, #{@newState.tiles[@id].top}px, 0px)" @oldState.tiles[@id]?.top = @newState.tiles[@id].top + @oldState.tiles[@id]?.left = @newState.tiles[@id].left if @newState.tiles[@id].newlyCreated newLineIds = [] From c4dff15c736022f4129c58feb33b189901ee2a81 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 14:57:04 +0200 Subject: [PATCH 31/93] Add `@scrollLeft` when calculating `@contentWidth` --- src/text-editor-presenter.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 0418fb6c2..1caa666df 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -612,6 +612,7 @@ class TextEditorPresenter oldContentWidth = @contentWidth clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped() @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left + @contentWidth += @scrollLeft @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width if @contentHeight isnt oldContentHeight From 5b23a002cf6b01f94619153b012f6d926cf7d243 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 15:18:53 +0200 Subject: [PATCH 32/93] :green_heart: Fix position specs --- spec/text-editor-presenter-spec.coffee | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 95f941fde..a13cc2dce 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -343,26 +343,26 @@ describe "TextEditorPresenter", -> expectValues presenter.getState().hiddenInput, {top: 3 * 10, left: 6 * 10} expectStateUpdate presenter, -> presenter.setScrollTop(15) - expectValues presenter.getState().hiddenInput, {top: (3 * 10) - 15, left: 6 * 10} + expectValues presenter.getState().hiddenInput, {top: 0, left: 6 * 10} expectStateUpdate presenter, -> presenter.setScrollLeft(35) - expectValues presenter.getState().hiddenInput, {top: (3 * 10) - 15, left: (6 * 10) - 35} + expectValues presenter.getState().hiddenInput, {top: 0, left: 0} expectStateUpdate presenter, -> presenter.setScrollTop(40) - expectValues presenter.getState().hiddenInput, {top: 0, left: (6 * 10) - 35} + expectValues presenter.getState().hiddenInput, {top: 0, left: 0} expectStateUpdate presenter, -> presenter.setScrollLeft(70) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43]) - expectValues presenter.getState().hiddenInput, {top: 11 * 10 - editor.getScrollTop(), left: 43 * 10 - editor.getScrollLeft()} + expectValues presenter.getState().hiddenInput, {top: 0, left: 30} newCursor = null expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10]) - expectValues presenter.getState().hiddenInput, {top: (6 * 10) - editor.getScrollTop(), left: (10 * 10) - editor.getScrollLeft()} + expectValues presenter.getState().hiddenInput, {top: 0, left: 20} expectStateUpdate presenter, -> newCursor.destroy() - expectValues presenter.getState().hiddenInput, {top: 50 - 10, left: 300 - 10} + expectValues presenter.getState().hiddenInput, {top: 30, left: 300 - 10} expectStateUpdate presenter, -> presenter.setFocused(false) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} @@ -967,9 +967,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) expect(stateForCursor(presenter, 0)).toBeUndefined() - expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 1)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10} expect(stateForCursor(presenter, 2)).toBeUndefined() - expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10 - 20, left: 12 * 10, width: 10, height: 10} expect(stateForCursor(presenter, 4)).toBeUndefined() it "is empty until all of the required measurements are assigned", -> @@ -1005,8 +1005,8 @@ describe "TextEditorPresenter", -> expect(stateForCursor(presenter, 0)).toBeUndefined() expect(stateForCursor(presenter, 1)).toBeUndefined() expect(stateForCursor(presenter, 2)).toBeUndefined() - expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10} - expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10, left: 4 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 3)).toEqual {top: 0, left: 12 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10 - 50, left: 4 * 10, width: 10, height: 10} it "updates when ::explicitHeight changes", -> editor.setSelectedBufferRanges([ @@ -1020,9 +1020,9 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setExplicitHeight(30) expect(stateForCursor(presenter, 0)).toBeUndefined() - expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 1)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10} expect(stateForCursor(presenter, 2)).toBeUndefined() - expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10 - 20, left: 12 * 10, width: 10, height: 10} expect(stateForCursor(presenter, 4)).toBeUndefined() it "updates when ::lineHeight changes", -> @@ -1039,15 +1039,15 @@ describe "TextEditorPresenter", -> expect(stateForCursor(presenter, 0)).toBeUndefined() expect(stateForCursor(presenter, 1)).toBeUndefined() expect(stateForCursor(presenter, 2)).toBeUndefined() - expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 5, left: 12 * 10, width: 10, height: 5} - expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5, left: 4 * 10, width: 10, height: 5} + expect(stateForCursor(presenter, 3)).toEqual {top: 5, left: 12 * 10, width: 10, height: 5} + expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5 - 20, left: 4 * 10, width: 10, height: 5} it "updates when ::baseCharacterWidth changes", -> editor.setCursorBufferPosition([2, 4]) presenter = buildPresenter(explicitHeight: 20, scrollTop: 20) expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20) - expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 20, width: 20, height: 10} + expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 20, width: 20, height: 10} it "updates when scoped character widths change", -> waitsForPromise -> @@ -1073,11 +1073,11 @@ describe "TextEditorPresenter", -> # moving into view expect(stateForCursor(presenter, 0)).toBeUndefined() editor.getCursors()[0].setBufferPosition([2, 4]) - expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10} # showing expectStateUpdate presenter, -> editor.getSelections()[1].clear() - expect(stateForCursor(presenter, 1)).toEqual {top: 3 * 10, left: 5 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 1)).toEqual {top: 5, left: 5 * 10, width: 10, height: 10} # hiding expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 5]]) @@ -1089,11 +1089,11 @@ describe "TextEditorPresenter", -> # adding expectStateUpdate presenter, -> editor.addCursorAtBufferPosition([4, 4]) - expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 4 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 4 * 10, width: 10, height: 10} # moving added cursor expectStateUpdate presenter, -> editor.getCursors()[2].setBufferPosition([4, 6]) - expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 6 * 10, width: 10, height: 10} + expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 6 * 10, width: 10, height: 10} # destroying destroyedCursor = editor.getCursors()[2] @@ -1211,39 +1211,39 @@ describe "TextEditorPresenter", -> expectValues stateForHighlight(presenter, highlight2), { class: 'b' regions: [ - {top: 2 * 10, left: 0 * 10, width: 6 * 10, height: 1 * 10} + {top: 2 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10} ] } expectValues stateForHighlight(presenter, highlight3), { class: 'c' regions: [ - {top: 2 * 10, left: 0 * 10, right: 0, height: 1 * 10} - {top: 3 * 10, left: 0 * 10, width: 6 * 10, height: 1 * 10} + {top: 2 * 10 - 20, left: 0 * 10, right: 0, height: 1 * 10} + {top: 3 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10} ] } expectValues stateForHighlight(presenter, highlight4), { class: 'd' regions: [ - {top: 2 * 10, left: 6 * 10, right: 0, height: 1 * 10} - {top: 3 * 10, left: 0, right: 0, height: 1 * 10} - {top: 4 * 10, left: 0, width: 6 * 10, height: 1 * 10} + {top: 2 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10} + {top: 3 * 10 - 20, left: 0, right: 0, height: 1 * 10} + {top: 4 * 10 - 20, left: 0, width: 6 * 10, height: 1 * 10} ] } expectValues stateForHighlight(presenter, highlight5), { class: 'e' regions: [ - {top: 3 * 10, left: 6 * 10, right: 0, height: 1 * 10} - {top: 4 * 10, left: 0 * 10, right: 0, height: 2 * 10} + {top: 3 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10} + {top: 4 * 10 - 20, left: 0 * 10, right: 0, height: 2 * 10} ] } expectValues stateForHighlight(presenter, highlight6), { class: 'f' regions: [ - {top: 5 * 10, left: 6 * 10, right: 0, height: 1 * 10} + {top: 5 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10} ] } From e7ddb3d8ad61c3ba34dcc4b15cd38890bacf58db Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 15:54:09 +0200 Subject: [PATCH 33/93] :green_heart: Fix all TextEditorPresenter specs --- spec/text-editor-presenter-spec.coffee | 76 +++++++++++++------------- src/text-editor-component.coffee | 9 ++- src/text-editor-presenter.coffee | 24 ++++---- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index a13cc2dce..8d5a4c0a8 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -39,7 +39,6 @@ describe "TextEditorPresenter", -> verticalScrollbarWidth: 10 scrollTop: 0 scrollLeft: 0 - lineOverdrawMargin: 0 new TextEditorPresenter(params) @@ -775,14 +774,14 @@ describe "TextEditorPresenter", -> tokens: line3.tokens } - it "does not remove out-of-view tiles corresponding to ::scrollingTileId until ::stoppedScrollingDelay elapses", -> + it "does not remove out-of-view tiles corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", -> presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3, stoppedScrollingDelay: 200) expect(presenter.getState().content.tiles[0]).toBeDefined() expect(presenter.getState().content.tiles[6]).toBeDefined() expect(presenter.getState().content.tiles[8]).toBeUndefined() - presenter.setScrollingTileRow(0) + presenter.setMouseWheelScreenRow(0) expectStateUpdate presenter, -> presenter.setScrollTop(4) expect(presenter.getState().content.tiles[0]).toBeDefined() @@ -799,15 +798,15 @@ describe "TextEditorPresenter", -> # should clear ::mouseWheelScreenRow after stoppedScrollingDelay elapses even if we don't scroll first - presenter.setScrollingTileRow(4) + presenter.setMouseWheelScreenRow(4) advanceClock(200) expectStateUpdate presenter, -> presenter.setScrollTop(6) expect(presenter.getState().content.tiles[4]).toBeUndefined() - it "does not preserve deleted on-screen tiles even if they correspond to ::scrollingTileId", -> + it "does not preserve deleted on-screen tiles even if they correspond to ::mouseWheelScreenRow", -> presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3, stoppedScrollingDelay: 200) - presenter.setScrollingTileRow(2) + presenter.setMouseWheelScreenRow(2) expectStateUpdate presenter, -> editor.setText("") @@ -1750,20 +1749,19 @@ describe "TextEditorPresenter", -> presenter.getState().gutters.lineNumberGutter.lineNumbers[key] - it "contains states for line numbers that are visible on screen, plus and minus the overdraw margin", -> + it "contains states for line numbers that are visible on screen", -> editor.foldBufferRow(4) editor.setSoftWrapped(true) editor.setEditorWidthInChars(50) - presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, lineOverdrawMargin: 1) + presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10) expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() - expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false, top: 2 * 10} + expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined() expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false, top: 3 * 10} expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true, top: 4 * 10} expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false, top: 5 * 10} expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false, top: 6 * 10} - expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false, top: 7 * 10} - expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() it "includes states for all line numbers if no ::explicitHeight is assigned", -> presenter = buildPresenter(explicitHeight: null) @@ -1774,43 +1772,43 @@ describe "TextEditorPresenter", -> editor.foldBufferRow(4) editor.setSoftWrapped(true) editor.setEditorWidthInChars(50) - presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineOverdrawMargin: 1) + presenter = buildPresenter(explicitHeight: 25, scrollTop: 30) - expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() - expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2} - expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8} - expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined() + expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7} + expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() expectStateUpdate presenter, -> presenter.setScrollTop(20) - expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined() - expectValues lineNumberStateForScreenRow(presenter, 1), {bufferRow: 1} - expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7} - expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() + expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2} + expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 4} + expect(lineNumberStateForScreenRow(presenter, 6)).toBeUndefined() it "updates when ::explicitHeight changes", -> editor.foldBufferRow(4) editor.setSoftWrapped(true) editor.setEditorWidthInChars(50) - presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineOverdrawMargin: 1) + presenter = buildPresenter(explicitHeight: 25, scrollTop: 30) - expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() - expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2} - expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8} - expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined() + expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7} + expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() expectStateUpdate presenter, -> presenter.setExplicitHeight(35) - expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined() - expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2} - expectValues lineNumberStateForScreenRow(presenter, 8), {bufferRow: 8} - expect(lineNumberStateForScreenRow(presenter, 9)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined() + expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8} + expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() it "updates when ::lineHeight changes", -> editor.foldBufferRow(4) editor.setSoftWrapped(true) editor.setEditorWidthInChars(50) - presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineOverdrawMargin: 0) + presenter = buildPresenter(explicitHeight: 25, scrollTop: 0) expectValues lineNumberStateForScreenRow(presenter, 0), {bufferRow: 0} expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} @@ -1826,7 +1824,7 @@ describe "TextEditorPresenter", -> editor.foldBufferRow(4) editor.setSoftWrapped(true) editor.setEditorWidthInChars(50) - presenter = buildPresenter(explicitHeight: 35, scrollTop: 30, lineOverdrawMargin: 0) + presenter = buildPresenter(explicitHeight: 35, scrollTop: 30) expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined() expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} @@ -1848,26 +1846,26 @@ describe "TextEditorPresenter", -> expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() it "does not remove out-of-view line numbers corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", -> - presenter = buildPresenter(explicitHeight: 25, lineOverdrawMargin: 1, stoppedScrollingDelay: 200) + presenter = buildPresenter(explicitHeight: 25, stoppedScrollingDelay: 200) expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined() - expect(lineNumberStateForScreenRow(presenter, 4)).toBeDefined() - expect(lineNumberStateForScreenRow(presenter, 5)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 3)).toBeDefined() + expect(lineNumberStateForScreenRow(presenter, 4)).toBeUndefined() presenter.setMouseWheelScreenRow(0) expectStateUpdate presenter, -> presenter.setScrollTop(35) expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined() expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() - expect(lineNumberStateForScreenRow(presenter, 7)).toBeDefined() - expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 6)).toBeDefined() + expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() expectStateUpdate presenter, -> advanceClock(200) expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined() expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() - expect(lineNumberStateForScreenRow(presenter, 7)).toBeDefined() - expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() + expect(lineNumberStateForScreenRow(presenter, 6)).toBeDefined() + expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() it "correctly handles the first screen line being soft-wrapped", -> editor.setSoftWrapped(true) @@ -2481,7 +2479,7 @@ describe "TextEditorPresenter", -> editor.setEditorWidthInChars(80) presenterParams = model: editor - lineOverdrawMargin: 1 + presenter = new TextEditorPresenter(presenterParams) statements = [] diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index ce17243e4..703d4141e 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -354,7 +354,7 @@ class TextEditorComponent event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft() else # Scrolling vertically - @presenter.setScrollingTileRow(@tileRowForNode(event.target)) + @presenter.setMouseWheelScreenRow(@screenRowForNode(event.target)) previousScrollTop = @presenter.getScrollTop() @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) event.preventDefault() unless previousScrollTop is @presenter.getScrollTop() @@ -729,12 +729,11 @@ class TextEditorComponent lineNumberNodeForScreenRow: (screenRow) -> @gutterContainerComponent.getLineNumberGutterComponent().lineNumberNodeForScreenRow(screenRow) - tileRowForNode: (node) -> + screenRowForNode: (node) -> while node? - if tileId = node.dataset.tileId - return tileId + if screenRow = node.dataset.screenRow + return parseInt(screenRow) node = node.parentElement - null getFontSize: -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 1caa666df..4944c4842 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -304,11 +304,6 @@ class TextEditorPresenter tileForRow: (row) -> row - (row % @tileSize) - isValidTile: (tileRow) -> - return false unless tileRow? - - tileRow <= @tileForRow(@model.getLastScreenRow()) - getVisibleTilesRange: -> startTileRow = Math.max(0, @tileForRow(@startRow)) endTileRow = Math.min( @@ -336,9 +331,12 @@ class TextEditorPresenter visibleTiles[startRow] = true - if @isValidTile(@scrollingTileRow) and not visibleTiles[@scrollingTileRow]? - @state.content.tiles[@scrollingTileRow].display = "none" - visibleTiles[@scrollingTileRow] = true + if @mouseWheelScreenRow? and @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)? + mouseWheelTile = @tileForRow(@mouseWheelScreenRow) + + unless visibleTiles[mouseWheelTile]? + @state.content.tiles[mouseWheelTile].display = "none" + visibleTiles[mouseWheelTile] = true for id, tile of @state.content.tiles continue if visibleTiles.hasOwnProperty(id) @@ -779,8 +777,8 @@ class TextEditorPresenter didStopScrolling: -> @state.content.scrollingVertically = false - if @scrollingTileRow? - @scrollingTileRow = null + if @mouseWheelScreenRow? + @mouseWheelScreenRow = null @shouldUpdateTilesState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -948,9 +946,9 @@ class TextEditorPresenter @emitDidUpdateState() - setScrollingTileRow: (tileRow) -> - if @scrollingTileRow isnt tileRow - @scrollingTileRow = tileRow + setMouseWheelScreenRow: (screenRow) -> + if @mouseWheelScreenRow isnt screenRow + @mouseWheelScreenRow = screenRow @didStartScrolling() setBaseCharacterWidth: (baseCharacterWidth) -> From 723bf5a302424383a4433e7dbd8606b3471b0454 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 18:30:29 +0200 Subject: [PATCH 34/93] Reimplement `lineNodeForScreenRow` --- src/lines-component.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index c4341bbbf..663dd9a31 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -131,3 +131,8 @@ class LinesComponent component.clearMeasurements() @presenter.clearScopedCharacterWidths() + + lineNodeForScreenRow: (screenRow) -> + tile = @presenter.tileForRow(screenRow) + + @tileComponentsByTileId[tile].lineNodeForScreenRow(screenRow) From 26b7c5e58e26529ce2dcdda8f9606f3379d943c9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 18:40:44 +0200 Subject: [PATCH 35/93] :fire: Get rid of unused `tileId` property --- src/tile-component.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 197a9adce..a68eb58e5 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -21,7 +21,6 @@ class TileComponent @screenRowsByLineId = {} @lineIdsByScreenRow = {} @domNode ?= document.createElement("div") - @domNode.dataset.tileId = @id @domNode.style.position = "absolute" @domNode.style.display = "block" @domNode.classList.add("tile") From 265090e7bba546175607a0fd74d944a5f0700738 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 18:47:51 +0200 Subject: [PATCH 36/93] Store tile state in a instance variable --- src/tile-component.coffee | 57 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/tile-component.coffee b/src/tile-component.coffee index a68eb58e5..dc0324e18 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -34,29 +34,32 @@ class TileComponent @oldState = {tiles: {}} @oldState.tiles[@id] = { lines: {}} - if @newState.tiles[@id].display isnt @oldState.tiles[@id]?.display - @domNode.style.display = @newState.tiles[@id].display - @oldState.tiles[@id]?.display = @newState.tiles[@id].display + @newTileState = @newState.tiles[@id] + @oldTileState = @oldState.tiles[@id] - if @newState.tiles[@id].height isnt @oldState.tiles[@id]?.height - @domNode.style.height = @newState.tiles[@id].height + 'px' - @oldState.tiles[@id]?.height = @newState.tiles[@id].height + if @newTileState.display isnt @oldTileState.display + @domNode.style.display = @newTileState.display + @oldTileState.display = @newTileState.display - if (@newState.tiles[@id].top isnt @oldState.tiles[@id]?.top) or (@newState.tiles[@id].left isnt @oldState.tiles[@id]?.left) - @domNode.style['-webkit-transform'] = "translate3d(#{@newState.tiles[@id].left}px, #{@newState.tiles[@id].top}px, 0px)" - @oldState.tiles[@id]?.top = @newState.tiles[@id].top - @oldState.tiles[@id]?.left = @newState.tiles[@id].left + if @newTileState.height isnt @oldTileState.height + @domNode.style.height = @newTileState.height + 'px' + @oldTileState.height = @newTileState.height - if @newState.tiles[@id].newlyCreated + if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left + @domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)" + @oldTileState.top = @newTileState.top + @oldTileState.left = @newTileState.left + + if @newTileState.newlyCreated newLineIds = [] newLinesHTML = "" - for id, lineState of @newState.tiles[@id].lines + for id, lineState of @newTileState.lines newLineIds.push(id) newLinesHTML += @buildLineHTML(id) @screenRowsByLineId[id] = lineState.screenRow @lineIdsByScreenRow[lineState.screenRow] = id - @oldState.tiles[@id]?.lines[id] = cloneObject(lineState) + @oldTileState.lines[id] = cloneObject(lineState) return if newLineIds.length is 0 @@ -77,7 +80,7 @@ class TileComponent @oldState.scrollWidth = @newState.scrollWidth removeLineNodes: -> - @removeLineNode(id) for id of @oldState.tiles[@id]?.lines + @removeLineNode(id) for id of @oldTileState.lines return removeLineNode: (id) -> @@ -85,18 +88,18 @@ class TileComponent delete @lineNodesByLineId[id] delete @lineIdsByScreenRow[@screenRowsByLineId[id]] delete @screenRowsByLineId[id] - delete @oldState.tiles[@id]?.lines[id] + delete @oldTileState.lines[id] updateLineNodes: -> - for id of @oldState.tiles[@id]?.lines - unless @newState.tiles[@id].lines.hasOwnProperty(id) + for id of @oldTileState.lines + unless @newTileState.lines.hasOwnProperty(id) @removeLineNode(id) newLineIds = null newLinesHTML = null - for id, lineState of @newState.tiles[@id].lines - if @oldState.tiles[@id]?.lines.hasOwnProperty(id) + for id, lineState of @newTileState.lines + if @oldTileState.lines.hasOwnProperty(id) @updateLineNode(id) else newLineIds ?= [] @@ -105,7 +108,7 @@ class TileComponent newLinesHTML += @buildLineHTML(id) @screenRowsByLineId[id] = lineState.screenRow @lineIdsByScreenRow[lineState.screenRow] = id - @oldState.tiles[@id]?.lines[id] = cloneObject(lineState) + @oldTileState.lines[id] = cloneObject(lineState) return unless newLineIds? @@ -120,7 +123,7 @@ class TileComponent buildLineHTML: (id) -> {scrollWidth} = @newState - {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.tiles[@id].lines[id] + {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id] classes = '' if decorationClasses? @@ -141,7 +144,7 @@ class TileComponent buildEmptyLineInnerHTML: (id) -> {indentGuidesVisible} = @newState - {indentLevel, tabLength, endOfLineInvisibles} = @newState.tiles[@id].lines[id] + {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id] if indentGuidesVisible and indentLevel > 0 invisibleIndex = 0 @@ -164,7 +167,7 @@ class TileComponent buildLineInnerHTML: (id) -> {indentGuidesVisible} = @newState - {tokens, text, isOnlyWhitespace} = @newState.tiles[@id].lines[id] + {tokens, text, isOnlyWhitespace} = @newTileState.lines[id] innerHTML = "" scopeStack = [] @@ -178,7 +181,7 @@ class TileComponent innerHTML buildEndOfLineHTML: (id) -> - {endOfLineInvisibles} = @newState.tiles[@id].lines[id] + {endOfLineInvisibles} = @newTileState.lines[id] html = '' if endOfLineInvisibles? @@ -212,8 +215,8 @@ class TileComponent "" updateLineNode: (id) -> - oldLineState = @oldState.tiles[@id]?.lines[id] - newLineState = @newState.tiles[@id].lines[id] + oldLineState = @oldTileState.lines[id] + newLineState = @newTileState.lines[id] lineNode = @lineNodesByLineId[id] @@ -248,7 +251,7 @@ class TileComponent @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] measureCharactersInNewLines: -> - for id, lineState of @oldState.tiles[@id]?.lines + for id, lineState of @oldTileState.lines unless @measuredLines.has(id) lineNode = @lineNodesByLineId[id] @measureCharactersInLine(id, lineState, lineNode) From 5201e4547b3db895c2cc98b502c25d998344c878 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 May 2015 19:08:07 +0200 Subject: [PATCH 37/93] :art: --- src/lines-component.coffee | 8 +++----- src/text-editor-presenter.coffee | 2 -- src/tile-component.coffee | 23 ++--------------------- 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 663dd9a31..6a49f16be 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -80,8 +80,7 @@ class LinesComponent removeTileNode: (id) -> node = @tileComponentsByTileId[id].getDomNode() - node.style.display = "none" - @freeDomNodes.push(node) + node.remove() delete @tileComponentsByTileId[id] delete @oldState.tiles[id] @@ -94,10 +93,9 @@ class LinesComponent if @oldState.tiles.hasOwnProperty(id) tileComponent = @tileComponentsByTileId[id] else - domNode = @freeDomNodes.pop() - tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter, domNode}) + tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter}) - @domNode.appendChild(tileComponent.getDomNode()) unless domNode? + @domNode.appendChild(tileComponent.getDomNode()) @oldState.tiles[id] = cloneObject(tileState) tileComponent.updateSync(@newState) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 4944c4842..48a410faa 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -319,12 +319,10 @@ class TextEditorPresenter for startRow in @getVisibleTilesRange() by @tileSize endRow = Math.min(@model.getScreenLineCount(), startRow + @tileSize) - isNewTile = not @state.content.tiles.hasOwnProperty(startRow) tile = @state.content.tiles[startRow] ?= {} tile.top = startRow * @lineHeight - @scrollTop tile.left = -@scrollLeft tile.height = @tileSize * @lineHeight - tile.newlyCreated = isNewTile tile.display = "block" @updateLinesState(tile, startRow, endRow) diff --git a/src/tile-component.coffee b/src/tile-component.coffee index dc0324e18..c31c4dcb6 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -50,27 +50,8 @@ class TileComponent @oldTileState.top = @newTileState.top @oldTileState.left = @newTileState.left - if @newTileState.newlyCreated - newLineIds = [] - newLinesHTML = "" - - for id, lineState of @newTileState.lines - newLineIds.push(id) - newLinesHTML += @buildLineHTML(id) - @screenRowsByLineId[id] = lineState.screenRow - @lineIdsByScreenRow[lineState.screenRow] = id - @oldTileState.lines[id] = cloneObject(lineState) - - return if newLineIds.length is 0 - - @domNode.innerHTML = newLinesHTML - newLineNodes = _.toArray(@domNode.children) - for id, i in newLineIds - lineNode = newLineNodes[i] - @lineNodesByLineId[id] = lineNode - else - @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible - @updateLineNodes() + @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible + @updateLineNodes() if @newState.scrollWidth isnt @oldState.scrollWidth @domNode.style.width = @newState.scrollWidth + 'px' From dbc57abeb0f391119b013fb3ccd6d48712dbca1a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 May 2015 11:50:13 +0200 Subject: [PATCH 38/93] :fire: Remove obsolete node pool --- src/lines-component.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 6a49f16be..d99fbfbe1 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -21,7 +21,6 @@ class LinesComponent constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> @tileComponentsByTileId = {} - @freeDomNodes = [] @domNode = document.createElement('div') @domNode.classList.add('lines') From d116a33202cd43e72da1aec0f8327d880290a805 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 May 2015 11:56:41 +0200 Subject: [PATCH 39/93] :fire: --- spec/text-editor-presenter-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 0e6632e52..889328386 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -5,7 +5,7 @@ TextBuffer = require 'text-buffer' TextEditor = require '../src/text-editor' TextEditorPresenter = require '../src/text-editor-presenter' -fdescribe "TextEditorPresenter", -> +describe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. # Please maintain this structure when adding specs for new state fields. describe "::getState()", -> From dc6938ae377a837249a23260902cf860101537e4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 May 2015 14:48:25 +0200 Subject: [PATCH 40/93] :art: --- src/tile-component.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tile-component.coffee b/src/tile-component.coffee index c31c4dcb6..868653d85 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -32,7 +32,7 @@ class TileComponent @newState = state unless @oldState @oldState = {tiles: {}} - @oldState.tiles[@id] = { lines: {}} + @oldState.tiles[@id] = {lines: {}} @newTileState = @newState.tiles[@id] @oldTileState = @oldState.tiles[@id] From ba0df9fecb64ef93a7967575334fee970827ef8f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 May 2015 14:55:23 +0200 Subject: [PATCH 41/93] Avoid allocating an extra array --- src/text-editor-presenter.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index d65eada41..a95d4e9fd 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -306,19 +306,19 @@ class TextEditorPresenter tileForRow: (row) -> row - (row % @tileSize) - getVisibleTilesRange: -> - startTileRow = Math.max(0, @tileForRow(@startRow)) - endTileRow = Math.min( + getStartTileRow: -> + Math.max(0, @tileForRow(@startRow)) + + getEndTileRow: -> + Math.min( @tileForRow(@model.getScreenLineCount()), @tileForRow(@endRow) ) - [startTileRow..endTileRow] - updateTilesState: -> return unless @startRow? and @endRow? and @lineHeight? visibleTiles = {} - for startRow in @getVisibleTilesRange() by @tileSize + for startRow in [@getStartTileRow()..@getEndTileRow()] by @tileSize endRow = Math.min(@model.getScreenLineCount(), startRow + @tileSize) tile = @state.content.tiles[startRow] ?= {} From b9138967e185f238c26cbf0ff4c79a6e93c18c09 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 May 2015 09:24:44 +0200 Subject: [PATCH 42/93] :art: --- src/tile-component.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 868653d85..0f8fd0209 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -15,15 +15,14 @@ module.exports = class TileComponent placeholderTextDiv: null - constructor: ({@presenter, @id, @domNode}) -> + constructor: ({@presenter, @id}) -> @measuredLines = new Set @lineNodesByLineId = {} @screenRowsByLineId = {} @lineIdsByScreenRow = {} - @domNode ?= document.createElement("div") + @domNode = document.createElement("div") @domNode.style.position = "absolute" @domNode.style.display = "block" - @domNode.classList.add("tile") getDomNode: -> @domNode From 90b1228622d3052efdadf328148380c761606f07 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 May 2015 10:26:44 +0200 Subject: [PATCH 43/93] :racehorse: Bump tileCount to 6 --- src/text-editor-presenter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index a95d4e9fd..fae99f209 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -20,7 +20,7 @@ class TextEditorPresenter @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileCount ?= 3 + @tileCount ?= 6 @disposables = new CompositeDisposable @emitter = new Emitter From 9fee62d884b41dc3d52b4178651a294e56cff060 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 May 2015 19:01:11 +0200 Subject: [PATCH 44/93] :fire: Remove comment --- src/text-editor-presenter.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index fae99f209..a86d5afc1 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -577,7 +577,6 @@ class TextEditorPresenter @lineNumberGutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} visibleLineNumberIds[id] = true - # FIXME: We should either rely on @mouseWheelScreenRow or convert this to use tiles. if @mouseWheelScreenRow? bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow) wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow) From ffc73f3b7bf879e547b003bbf20f2aa7f95f3f18 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 May 2015 12:07:12 +0200 Subject: [PATCH 45/93] Reduce tile count to 4 --- src/text-editor-presenter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index a86d5afc1..00a884eb4 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -20,7 +20,7 @@ class TextEditorPresenter @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileCount ?= 6 + @tileCount ?= 4 @disposables = new CompositeDisposable @emitter = new Emitter From 752dbf2c6eac831771c705ced0a3c6f542b5272a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 May 2015 18:55:47 +0200 Subject: [PATCH 46/93] Manually config tileSize Dealing with a manually entered `tileSize` is actually easier to reason about, therefore we no longer calculate it based on `tileCount.` --- spec/text-editor-component-spec.coffee | 18 ++++++++++++---- spec/text-editor-presenter-spec.coffee | 30 +++++++++++++------------- src/text-editor-component.coffee | 8 +++---- src/text-editor-element.coffee | 4 ++-- src/text-editor-presenter.coffee | 14 ++---------- src/text-editor-view.coffee | 2 +- src/tile-component.coffee | 1 + 7 files changed, 39 insertions(+), 38 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 12d189035..d9a577c25 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1920,13 +1920,23 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - linesNode = componentNode.querySelector('.lines') - expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" + tilesNodes = componentNode.querySelectorAll(".tile") + + top = 0 + for tileNode in tilesNodes + expect(tileNode.style['-webkit-transform']).toBe "translate3d(0px, #{top}px, 0px)" + top += tileNode.offsetHeight + expect(horizontalScrollbarNode.scrollLeft).toBe 0 editor.setScrollLeft(100) nextAnimationFrame() - expect(linesNode.style['-webkit-transform']).toBe "translate3d(-100px, 0px, 0px)" + + top = 0 + for tileNode in tilesNodes + expect(tileNode.style['-webkit-transform']).toBe "translate3d(-100px, #{top}px, 0px)" + top += tileNode.offsetHeight + expect(horizontalScrollbarNode.scrollLeft).toBe 100 it "updates the scrollLeft of the model when the scrollLeft of the horizontal scrollbar changes", -> @@ -2508,7 +2518,7 @@ describe "TextEditorComponent", -> advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() - expect(componentNode.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1) + expect(componentNode.querySelectorAll('.line')).toHaveLength(6) gutterWidth = componentNode.querySelector('.gutter').offsetWidth componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px' diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 889328386..c147fc4e1 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -342,26 +342,26 @@ describe "TextEditorPresenter", -> expectValues presenter.getState().hiddenInput, {top: 3 * 10, left: 6 * 10} expectStateUpdate presenter, -> presenter.setScrollTop(15) - expectValues presenter.getState().hiddenInput, {top: 0, left: 6 * 10} + expectValues presenter.getState().hiddenInput, {top: (3 * 10) - 15, left: 6 * 10} expectStateUpdate presenter, -> presenter.setScrollLeft(35) - expectValues presenter.getState().hiddenInput, {top: 0, left: 0} + expectValues presenter.getState().hiddenInput, {top: (3 * 10) - 15, left: (6 * 10) - 35} expectStateUpdate presenter, -> presenter.setScrollTop(40) - expectValues presenter.getState().hiddenInput, {top: 0, left: 0} + expectValues presenter.getState().hiddenInput, {top: 0, left: (6 * 10) - 35} expectStateUpdate presenter, -> presenter.setScrollLeft(70) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43]) - expectValues presenter.getState().hiddenInput, {top: 0, left: 30} + expectValues presenter.getState().hiddenInput, {top: 11 * 10 - editor.getScrollTop(), left: 43 * 10 - editor.getScrollLeft()} newCursor = null expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10]) - expectValues presenter.getState().hiddenInput, {top: 0, left: 20} + expectValues presenter.getState().hiddenInput, {top: (6 * 10) - editor.getScrollTop(), left: (10 * 10) - editor.getScrollLeft()} expectStateUpdate presenter, -> newCursor.destroy() - expectValues presenter.getState().hiddenInput, {top: 30, left: 300 - 10} + expectValues presenter.getState().hiddenInput, {top: 50 - 10, left: 300 - 10} expectStateUpdate presenter, -> presenter.setFocused(false) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} @@ -663,7 +663,7 @@ describe "TextEditorPresenter", -> presenter.getState().content.tiles[tileRow].lines[lineId] it "contains states for tiles that are visible on screen", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) expectValues presenter.getState().content.tiles[0], { top: 0 @@ -681,12 +681,12 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[8]).toBeUndefined() it "includes state for all tiles if no external ::explicitHeight is assigned", -> - presenter = buildPresenter(explicitHeight: null, tileCount: 12, tileOverdrawMargin: 1) + presenter = buildPresenter(explicitHeight: null, tileSize: 2) expect(presenter.getState().content.tiles[0]).toBeDefined() expect(presenter.getState().content.tiles[12]).toBeDefined() it "is empty until all of the required measurements are assigned", -> - presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, tileCount: 3) + presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null) expect(presenter.getState().content.tiles).toEqual({}) presenter.setExplicitHeight(25) @@ -699,7 +699,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles).not.toEqual({}) it "updates when ::scrollTop changes", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) expect(presenter.getState().content.tiles[0]).toBeDefined() expect(presenter.getState().content.tiles[2]).toBeDefined() @@ -717,7 +717,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[10]).toBeUndefined() it "updates when ::explicitHeight changes", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) expect(presenter.getState().content.tiles[0]).toBeDefined() expect(presenter.getState().content.tiles[2]).toBeDefined() @@ -736,7 +736,7 @@ describe "TextEditorPresenter", -> it "updates when ::lineHeight changes", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) expect(presenter.getState().content.tiles[0]).toBeDefined() expect(presenter.getState().content.tiles[2]).toBeDefined() @@ -752,7 +752,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[6]).toBeUndefined() it "updates when the editor's content changes", -> - presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10, tileCount: 3, tileOverdrawMargin: 1) + presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10, tileSize: 2) expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n") @@ -775,7 +775,7 @@ describe "TextEditorPresenter", -> } it "does not remove out-of-view tiles corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3, stoppedScrollingDelay: 200) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200) expect(presenter.getState().content.tiles[0]).toBeDefined() expect(presenter.getState().content.tiles[6]).toBeDefined() @@ -804,7 +804,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[4]).toBeUndefined() it "does not preserve deleted on-screen tiles even if they correspond to ::mouseWheelScreenRow", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileCount: 3, stoppedScrollingDelay: 200) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200) presenter.setMouseWheelScreenRow(2) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index fb840f534..f8f6de6bf 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -18,7 +18,7 @@ class TextEditorComponent scrollSensitivity: 0.4 cursorBlinkPeriod: 800 cursorBlinkResumeDelay: 100 - lineOverdrawMargin: 15 + tileSize: 12 pendingScrollTop: null pendingScrollLeft: null @@ -36,8 +36,8 @@ class TextEditorComponent gutterComponent: null mounted: true - constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, lineOverdrawMargin}) -> - @lineOverdrawMargin = lineOverdrawMargin if lineOverdrawMargin? + constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize}) -> + @tileSize = tileSize if tileSize? @disposables = new CompositeDisposable @observeConfig() @@ -47,7 +47,7 @@ class TextEditorComponent model: @editor scrollTop: @editor.getScrollTop() scrollLeft: @editor.getScrollLeft() - lineOverdrawMargin: lineOverdrawMargin + tileSize: tileSize cursorBlinkPeriod: @cursorBlinkPeriod cursorBlinkResumeDelay: @cursorBlinkResumeDelay stoppedScrollingDelay: 200 diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index ab18be8bf..bc1dc05d6 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -15,7 +15,7 @@ class TextEditorElement extends HTMLElement componentDescriptor: null component: null attached: false - lineOverdrawMargin: null + tileSize: null focusOnAttach: false createdCallback: -> @@ -110,7 +110,7 @@ class TextEditorElement extends HTMLElement rootElement: @rootElement stylesElement: @stylesElement editor: @model - lineOverdrawMargin: @lineOverdrawMargin + tileSize: @tileSize useShadowDOM: @useShadowDOM ) @rootElement.appendChild(@component.getDomNode()) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 00a884eb4..4db15ade4 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -15,12 +15,12 @@ class TextEditorPresenter constructor: (params) -> {@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params {horizontalScrollbarHeight, verticalScrollbarWidth} = params - {@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor, @tileCount} = params + {@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 - @tileCount ?= 4 + @tileSize ?= 12 @disposables = new CompositeDisposable @emitter = new Emitter @@ -66,7 +66,6 @@ class TextEditorPresenter @updateScrollbarDimensions() @updateStartRow() @updateEndRow() - @updateTileSize() @updateFocusedState() if @shouldUpdateFocusedState @updateHeightState() if @shouldUpdateHeightState @@ -224,7 +223,6 @@ class TextEditorPresenter @updateScrollbarDimensions() @updateStartRow() @updateEndRow() - @updateTileSize() @updateFocusedState() @updateHeightState() @@ -285,8 +283,6 @@ class TextEditorPresenter {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange()) if @focused - top -= @scrollTop - left -= @scrollLeft @state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0) @state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0) else @@ -603,12 +599,6 @@ class TextEditorPresenter endRow = startRow + visibleLinesCount @endRow = Math.min(@model.getScreenLineCount(), endRow) - updateTileSize: -> - return unless @height? and @lineHeight? and @tileCount? - - @tileSize = Math.floor(@height / @lineHeight / @tileCount) - @tileSize = Math.max(1, @tileSize) - updateScrollWidth: -> return unless @contentWidth? and @clientWidth? diff --git a/src/text-editor-view.coffee b/src/text-editor-view.coffee index b86367ef4..ffb59cc66 100644 --- a/src/text-editor-view.coffee +++ b/src/text-editor-view.coffee @@ -60,7 +60,7 @@ class TextEditorView extends View placeholderText: placeholderText element = new TextEditorElement - element.lineOverdrawMargin = props?.lineOverdrawMargin + element.tileSize = props?.tileSize element.setAttribute(name, value) for name, value of attributes if attributes? element.setModel(model) return element.__spacePenView diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 0f8fd0209..20c9d7ff1 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -21,6 +21,7 @@ class TileComponent @screenRowsByLineId = {} @lineIdsByScreenRow = {} @domNode = document.createElement("div") + @domNode.classList.add("tile") @domNode.style.position = "absolute" @domNode.style.display = "block" From 1a18cda000a7651e63b1857c93deac7f1d52c7c0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 May 2015 20:54:11 +0200 Subject: [PATCH 47/93] :green_heart: Fix remaining specs --- spec/text-editor-component-spec.coffee | 146 ++++++++++++++++++------- src/lines-component.coffee | 2 +- 2 files changed, 106 insertions(+), 42 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index d9a577c25..30c955c57 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -7,10 +7,10 @@ nbsp = String.fromCharCode(160) describe "TextEditorComponent", -> [contentNode, editor, wrapperView, wrapperNode, component, componentNode, verticalScrollbarNode, horizontalScrollbarNode] = [] - [lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, lineOverdrawMargin] = [] + [lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, tileSize] = [] beforeEach -> - lineOverdrawMargin = 2 + tileSize = 3 waitsForPromise -> atom.packages.activatePackage('language-javascript') @@ -34,7 +34,7 @@ describe "TextEditorComponent", -> contentNode = document.querySelector('#jasmine-content') contentNode.style.width = '1000px' - wrapperView = new TextEditorView(editor, {lineOverdrawMargin}) + wrapperView = new TextEditorView(editor, {tileSize}) wrapperView.attachToDom() wrapperNode = wrapperView.element wrapperNode.setUpdatedSynchronously(false) @@ -68,48 +68,111 @@ describe "TextEditorComponent", -> expect(nextAnimationFrame).not.toThrow() describe "line rendering", -> - it "renders the currently-visible lines plus the overdraw margin", -> - wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + expectLineRender = (tileNode, {screenRow, top}) -> + lineNode = tileNode.querySelector("[data-screen-row='#{screenRow}']") + tokenizedLine = editor.tokenizedLineForScreenRow(screenRow) + + expect(lineNode.offsetTop).toBe(top) + if tokenizedLine.text is "" + expect(lineNode.innerHTML).toBe(" ") + else + expect(lineNode.textContent).toBe(tokenizedLine.text) + + it "renders the currently-visible lines into a tiled fashion", -> + wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' + tileHeight = tileSize * lineHeightInPixels component.measureDimensions() nextAnimationFrame() - linesNode = componentNode.querySelector('.lines') - expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" - expect(componentNode.querySelectorAll('.line').length).toBe 6 + 2 # no margin above - expect(component.lineNodeForScreenRow(0).textContent).toBe editor.tokenizedLineForScreenRow(0).text - expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 - expect(component.lineNodeForScreenRow(5).textContent).toBe editor.tokenizedLineForScreenRow(5).text - expect(component.lineNodeForScreenRow(5).offsetTop).toBe 5 * lineHeightInPixels + tilesNodes = componentNode.querySelectorAll(".tile") - verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels + expect(tilesNodes.length).toBe(3) + + expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" + expect(tilesNodes[0].children.length).toBe(tileSize) + expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels) + + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)" + expect(tilesNodes[1].children.length).toBe(tileSize) + expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels) + + expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)" + expect(tilesNodes[2].children.length).toBe(tileSize) + expectLineRender(tilesNodes[2], screenRow: 6, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[2], screenRow: 7, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[2], screenRow: 8, top: 2 * lineHeightInPixels) + + expect(component.lineNodeForScreenRow(9)).toBeUndefined() + + verticalScrollbarNode.scrollTop = tileSize * lineHeightInPixels + 5 verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, #{-4.5 * lineHeightInPixels}px, 0px)" - expect(componentNode.querySelectorAll('.line').length).toBe 6 + 4 # margin above and below - expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels - expect(component.lineNodeForScreenRow(2).textContent).toBe editor.tokenizedLineForScreenRow(2).text - expect(component.lineNodeForScreenRow(9).offsetTop).toBe 9 * lineHeightInPixels - expect(component.lineNodeForScreenRow(9).textContent).toBe editor.tokenizedLineForScreenRow(9).text + tilesNodes = componentNode.querySelectorAll(".tile") - it "updates the top position of subsequent lines when lines are inserted or removed", -> + expect(component.lineNodeForScreenRow(2)).toBeUndefined() + expect(tilesNodes.length).toBe(3) + + expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)" + expect(tilesNodes[0].children.length).toBe(tileSize) + expectLineRender(tilesNodes[0], screenRow: 3, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 4, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 5, top: 2 * lineHeightInPixels) + + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight - 5}px, 0px)" + expect(tilesNodes[1].children.length).toBe(tileSize) + expectLineRender(tilesNodes[1], screenRow: 6, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 7, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 8, top: 2 * lineHeightInPixels) + + expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight - 5}px, 0px)" + expect(tilesNodes[2].children.length).toBe(tileSize) + expectLineRender(tilesNodes[2], screenRow: 9, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[2], screenRow: 10, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[2], screenRow: 11, top: 2 * lineHeightInPixels) + + it "updates the top position of subsequent tiles when lines are inserted or removed", -> + wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' + tileHeight = tileSize * lineHeightInPixels + component.measureDimensions() editor.getBuffer().deleteRows(0, 1) nextAnimationFrame() - lineNodes = componentNode.querySelectorAll('.line') - expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 - expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels - expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels + tilesNodes = componentNode.querySelectorAll(".tile") + + expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" + expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels) + + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)" + expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels) editor.getBuffer().insert([0, 0], '\n\n') nextAnimationFrame() - lineNodes = componentNode.querySelectorAll('.line') - expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 * lineHeightInPixels - expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels - expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels - expect(component.lineNodeForScreenRow(3).offsetTop).toBe 3 * lineHeightInPixels - expect(component.lineNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels + tilesNodes = componentNode.querySelectorAll(".tile") + + expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" + expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels) + + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)" + expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels) + + expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)" + expectLineRender(tilesNodes[2], screenRow: 6, top: 0 * lineHeightInPixels) + expectLineRender(tilesNodes[2], screenRow: 7, top: 1 * lineHeightInPixels) + expectLineRender(tilesNodes[2], screenRow: 8, top: 2 * lineHeightInPixels) it "updates the lines when lines are inserted or removed above the rendered row range", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' @@ -483,7 +546,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number + expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # visible line-numbers + dummy line number expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1" expect(component.lineNumberNodeForScreenRow(5).textContent).toBe "#{nbsp}6" @@ -491,7 +554,7 @@ describe "TextEditorComponent", -> verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 4 + 1 # line overdraw margin above/below + dummy line number + expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # visible line-numbers + dummy line number expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}3" expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels @@ -527,7 +590,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line componentNode + expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # 1 dummy line expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1" expect(component.lineNumberNodeForScreenRow(1).textContent).toBe "#{nbsp}•" expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}2" @@ -725,13 +788,13 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 2 - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)" - expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" + expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" editor.onDidChangeCursorPosition cursorMovedListener = jasmine.createSpy('cursorMovedListener') cursor3.setScreenPosition([4, 11], autoscroll: false) nextAnimationFrame() - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" expect(cursorMovedListener).toHaveBeenCalled() cursor3.destroy() @@ -739,7 +802,7 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" it "accounts for character widths when positioning cursors", -> atom.config.set('editor.fontFamily', 'sans-serif') @@ -1001,7 +1064,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() # Scroll decorations into view - verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels + verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() expect(lineAndLineNumberHaveClass(9, 'b')).toBe true @@ -1147,15 +1210,16 @@ describe "TextEditorComponent", -> # Nothing when outside the rendered row range expect(regions.length).toBe 0 - verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels + verticalScrollbarNode.scrollTop = 6 * lineHeightInPixels verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() + expect(component.presenter.endRow).toBeGreaterThan(8) regions = componentNode.querySelectorAll('.some-highlight .region') expect(regions.length).toBe 1 regionRect = regions[0].style - expect(regionRect.top).toBe 9 * lineHeightInPixels + 'px' + expect(regionRect.top).toBe (9 * lineHeightInPixels - verticalScrollbarNode.scrollTop) + 'px' expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px' expect(regionRect.left).toBe 2 * charWidth + 'px' expect(regionRect.width).toBe 2 * charWidth + 'px' @@ -2397,7 +2461,7 @@ describe "TextEditorComponent", -> hiddenParent.style.display = 'none' contentNode.appendChild(hiddenParent) - wrapperView = new TextEditorView(editor, {lineOverdrawMargin}) + wrapperView = new TextEditorView(editor, {tileSize}) wrapperNode = wrapperView.element wrapperView.appendTo(hiddenParent) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index d99fbfbe1..1c855e125 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -132,4 +132,4 @@ class LinesComponent lineNodeForScreenRow: (screenRow) -> tile = @presenter.tileForRow(screenRow) - @tileComponentsByTileId[tile].lineNodeForScreenRow(screenRow) + @tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow) From f49078d394b333d8c0fc7400fa6f2c9eec22f3a4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 21 May 2015 10:40:37 +0200 Subject: [PATCH 48/93] wip --- src/foo-component.coffee | 135 ++++++++++++++++ src/lines-component.coffee | 313 +++++++++++++++++++++++++++---------- src/tile-component.coffee | 286 --------------------------------- 3 files changed, 367 insertions(+), 367 deletions(-) create mode 100644 src/foo-component.coffee delete mode 100644 src/tile-component.coffee diff --git a/src/foo-component.coffee b/src/foo-component.coffee new file mode 100644 index 000000000..1c855e125 --- /dev/null +++ b/src/foo-component.coffee @@ -0,0 +1,135 @@ +_ = require 'underscore-plus' +{toArray} = require 'underscore-plus' +{$$} = require 'space-pen' + +CursorsComponent = require './cursors-component' +HighlightsComponent = require './highlights-component' +TileComponent = require './tile-component' + +DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] +AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} +WrapperDiv = document.createElement('div') + +cloneObject = (object) -> + clone = {} + clone[key] = value for key, value of object + clone + +module.exports = +class LinesComponent + placeholderTextDiv: null + + constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> + @tileComponentsByTileId = {} + + @domNode = document.createElement('div') + @domNode.classList.add('lines') + + @cursorsComponent = new CursorsComponent(@presenter) + @domNode.appendChild(@cursorsComponent.getDomNode()) + + @highlightsComponent = new HighlightsComponent(@presenter) + @domNode.appendChild(@highlightsComponent.getDomNode()) + + if @useShadowDOM + insertionPoint = document.createElement('content') + insertionPoint.setAttribute('select', '.overlayer') + @domNode.appendChild(insertionPoint) + + getDomNode: -> + @domNode + + updateSync: (state) -> + @newState = state.content + @oldState ?= {tiles: {}} + + if @newState.scrollHeight isnt @oldState.scrollHeight + @domNode.style.height = @newState.scrollHeight + 'px' + @oldState.scrollHeight = @newState.scrollHeight + + if @newState.backgroundColor isnt @oldState.backgroundColor + @domNode.style.backgroundColor = @newState.backgroundColor + @oldState.backgroundColor = @newState.backgroundColor + + if @newState.placeholderText isnt @oldState.placeholderText + @placeholderTextDiv?.remove() + if @newState.placeholderText? + @placeholderTextDiv = document.createElement('div') + @placeholderTextDiv.classList.add('placeholder-text') + @placeholderTextDiv.textContent = @newState.placeholderText + @domNode.appendChild(@placeholderTextDiv) + + @removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible + @updateTileNodes() + + if @newState.scrollWidth isnt @oldState.scrollWidth + @domNode.style.width = @newState.scrollWidth + 'px' + @oldState.scrollWidth = @newState.scrollWidth + + @cursorsComponent.updateSync(state) + @highlightsComponent.updateSync(state) + + @oldState.indentGuidesVisible = @newState.indentGuidesVisible + @oldState.scrollWidth = @newState.scrollWidth + + removeTileNodes: -> + @removeTileNode(id) for id of @oldState.tiles + return + + removeTileNode: (id) -> + node = @tileComponentsByTileId[id].getDomNode() + + node.remove() + delete @tileComponentsByTileId[id] + delete @oldState.tiles[id] + + updateTileNodes: -> + for id of @oldState.tiles + unless @newState.tiles.hasOwnProperty(id) + @removeTileNode(id) + + for id, tileState of @newState.tiles + if @oldState.tiles.hasOwnProperty(id) + tileComponent = @tileComponentsByTileId[id] + else + tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter}) + + @domNode.appendChild(tileComponent.getDomNode()) + @oldState.tiles[id] = cloneObject(tileState) + + tileComponent.updateSync(@newState) + + return + + measureLineHeightAndDefaultCharWidth: -> + @domNode.appendChild(DummyLineNode) + lineHeightInPixels = DummyLineNode.getBoundingClientRect().height + charWidth = DummyLineNode.firstChild.getBoundingClientRect().width + @domNode.removeChild(DummyLineNode) + + @presenter.setLineHeight(lineHeightInPixels) + @presenter.setBaseCharacterWidth(charWidth) + + remeasureCharacterWidths: -> + return unless @presenter.baseCharacterWidth + + @clearScopedCharWidths() + @measureCharactersInNewLines() + + measureCharactersInNewLines: -> + @presenter.batchCharacterMeasurement => + for id, component of @tileComponentsByTileId + component.measureCharactersInNewLines() + + return + + clearScopedCharWidths: -> + for id, component of @tileComponentsByTileId + component.clearMeasurements() + + @presenter.clearScopedCharacterWidths() + + lineNodeForScreenRow: (screenRow) -> + tile = @presenter.tileForRow(screenRow) + + @tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 1c855e125..20c9d7ff1 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -2,10 +2,6 @@ _ = require 'underscore-plus' {toArray} = require 'underscore-plus' {$$} = require 'space-pen' -CursorsComponent = require './cursors-component' -HighlightsComponent = require './highlights-component' -TileComponent = require './tile-component' - DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} WrapperDiv = document.createElement('div') @@ -16,120 +12,275 @@ cloneObject = (object) -> clone module.exports = -class LinesComponent +class TileComponent placeholderTextDiv: null - constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> - @tileComponentsByTileId = {} - - @domNode = document.createElement('div') - @domNode.classList.add('lines') - - @cursorsComponent = new CursorsComponent(@presenter) - @domNode.appendChild(@cursorsComponent.getDomNode()) - - @highlightsComponent = new HighlightsComponent(@presenter) - @domNode.appendChild(@highlightsComponent.getDomNode()) - - if @useShadowDOM - insertionPoint = document.createElement('content') - insertionPoint.setAttribute('select', '.overlayer') - @domNode.appendChild(insertionPoint) + constructor: ({@presenter, @id}) -> + @measuredLines = new Set + @lineNodesByLineId = {} + @screenRowsByLineId = {} + @lineIdsByScreenRow = {} + @domNode = document.createElement("div") + @domNode.classList.add("tile") + @domNode.style.position = "absolute" + @domNode.style.display = "block" getDomNode: -> @domNode updateSync: (state) -> - @newState = state.content - @oldState ?= {tiles: {}} + @newState = state + unless @oldState + @oldState = {tiles: {}} + @oldState.tiles[@id] = {lines: {}} - if @newState.scrollHeight isnt @oldState.scrollHeight - @domNode.style.height = @newState.scrollHeight + 'px' - @oldState.scrollHeight = @newState.scrollHeight + @newTileState = @newState.tiles[@id] + @oldTileState = @oldState.tiles[@id] - if @newState.backgroundColor isnt @oldState.backgroundColor - @domNode.style.backgroundColor = @newState.backgroundColor - @oldState.backgroundColor = @newState.backgroundColor + if @newTileState.display isnt @oldTileState.display + @domNode.style.display = @newTileState.display + @oldTileState.display = @newTileState.display - if @newState.placeholderText isnt @oldState.placeholderText - @placeholderTextDiv?.remove() - if @newState.placeholderText? - @placeholderTextDiv = document.createElement('div') - @placeholderTextDiv.classList.add('placeholder-text') - @placeholderTextDiv.textContent = @newState.placeholderText - @domNode.appendChild(@placeholderTextDiv) + if @newTileState.height isnt @oldTileState.height + @domNode.style.height = @newTileState.height + 'px' + @oldTileState.height = @newTileState.height - @removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible - @updateTileNodes() + if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left + @domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)" + @oldTileState.top = @newTileState.top + @oldTileState.left = @newTileState.left + + @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible + @updateLineNodes() if @newState.scrollWidth isnt @oldState.scrollWidth @domNode.style.width = @newState.scrollWidth + 'px' @oldState.scrollWidth = @newState.scrollWidth - @cursorsComponent.updateSync(state) - @highlightsComponent.updateSync(state) - @oldState.indentGuidesVisible = @newState.indentGuidesVisible @oldState.scrollWidth = @newState.scrollWidth - removeTileNodes: -> - @removeTileNode(id) for id of @oldState.tiles + removeLineNodes: -> + @removeLineNode(id) for id of @oldTileState.lines return - removeTileNode: (id) -> - node = @tileComponentsByTileId[id].getDomNode() + removeLineNode: (id) -> + @lineNodesByLineId[id].remove() + delete @lineNodesByLineId[id] + delete @lineIdsByScreenRow[@screenRowsByLineId[id]] + delete @screenRowsByLineId[id] + delete @oldTileState.lines[id] - node.remove() - delete @tileComponentsByTileId[id] - delete @oldState.tiles[id] + updateLineNodes: -> + for id of @oldTileState.lines + unless @newTileState.lines.hasOwnProperty(id) + @removeLineNode(id) - updateTileNodes: -> - for id of @oldState.tiles - unless @newState.tiles.hasOwnProperty(id) - @removeTileNode(id) + newLineIds = null + newLinesHTML = null - for id, tileState of @newState.tiles - if @oldState.tiles.hasOwnProperty(id) - tileComponent = @tileComponentsByTileId[id] + for id, lineState of @newTileState.lines + if @oldTileState.lines.hasOwnProperty(id) + @updateLineNode(id) else - tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter}) + newLineIds ?= [] + newLinesHTML ?= "" + newLineIds.push(id) + newLinesHTML += @buildLineHTML(id) + @screenRowsByLineId[id] = lineState.screenRow + @lineIdsByScreenRow[lineState.screenRow] = id + @oldTileState.lines[id] = cloneObject(lineState) - @domNode.appendChild(tileComponent.getDomNode()) - @oldState.tiles[id] = cloneObject(tileState) + return unless newLineIds? - tileComponent.updateSync(@newState) + WrapperDiv.innerHTML = newLinesHTML + newLineNodes = _.toArray(WrapperDiv.children) + for id, i in newLineIds + lineNode = newLineNodes[i] + @lineNodesByLineId[id] = lineNode + @domNode.appendChild(lineNode) return - measureLineHeightAndDefaultCharWidth: -> - @domNode.appendChild(DummyLineNode) - lineHeightInPixels = DummyLineNode.getBoundingClientRect().height - charWidth = DummyLineNode.firstChild.getBoundingClientRect().width - @domNode.removeChild(DummyLineNode) + buildLineHTML: (id) -> + {scrollWidth} = @newState + {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id] - @presenter.setLineHeight(lineHeightInPixels) - @presenter.setBaseCharacterWidth(charWidth) + classes = '' + if decorationClasses? + for decorationClass in decorationClasses + classes += decorationClass + ' ' + classes += 'line' - remeasureCharacterWidths: -> - return unless @presenter.baseCharacterWidth + lineHTML = "
" - @clearScopedCharWidths() - @measureCharactersInNewLines() + if text is "" + lineHTML += @buildEmptyLineInnerHTML(id) + else + lineHTML += @buildLineInnerHTML(id) - measureCharactersInNewLines: -> - @presenter.batchCharacterMeasurement => - for id, component of @tileComponentsByTileId - component.measureCharactersInNewLines() + lineHTML += '' if fold + lineHTML += "
" + lineHTML - return + buildEmptyLineInnerHTML: (id) -> + {indentGuidesVisible} = @newState + {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id] - clearScopedCharWidths: -> - for id, component of @tileComponentsByTileId - component.clearMeasurements() + if indentGuidesVisible and indentLevel > 0 + invisibleIndex = 0 + lineHTML = '' + for i in [0...indentLevel] + lineHTML += "" + for j in [0...tabLength] + if invisible = endOfLineInvisibles?[invisibleIndex++] + lineHTML += "#{invisible}" + else + lineHTML += ' ' + lineHTML += "" - @presenter.clearScopedCharacterWidths() + while invisibleIndex < endOfLineInvisibles?.length + lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}" + + lineHTML + else + @buildEndOfLineHTML(id) or ' ' + + buildLineInnerHTML: (id) -> + {indentGuidesVisible} = @newState + {tokens, text, isOnlyWhitespace} = @newTileState.lines[id] + innerHTML = "" + + scopeStack = [] + for token in tokens + innerHTML += @updateScopeStack(scopeStack, token.scopes) + hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace)) + innerHTML += token.getValueAsHtml({hasIndentGuide}) + + innerHTML += @popScope(scopeStack) while scopeStack.length > 0 + innerHTML += @buildEndOfLineHTML(id) + innerHTML + + buildEndOfLineHTML: (id) -> + {endOfLineInvisibles} = @newTileState.lines[id] + + html = '' + if endOfLineInvisibles? + for invisible in endOfLineInvisibles + html += "#{invisible}" + html + + updateScopeStack: (scopeStack, desiredScopeDescriptor) -> + html = "" + + # Find a common prefix + for scope, i in desiredScopeDescriptor + break unless scopeStack[i] is desiredScopeDescriptor[i] + + # Pop scopeDescriptor until we're at the common prefx + until scopeStack.length is i + html += @popScope(scopeStack) + + # Push onto common prefix until scopeStack equals desiredScopeDescriptor + for j in [i...desiredScopeDescriptor.length] + html += @pushScope(scopeStack, desiredScopeDescriptor[j]) + + html + + popScope: (scopeStack) -> + scopeStack.pop() + "
" + + pushScope: (scopeStack, scope) -> + scopeStack.push(scope) + "" + + updateLineNode: (id) -> + oldLineState = @oldTileState.lines[id] + newLineState = @newTileState.lines[id] + + lineNode = @lineNodesByLineId[id] + + if @newState.scrollWidth isnt @oldState.scrollWidth + lineNode.style.width = @newState.scrollWidth + 'px' + + newDecorationClasses = newLineState.decorationClasses + oldDecorationClasses = oldLineState.decorationClasses + + if oldDecorationClasses? + for decorationClass in oldDecorationClasses + unless newDecorationClasses? and decorationClass in newDecorationClasses + lineNode.classList.remove(decorationClass) + + if newDecorationClasses? + for decorationClass in newDecorationClasses + unless oldDecorationClasses? and decorationClass in oldDecorationClasses + lineNode.classList.add(decorationClass) + + oldLineState.decorationClasses = newLineState.decorationClasses + + if newLineState.top isnt oldLineState.top + lineNode.style.top = newLineState.top + 'px' + oldLineState.top = newLineState.top + + if newLineState.screenRow isnt oldLineState.screenRow + lineNode.dataset.screenRow = newLineState.screenRow + oldLineState.screenRow = newLineState.screenRow + @lineIdsByScreenRow[newLineState.screenRow] = id lineNodeForScreenRow: (screenRow) -> - tile = @presenter.tileForRow(screenRow) + @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] - @tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow) + measureCharactersInNewLines: -> + for id, lineState of @oldTileState.lines + unless @measuredLines.has(id) + lineNode = @lineNodesByLineId[id] + @measureCharactersInLine(id, lineState, lineNode) + return + + measureCharactersInLine: (lineId, tokenizedLine, lineNode) -> + rangeForMeasurement = null + iterator = null + charIndex = 0 + + for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens + charWidths = @presenter.getScopedCharacterWidths(scopes) + + valueIndex = 0 + while valueIndex < value.length + if hasPairedCharacter + char = value.substr(valueIndex, 2) + charLength = 2 + valueIndex += 2 + else + char = value[valueIndex] + charLength = 1 + valueIndex++ + + continue if char is '\0' + + unless charWidths[char]? + unless textNode? + rangeForMeasurement ?= document.createRange() + iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter) + textNode = iterator.nextNode() + textNodeIndex = 0 + nextTextNodeIndex = textNode.textContent.length + + while nextTextNodeIndex <= charIndex + textNode = iterator.nextNode() + textNodeIndex = nextTextNodeIndex + nextTextNodeIndex = textNodeIndex + textNode.textContent.length + + i = charIndex - textNodeIndex + rangeForMeasurement.setStart(textNode, i) + rangeForMeasurement.setEnd(textNode, i + charLength) + charWidth = rangeForMeasurement.getBoundingClientRect().width + @presenter.setScopedCharacterWidth(scopes, char, charWidth) + + charIndex += charLength + + @measuredLines.add(lineId) + + clearMeasurements: -> + @measuredLines.clear() diff --git a/src/tile-component.coffee b/src/tile-component.coffee deleted file mode 100644 index 20c9d7ff1..000000000 --- a/src/tile-component.coffee +++ /dev/null @@ -1,286 +0,0 @@ -_ = require 'underscore-plus' -{toArray} = require 'underscore-plus' -{$$} = require 'space-pen' - -DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] -AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} -WrapperDiv = document.createElement('div') - -cloneObject = (object) -> - clone = {} - clone[key] = value for key, value of object - clone - -module.exports = -class TileComponent - placeholderTextDiv: null - - constructor: ({@presenter, @id}) -> - @measuredLines = new Set - @lineNodesByLineId = {} - @screenRowsByLineId = {} - @lineIdsByScreenRow = {} - @domNode = document.createElement("div") - @domNode.classList.add("tile") - @domNode.style.position = "absolute" - @domNode.style.display = "block" - - getDomNode: -> - @domNode - - updateSync: (state) -> - @newState = state - unless @oldState - @oldState = {tiles: {}} - @oldState.tiles[@id] = {lines: {}} - - @newTileState = @newState.tiles[@id] - @oldTileState = @oldState.tiles[@id] - - if @newTileState.display isnt @oldTileState.display - @domNode.style.display = @newTileState.display - @oldTileState.display = @newTileState.display - - if @newTileState.height isnt @oldTileState.height - @domNode.style.height = @newTileState.height + 'px' - @oldTileState.height = @newTileState.height - - if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left - @domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)" - @oldTileState.top = @newTileState.top - @oldTileState.left = @newTileState.left - - @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible - @updateLineNodes() - - if @newState.scrollWidth isnt @oldState.scrollWidth - @domNode.style.width = @newState.scrollWidth + 'px' - @oldState.scrollWidth = @newState.scrollWidth - - @oldState.indentGuidesVisible = @newState.indentGuidesVisible - @oldState.scrollWidth = @newState.scrollWidth - - removeLineNodes: -> - @removeLineNode(id) for id of @oldTileState.lines - return - - removeLineNode: (id) -> - @lineNodesByLineId[id].remove() - delete @lineNodesByLineId[id] - delete @lineIdsByScreenRow[@screenRowsByLineId[id]] - delete @screenRowsByLineId[id] - delete @oldTileState.lines[id] - - updateLineNodes: -> - for id of @oldTileState.lines - unless @newTileState.lines.hasOwnProperty(id) - @removeLineNode(id) - - newLineIds = null - newLinesHTML = null - - for id, lineState of @newTileState.lines - if @oldTileState.lines.hasOwnProperty(id) - @updateLineNode(id) - else - newLineIds ?= [] - newLinesHTML ?= "" - newLineIds.push(id) - newLinesHTML += @buildLineHTML(id) - @screenRowsByLineId[id] = lineState.screenRow - @lineIdsByScreenRow[lineState.screenRow] = id - @oldTileState.lines[id] = cloneObject(lineState) - - return unless newLineIds? - - WrapperDiv.innerHTML = newLinesHTML - newLineNodes = _.toArray(WrapperDiv.children) - for id, i in newLineIds - lineNode = newLineNodes[i] - @lineNodesByLineId[id] = lineNode - @domNode.appendChild(lineNode) - - return - - buildLineHTML: (id) -> - {scrollWidth} = @newState - {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id] - - classes = '' - if decorationClasses? - for decorationClass in decorationClasses - classes += decorationClass + ' ' - classes += 'line' - - lineHTML = "
" - - if text is "" - lineHTML += @buildEmptyLineInnerHTML(id) - else - lineHTML += @buildLineInnerHTML(id) - - lineHTML += '' if fold - lineHTML += "
" - lineHTML - - buildEmptyLineInnerHTML: (id) -> - {indentGuidesVisible} = @newState - {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id] - - if indentGuidesVisible and indentLevel > 0 - invisibleIndex = 0 - lineHTML = '' - for i in [0...indentLevel] - lineHTML += "" - for j in [0...tabLength] - if invisible = endOfLineInvisibles?[invisibleIndex++] - lineHTML += "#{invisible}" - else - lineHTML += ' ' - lineHTML += "" - - while invisibleIndex < endOfLineInvisibles?.length - lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}" - - lineHTML - else - @buildEndOfLineHTML(id) or ' ' - - buildLineInnerHTML: (id) -> - {indentGuidesVisible} = @newState - {tokens, text, isOnlyWhitespace} = @newTileState.lines[id] - innerHTML = "" - - scopeStack = [] - for token in tokens - innerHTML += @updateScopeStack(scopeStack, token.scopes) - hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace)) - innerHTML += token.getValueAsHtml({hasIndentGuide}) - - innerHTML += @popScope(scopeStack) while scopeStack.length > 0 - innerHTML += @buildEndOfLineHTML(id) - innerHTML - - buildEndOfLineHTML: (id) -> - {endOfLineInvisibles} = @newTileState.lines[id] - - html = '' - if endOfLineInvisibles? - for invisible in endOfLineInvisibles - html += "#{invisible}" - html - - updateScopeStack: (scopeStack, desiredScopeDescriptor) -> - html = "" - - # Find a common prefix - for scope, i in desiredScopeDescriptor - break unless scopeStack[i] is desiredScopeDescriptor[i] - - # Pop scopeDescriptor until we're at the common prefx - until scopeStack.length is i - html += @popScope(scopeStack) - - # Push onto common prefix until scopeStack equals desiredScopeDescriptor - for j in [i...desiredScopeDescriptor.length] - html += @pushScope(scopeStack, desiredScopeDescriptor[j]) - - html - - popScope: (scopeStack) -> - scopeStack.pop() - "
" - - pushScope: (scopeStack, scope) -> - scopeStack.push(scope) - "" - - updateLineNode: (id) -> - oldLineState = @oldTileState.lines[id] - newLineState = @newTileState.lines[id] - - lineNode = @lineNodesByLineId[id] - - if @newState.scrollWidth isnt @oldState.scrollWidth - lineNode.style.width = @newState.scrollWidth + 'px' - - newDecorationClasses = newLineState.decorationClasses - oldDecorationClasses = oldLineState.decorationClasses - - if oldDecorationClasses? - for decorationClass in oldDecorationClasses - unless newDecorationClasses? and decorationClass in newDecorationClasses - lineNode.classList.remove(decorationClass) - - if newDecorationClasses? - for decorationClass in newDecorationClasses - unless oldDecorationClasses? and decorationClass in oldDecorationClasses - lineNode.classList.add(decorationClass) - - oldLineState.decorationClasses = newLineState.decorationClasses - - if newLineState.top isnt oldLineState.top - lineNode.style.top = newLineState.top + 'px' - oldLineState.top = newLineState.top - - if newLineState.screenRow isnt oldLineState.screenRow - lineNode.dataset.screenRow = newLineState.screenRow - oldLineState.screenRow = newLineState.screenRow - @lineIdsByScreenRow[newLineState.screenRow] = id - - lineNodeForScreenRow: (screenRow) -> - @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] - - measureCharactersInNewLines: -> - for id, lineState of @oldTileState.lines - unless @measuredLines.has(id) - lineNode = @lineNodesByLineId[id] - @measureCharactersInLine(id, lineState, lineNode) - return - - measureCharactersInLine: (lineId, tokenizedLine, lineNode) -> - rangeForMeasurement = null - iterator = null - charIndex = 0 - - for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens - charWidths = @presenter.getScopedCharacterWidths(scopes) - - valueIndex = 0 - while valueIndex < value.length - if hasPairedCharacter - char = value.substr(valueIndex, 2) - charLength = 2 - valueIndex += 2 - else - char = value[valueIndex] - charLength = 1 - valueIndex++ - - continue if char is '\0' - - unless charWidths[char]? - unless textNode? - rangeForMeasurement ?= document.createRange() - iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter) - textNode = iterator.nextNode() - textNodeIndex = 0 - nextTextNodeIndex = textNode.textContent.length - - while nextTextNodeIndex <= charIndex - textNode = iterator.nextNode() - textNodeIndex = nextTextNodeIndex - nextTextNodeIndex = textNodeIndex + textNode.textContent.length - - i = charIndex - textNodeIndex - rangeForMeasurement.setStart(textNode, i) - rangeForMeasurement.setEnd(textNode, i + charLength) - charWidth = rangeForMeasurement.getBoundingClientRect().width - @presenter.setScopedCharacterWidth(scopes, char, charWidth) - - charIndex += charLength - - @measuredLines.add(lineId) - - clearMeasurements: -> - @measuredLines.clear() From efeb129cff9a81e38cfa8e8d8663680b63b0959c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 21 May 2015 10:47:23 +0200 Subject: [PATCH 49/93] Rename LinesComponent to TileComponent --- src/{lines-component.coffee => tile-component.coffee} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{lines-component.coffee => tile-component.coffee} (100%) diff --git a/src/lines-component.coffee b/src/tile-component.coffee similarity index 100% rename from src/lines-component.coffee rename to src/tile-component.coffee From 1a5e2fe5dd6b0632b38c956822fc6223433abbca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 21 May 2015 10:48:04 +0200 Subject: [PATCH 50/93] :art: --- src/{foo-component.coffee => lines-component.coffee} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{foo-component.coffee => lines-component.coffee} (100%) diff --git a/src/foo-component.coffee b/src/lines-component.coffee similarity index 100% rename from src/foo-component.coffee rename to src/lines-component.coffee From 490ab2c468605ca0165a359b3f623eee1808f149 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 21 May 2015 11:07:03 +0200 Subject: [PATCH 51/93] :green_heart: Conflicts: spec/text-editor-presenter-spec.coffee src/lines-component.coffee src/text-editor-presenter.coffee --- spec/text-editor-presenter-spec.coffee | 50 +++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 424f4bc85..872b71c58 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -660,7 +660,7 @@ describe "TextEditorPresenter", -> lineStateForScreenRow = (presenter, row) -> lineId = presenter.model.tokenizedLineForScreenRow(row).id tileRow = presenter.tileForRow(row) - presenter.getState().content.tiles[tileRow].lines[lineId] + presenter.getState().content.tiles[tileRow]?.lines[lineId] it "contains states for tiles that are visible on screen", -> presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) @@ -680,6 +680,25 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[8]).toBeUndefined() + expectStateUpdate -> presenter.setScrollTop(3) + + expect(presenter.getState().content.tiles[0]).toBeUndefined() + + expectValues presenter.getState().content.tiles[2], { + top: -1 + } + expectValues presenter.getState().content.tiles[4], { + top: 1 + } + expectValues presenter.getState().content.tiles[6], { + top: 3 + } + expectValues presenter.getState().content.tiles[8], { + top: 5 + } + + expect(presenter.getState().content.tiles[10]).toBeUndefined() + it "includes state for all tiles if no external ::explicitHeight is assigned", -> presenter = buildPresenter(explicitHeight: null, tileSize: 2) expect(presenter.getState().content.tiles[0]).toBeDefined() @@ -814,8 +833,22 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[0]).toBeDefined() describe "[tileId].lines[lineId]", -> # line state objects - xit "includes the state for lines in a tile", -> - expect(lineStateForScreenRow(presenter, 3)).toBeUndefined() + it "includes the state for visible lines in a tile", -> + presenter = buildPresenter(explicitHeight: 3, scrollTop: 4, lineHeight: 1, tileSize: 3, stoppedScrollingDelay: 200) + + expect(lineStateForScreenRow(presenter, 2)).toBeUndefined() + + line3 = editor.tokenizedLineForScreenRow(3) + expectValues lineStateForScreenRow(presenter, 3), { + screenRow: 3 + text: line3.text + tags: line3.tags + specialTokens: line3.specialTokens + firstNonWhitespaceIndex: line3.firstNonWhitespaceIndex + firstTrailingWhitespaceIndex: line3.firstTrailingWhitespaceIndex + invisibles: line3.invisibles + top: 0 + } line4 = editor.tokenizedLineForScreenRow(4) expectValues lineStateForScreenRow(presenter, 4), { @@ -826,7 +859,7 @@ describe "TextEditorPresenter", -> firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex invisibles: line4.invisibles - top: 10 * 4 + top: 1 } line5 = editor.tokenizedLineForScreenRow(5) @@ -838,7 +871,7 @@ describe "TextEditorPresenter", -> firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex invisibles: line5.invisibles - top: 10 * 5 + top: 2 } line6 = editor.tokenizedLineForScreenRow(6) @@ -850,7 +883,7 @@ describe "TextEditorPresenter", -> firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex invisibles: line6.invisibles - top: 10 * 6 + top: 0 } line7 = editor.tokenizedLineForScreenRow(7) @@ -862,7 +895,7 @@ describe "TextEditorPresenter", -> firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex invisibles: line7.invisibles - top: 10 * 7 + top: 1 } line8 = editor.tokenizedLineForScreenRow(8) @@ -874,9 +907,10 @@ describe "TextEditorPresenter", -> firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex invisibles: line8.invisibles - top: 10 * 8 + top: 2 } + expect(lineStateForScreenRow(presenter, 9)).toBeUndefined() it "includes the .endOfLineInvisibles if the editor.showInvisibles config option is true", -> editor.setText("hello\nworld\r\n") From c9a159aab39bf242383534d7365d29fc87cca5e7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 21 May 2015 11:16:28 +0200 Subject: [PATCH 52/93] :fire: Remove unused requires --- src/lines-component.coffee | 4 ---- src/tile-component.coffee | 3 --- 2 files changed, 7 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 1c855e125..23f1c0015 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -1,5 +1,3 @@ -_ = require 'underscore-plus' -{toArray} = require 'underscore-plus' {$$} = require 'space-pen' CursorsComponent = require './cursors-component' @@ -7,8 +5,6 @@ HighlightsComponent = require './highlights-component' TileComponent = require './tile-component' DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] -AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} -WrapperDiv = document.createElement('div') cloneObject = (object) -> clone = {} diff --git a/src/tile-component.coffee b/src/tile-component.coffee index 31ff61616..88e06afd2 100644 --- a/src/tile-component.coffee +++ b/src/tile-component.coffee @@ -1,9 +1,6 @@ _ = require 'underscore-plus' -{toArray} = require 'underscore-plus' -{$$} = require 'space-pen' TokenIterator = require './token-iterator' -DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} WrapperDiv = document.createElement('div') TokenTextEscapeRegex = /[&"'<>]/g From 17834157147bb3cffe2ed70d71998e3ee6e81699 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 22 May 2015 09:51:48 +0200 Subject: [PATCH 53/93] Fix spec failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @as-cii this was just a typo, but there’s a failure later in the spec that you’re probably in a better position to fix quickly. --- spec/text-editor-presenter-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 872b71c58..d8d1dbbbb 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -680,7 +680,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.tiles[8]).toBeUndefined() - expectStateUpdate -> presenter.setScrollTop(3) + expectStateUpdate presenter, -> presenter.setScrollTop(3) expect(presenter.getState().content.tiles[0]).toBeUndefined() From 538fcfece349cc8acf4fe27999b22ee394124a14 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 22 May 2015 14:01:52 +0200 Subject: [PATCH 54/93] :art: Better test naming --- spec/text-editor-component-spec.coffee | 70 +++++++++++++------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 30c955c57..b2bb09acd 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -68,7 +68,7 @@ describe "TextEditorComponent", -> expect(nextAnimationFrame).not.toThrow() describe "line rendering", -> - expectLineRender = (tileNode, {screenRow, top}) -> + expectTileContainsRow = (tileNode, screenRow, {top}) -> lineNode = tileNode.querySelector("[data-screen-row='#{screenRow}']") tokenizedLine = editor.tokenizedLineForScreenRow(screenRow) @@ -78,7 +78,7 @@ describe "TextEditorComponent", -> else expect(lineNode.textContent).toBe(tokenizedLine.text) - it "renders the currently-visible lines into a tiled fashion", -> + it "renders the currently-visible lines in a tiled fashion", -> wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' tileHeight = tileSize * lineHeightInPixels component.measureDimensions() @@ -90,21 +90,21 @@ describe "TextEditorComponent", -> expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" expect(tilesNodes[0].children.length).toBe(tileSize) - expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels) expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)" expect(tilesNodes[1].children.length).toBe(tileSize) - expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels) expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)" expect(tilesNodes[2].children.length).toBe(tileSize) - expectLineRender(tilesNodes[2], screenRow: 6, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[2], screenRow: 7, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[2], screenRow: 8, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels) expect(component.lineNodeForScreenRow(9)).toBeUndefined() @@ -119,21 +119,21 @@ describe "TextEditorComponent", -> expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)" expect(tilesNodes[0].children.length).toBe(tileSize) - expectLineRender(tilesNodes[0], screenRow: 3, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 4, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 5, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 3, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 4, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 5, top: 2 * lineHeightInPixels) expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight - 5}px, 0px)" expect(tilesNodes[1].children.length).toBe(tileSize) - expectLineRender(tilesNodes[1], screenRow: 6, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 7, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 8, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 6, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 7, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 8, top: 2 * lineHeightInPixels) expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight - 5}px, 0px)" expect(tilesNodes[2].children.length).toBe(tileSize) - expectLineRender(tilesNodes[2], screenRow: 9, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[2], screenRow: 10, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[2], screenRow: 11, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels) it "updates the top position of subsequent tiles when lines are inserted or removed", -> wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' @@ -145,14 +145,14 @@ describe "TextEditorComponent", -> tilesNodes = componentNode.querySelectorAll(".tile") expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" - expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels) expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)" - expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels) editor.getBuffer().insert([0, 0], '\n\n') nextAnimationFrame() @@ -160,19 +160,19 @@ describe "TextEditorComponent", -> tilesNodes = componentNode.querySelectorAll(".tile") expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" - expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels) expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)" - expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels) expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)" - expectLineRender(tilesNodes[2], screenRow: 6, top: 0 * lineHeightInPixels) - expectLineRender(tilesNodes[2], screenRow: 7, top: 1 * lineHeightInPixels) - expectLineRender(tilesNodes[2], screenRow: 8, top: 2 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels) + expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels) it "updates the lines when lines are inserted or removed above the rendered row range", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' From 80a3294f8e97e606f77ae3dd622adce8b8a17d3d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 22 May 2015 14:05:21 +0200 Subject: [PATCH 55/93] :green_heart: Fix wrong spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @nathansobo: I just forgot to include an actually visible tile in this test which, therefore, complained. “Production code”-wise the behavior was consistent and correct. --- spec/text-editor-presenter-spec.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index d8d1dbbbb..6a1b2a6d0 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -696,8 +696,11 @@ describe "TextEditorPresenter", -> expectValues presenter.getState().content.tiles[8], { top: 5 } + expectValues presenter.getState().content.tiles[10], { + top: 7 + } - expect(presenter.getState().content.tiles[10]).toBeUndefined() + expect(presenter.getState().content.tiles[12]).toBeUndefined() it "includes state for all tiles if no external ::explicitHeight is assigned", -> presenter = buildPresenter(explicitHeight: null, tileSize: 2) From 93eeda1ccc8a14048c5df2f1cbd6fa430bcc0798 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 22 May 2015 23:29:40 +0200 Subject: [PATCH 56/93] :memo: Document command name standard Resolves #6893 --- src/command-registry.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 3969dc283..9764af027 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -18,6 +18,11 @@ SequenceCount = 0 # command event listeners globally on `atom.commands` and constrain them to # specific kinds of elements with CSS selectors. # +# Command names must follow the `namespace:event` pattern, where both parts can +# be further split into words by hyphens. Typically `namespace` will be the +# name of your package, and `event` the name of your command. +# E.g. `awesome-package:toggle`. +# # As the event bubbles upward through the DOM, all registered event listeners # with matching selectors are invoked in order of specificity. In the event of a # specificity tie, the most recently registered listener is invoked first. This From dfd365cf51acd11958dd166c54ddffe9e83045a4 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 22 May 2015 23:42:23 +0200 Subject: [PATCH 57/93] :memo: Rephrase the description of `event` --- src/command-registry.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 9764af027..ebf0f50fe 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -20,7 +20,7 @@ SequenceCount = 0 # # Command names must follow the `namespace:event` pattern, where both parts can # be further split into words by hyphens. Typically `namespace` will be the -# name of your package, and `event` the name of your command. +# name of your package, and `event` describes the behavior of your command. # E.g. `awesome-package:toggle`. # # As the event bubbles upward through the DOM, all registered event listeners From b4ff2e15d19eb0b1fd3864b94bec74f5c9cb7026 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 23 May 2015 00:03:01 +0200 Subject: [PATCH 58/93] :memo: Stronger wording, and change `event` to `action` --- src/command-registry.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index ebf0f50fe..2de076d1a 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -18,10 +18,10 @@ SequenceCount = 0 # command event listeners globally on `atom.commands` and constrain them to # specific kinds of elements with CSS selectors. # -# Command names must follow the `namespace:event` pattern, where both parts can -# be further split into words by hyphens. Typically `namespace` will be the -# name of your package, and `event` describes the behavior of your command. -# E.g. `awesome-package:toggle`. +# Command names must follow the `namespace:action` pattern, where `namespace` +# will typically be the name of your package, and `action` describes the +# behavior of your command. If either part consists of multiple words, these +# must be separated by hyphens. E.g. `awesome-package:turn-it-up-to-eleven`. # # As the event bubbles upward through the DOM, all registered event listeners # with matching selectors are invoked in order of specificity. In the event of a From 7926ca5f231c9cd361a1c6f2781b39a273df09ce Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 27 May 2015 09:01:41 +0200 Subject: [PATCH 59/93] Declare `atom.hasTiledEditor = true` This way packages that are affected by relative coordinates will be able to smoothly upgrade using a feature toggle. --- src/atom.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index 0d029b61d..35ee786f7 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -34,6 +34,8 @@ class Atom extends Model atom = @deserialize(@loadState(mode)) ? new this({mode, @version}) atom.deserializeTimings.atom = Date.now() - startTime + Object.defineProperty(atom, "hasTiledEditor", value: true) + if includeDeprecatedAPIs workspaceViewDeprecationMessage = """ atom.workspaceView is no longer available. From a0ce1e6d01e581f4461c6b42d57319eb682bb8e5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 27 May 2015 12:59:36 +0200 Subject: [PATCH 60/93] Narrow scope and rename flag --- src/atom.coffee | 2 -- src/text-editor-element.coffee | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 35ee786f7..0d029b61d 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -34,8 +34,6 @@ class Atom extends Model atom = @deserialize(@loadState(mode)) ? new this({mode, @version}) atom.deserializeTimings.atom = Date.now() - startTime - Object.defineProperty(atom, "hasTiledEditor", value: true) - if includeDeprecatedAPIs workspaceViewDeprecationMessage = """ atom.workspaceView is no longer available. diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 88e07a18d..e3eaaeb2a 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -17,6 +17,7 @@ class TextEditorElement extends HTMLElement attached: false tileSize: null focusOnAttach: false + hasTiledRendering: true createdCallback: -> @emitter = new Emitter From 4c9e6c5a27e9c12cf2fba2b264596728e252aad2 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Wed, 27 May 2015 11:18:15 -0700 Subject: [PATCH 61/93] Revert "Merge pull request #6977 from atom/revert-6813-local-initial-paths" This reverts commit 0c668022789a06330e2a6ddc73478a458bc3c625, reversing changes made to bdce576ab9c7e7a1a9cd0736999db5c38eac2a94. --- spec/default-directory-provider-spec.coffee | 6 ++++++ spec/integration/startup-spec.coffee | 10 ++++++++++ spec/window-spec.coffee | 11 +++++++++++ src/browser/atom-application.coffee | 8 +++++++- src/browser/main.coffee | 9 ++++++--- src/default-directory-provider.coffee | 21 +++++++++++++++------ src/window-event-handler.coffee | 10 ++++++---- 7 files changed, 61 insertions(+), 14 deletions(-) diff --git a/spec/default-directory-provider-spec.coffee b/spec/default-directory-provider-spec.coffee index 69370a77b..357348c4a 100644 --- a/spec/default-directory-provider-spec.coffee +++ b/spec/default-directory-provider-spec.coffee @@ -31,6 +31,12 @@ describe "DefaultDirectoryProvider", -> directory = provider.directoryForURISync(file) expect(directory.getPath()).toEqual tmp + it "creates a Directory with a path as a uri when passed a uri", -> + provider = new DefaultDirectoryProvider() + uri = 'remote://server:6792/path/to/a/dir' + directory = provider.directoryForURISync(uri) + expect(directory.getPath()).toEqual uri + describe ".directoryForURI(uri)", -> it "returns a Promise that resolves to a Directory with a path that matches the uri", -> provider = new DefaultDirectoryProvider() diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 54817fedc..d2583ed27 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -227,3 +227,13 @@ describe "Starting Atom", -> [tempDirPath] [otherTempDirPath] ].sort() + + describe "opening a remote directory", -> + it "opens the parent directory and creates an empty text editor", -> + remoteDirectory = 'remote://server:3437/some/directory/path' + runAtom [remoteDirectory], {ATOM_HOME: atomHome}, (client) -> + client + .waitForWindowCount(1, 1000) + .waitForExist("atom-workspace", 5000) + .treeViewRootDirectories() + .then ({value}) -> expect(value).toEqual([remoteDirectory]) diff --git a/spec/window-spec.coffee b/spec/window-spec.coffee index 376a71ab3..94e605a36 100644 --- a/spec/window-spec.coffee +++ b/spec/window-spec.coffee @@ -293,3 +293,14 @@ describe "Window", -> pathToOpen = __dirname atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}] expect(atom.workspace.open.callCount).toBe 0 + + describe "when the opened path is a uri", -> + it "adds it to the project's paths as is", -> + pathToOpen = 'remote://server:7644/some/dir/path' + atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}] + + waitsFor -> + atom.project.getPaths().length is 1 + + runs -> + expect(atom.project.getPaths()[0]).toBe pathToOpen diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 1b2aa2e86..5bd6be73e 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -369,7 +369,12 @@ class AtomApplication # :windowDimensions - Object with height and width keys. # :window - {AtomWindow} to open file paths in. openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, windowDimensions, profileStartup, window}={}) -> - pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen) + pathsToOpen = pathsToOpen.map (pathToOpen) -> + if fs.existsSync(pathToOpen) + fs.normalize(pathToOpen) + else + pathToOpen + locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen) unless pidToKillWhenClosed or newWindow @@ -518,6 +523,7 @@ class AtomApplication locationForPathToOpen: (pathToOpen) -> return {pathToOpen} unless pathToOpen + return {pathToOpen} if url.parse(pathToOpen).protocol? return {pathToOpen} if fs.existsSync(pathToOpen) pathToOpen = pathToOpen.replace(/[:\s]+$/, '') diff --git a/src/browser/main.coffee b/src/browser/main.coffee index 1af0526dd..371dd71ea 100644 --- a/src/browser/main.coffee +++ b/src/browser/main.coffee @@ -5,6 +5,7 @@ app = require 'app' fs = require 'fs-plus' path = require 'path' yargs = require 'yargs' +url = require 'url' nslog = require 'nslog' console.log = nslog @@ -45,9 +46,11 @@ start = -> cwd = args.executedFrom?.toString() or process.cwd() args.pathsToOpen = args.pathsToOpen.map (pathToOpen) -> - pathToOpen = fs.normalize(pathToOpen) - if cwd - path.resolve(cwd, pathToOpen) + normalizedPath = fs.normalize(pathToOpen) + if url.parse(pathToOpen).protocol? + pathToOpen + else if cwd + path.resolve(cwd, normalizedPath) else path.resolve(pathToOpen) diff --git a/src/default-directory-provider.coffee b/src/default-directory-provider.coffee index 9e25e097b..da2d17593 100644 --- a/src/default-directory-provider.coffee +++ b/src/default-directory-provider.coffee @@ -1,6 +1,7 @@ {Directory} = require 'pathwatcher' fs = require 'fs-plus' path = require 'path' +url = require 'url' module.exports = class DefaultDirectoryProvider @@ -14,14 +15,22 @@ class DefaultDirectoryProvider # * {Directory} if the given URI is compatible with this provider. # * `null` if the given URI is not compatibile with this provider. directoryForURISync: (uri) -> - projectPath = path.normalize(uri) - - directoryPath = if not fs.isDirectorySync(projectPath) and fs.isDirectorySync(path.dirname(projectPath)) - path.dirname(projectPath) + normalizedPath = path.normalize(uri) + {protocol} = url.parse(uri) + directoryPath = if protocol? + uri + else if not fs.isDirectorySync(normalizedPath) and fs.isDirectorySync(path.dirname(normalizedPath)) + path.dirname(normalizedPath) else - projectPath + normalizedPath - new Directory(directoryPath) + # TODO: Stop normalizing the path in pathwatcher's Directory. + directory = new Directory(directoryPath) + if protocol? + directory.path = directoryPath + if fs.isCaseInsensitive() + directory.lowerCasePath = directoryPath.toLowerCase() + directory # Public: Create a Directory that corresponds to the specified URI. # diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 7d67e87ab..0855b27a1 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -5,6 +5,7 @@ ipc = require 'ipc' shell = require 'shell' {Subscriber} = require 'emissary' fs = require 'fs-plus' +url = require 'url' # Handles low-level events related to the window. module.exports = @@ -23,12 +24,13 @@ class WindowEventHandler if pathToOpen? and needsProjectPaths if fs.existsSync(pathToOpen) atom.project.addPath(pathToOpen) + else if fs.existsSync(path.dirname(pathToOpen)) + atom.project.addPath(path.dirname(pathToOpen)) else - dirToOpen = path.dirname(pathToOpen) - if fs.existsSync(dirToOpen) - atom.project.addPath(dirToOpen) + atom.project.addPath(pathToOpen) - unless fs.isDirectorySync(pathToOpen) + {protocol} = url.parse(pathToOpen) + unless fs.isDirectorySync(pathToOpen) or protocol? atom.workspace?.open(pathToOpen, {initialLine, initialColumn}) return From 5e7bc308d4a0a78d1a99ac4fb1750623c7b13f3d Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Wed, 27 May 2015 11:20:49 -0700 Subject: [PATCH 62/93] Remove the protocol parse and check in the window-event-handler --- src/window-event-handler.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 0855b27a1..c9fd94681 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -29,8 +29,7 @@ class WindowEventHandler else atom.project.addPath(pathToOpen) - {protocol} = url.parse(pathToOpen) - unless fs.isDirectorySync(pathToOpen) or protocol? + unless fs.isDirectorySync(pathToOpen) atom.workspace?.open(pathToOpen, {initialLine, initialColumn}) return From 952f92c367bccd1128d338bab539ea5aefef817f Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Wed, 27 May 2015 11:38:06 -0700 Subject: [PATCH 63/93] Remove uneeded require to url --- src/window-event-handler.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index c9fd94681..3b9d54b96 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -5,7 +5,6 @@ ipc = require 'ipc' shell = require 'shell' {Subscriber} = require 'emissary' fs = require 'fs-plus' -url = require 'url' # Handles low-level events related to the window. module.exports = From 98dc2f9d7f717944b7a3a928a5a8880e8c6038eb Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 28 May 2015 16:09:15 -0700 Subject: [PATCH 64/93] Style links and code blocks in text-* classes --- static/atom.less | 1 + static/text.less | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 static/text.less diff --git a/static/atom.less b/static/atom.less index b45d7b29a..1acb0f54b 100644 --- a/static/atom.less +++ b/static/atom.less @@ -25,5 +25,6 @@ @import "text-editor-light"; @import "select-list"; @import "syntax"; +@import "text"; @import "utilities"; @import "octicons"; diff --git a/static/text.less b/static/text.less new file mode 100644 index 000000000..4782ee7f9 --- /dev/null +++ b/static/text.less @@ -0,0 +1,35 @@ +.text-bits (@type) { + @text-color: "text-color-@{type}"; + @bg-color: "background-color-@{type}"; + + code { + color: @@text-color; + background: fadeout(@@bg-color, 80%); + } + + a, a code { + color: @@text-color; + text-decoration: underline; + color: darken(@@text-color, 10%); + + &:hover { + color: darken(@@text-color, 15%); + } + } +} + +.text-info { + .text-bits(info); +} + +.text-success { + .text-bits(success); +} + +.text-warning { + .text-bits(warning); +} + +.text-error { + .text-bits(error); +} From ba9c0c65c1e61d4b38d9d9f41d5754bd7f0bc124 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 29 May 2015 15:12:34 +0900 Subject: [PATCH 65/93] :arrow_up: one-dark/light-syntax@0.7.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5165c61eb..5bb2738bc 100644 --- a/package.json +++ b/package.json @@ -78,8 +78,8 @@ "base16-tomorrow-dark-theme": "0.26.0", "base16-tomorrow-light-theme": "0.9.0", "one-dark-ui": "0.8.2", - "one-dark-syntax": "0.5.0", - "one-light-syntax": "0.6.0", + "one-dark-syntax": "0.7.0", + "one-light-syntax": "0.7.0", "one-light-ui": "0.8.2", "solarized-dark-syntax": "0.35.0", "solarized-light-syntax": "0.21.0", From 7c968542faccb5d09d501475c3761c0b65582950 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 29 May 2015 15:13:50 +0900 Subject: [PATCH 66/93] :arrow_up: one-dark/light-ui@0.9.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5bb2738bc..54ef562b4 100644 --- a/package.json +++ b/package.json @@ -77,10 +77,10 @@ "atom-light-ui": "0.41.0", "base16-tomorrow-dark-theme": "0.26.0", "base16-tomorrow-light-theme": "0.9.0", - "one-dark-ui": "0.8.2", + "one-dark-ui": "0.9.0", "one-dark-syntax": "0.7.0", "one-light-syntax": "0.7.0", - "one-light-ui": "0.8.2", + "one-light-ui": "0.9.0", "solarized-dark-syntax": "0.35.0", "solarized-light-syntax": "0.21.0", "archive-view": "0.57.0", From 9b4d62b687bc91b28ac4be16453f5fb148d5f1c7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 29 May 2015 15:21:00 +0200 Subject: [PATCH 67/93] :bug: Update cursors after model changes /cc: @nathansobo --- spec/text-editor-presenter-spec.coffee | 13 +++++++++++++ src/text-editor-presenter.coffee | 1 + 2 files changed, 14 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 0d5c5cffb..142026913 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1108,6 +1108,19 @@ describe "TextEditorPresenter", -> expect(stateForCursor(presenter, 3)).toEqual {top: 0, left: 12 * 10, width: 10, height: 10} expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10 - 50, left: 4 * 10, width: 10, height: 10} + it "updates when ::scrollTop changes after the model was changed", -> + editor.setCursorBufferPosition([8, 22]) + presenter = buildPresenter(explicitHeight: 50, scrollTop: 10 * 8) + + expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 10 * 22, width: 10, height: 10} + + expectStateUpdate presenter, -> + editor.getBuffer().deleteRow(12) + editor.getBuffer().deleteRow(11) + editor.getBuffer().deleteRow(10) + + expect(stateForCursor(presenter, 0)).toEqual {top: 20, left: 10 * 22, width: 10, height: 10} + it "updates when ::explicitHeight changes", -> editor.setSelectedBufferRanges([ [[1, 2], [1, 2]], diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2acd39863..7b63fa10a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -115,6 +115,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true + @shouldUpdateCursorsState = true @shouldUpdateTilesState = true @shouldUpdateLineNumberGutterState = true @shouldUpdateLineNumbersState = true From 68030e1793aa7bb25daa01a3b823ce2832cdd1f4 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 29 May 2015 10:30:56 -0700 Subject: [PATCH 68/93] :arrow_up: autocomplete-snippets to fix exception --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 54ef562b4..430d9cfc8 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "autocomplete-css": "0.7.2", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.17.3", - "autocomplete-snippets": "1.6.3", + "autocomplete-snippets": "1.7.0", "autoflow": "0.24.0", "autosave": "0.20.0", "background-tips": "0.25.0", From fd6e8fabb16ce652d0b89e8df7e364357aded91e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 29 May 2015 10:31:19 -0700 Subject: [PATCH 69/93] :arrow_up: snippets to load bundled snippets first --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 430d9cfc8..86c238011 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "package-generator": "0.39.0", "release-notes": "0.52.0", "settings-view": "0.206.0", - "snippets": "0.91.0", + "snippets": "0.92.0", "spell-check": "0.58.0", "status-bar": "0.74.0", "styleguide": "0.44.0", From fad99c69e617389d532fc7483a4425cfb15a19d0 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 29 May 2015 10:38:37 -0700 Subject: [PATCH 70/93] Fix build issue: import ui vars. --- static/text.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/text.less b/static/text.less index 4782ee7f9..249cf820d 100644 --- a/static/text.less +++ b/static/text.less @@ -1,3 +1,5 @@ +@import "ui-variables"; + .text-bits (@type) { @text-color: "text-color-@{type}"; @bg-color: "background-color-@{type}"; From 5389156d767f33fa9513549b8fed56c85b9c8f1d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 29 May 2015 20:23:08 +0200 Subject: [PATCH 71/93] :arrow_up: first-mate to fix regression --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86c238011..81b2771ec 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "delegato": "^1", "emissary": "^1.3.3", "event-kit": "^1.2.0", - "first-mate": "^4.1.5", + "first-mate": "^4.1.6", "fs-plus": "^2.8.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", From 97f817c542f2a567af70a685f75942a5255bc9e7 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 29 May 2015 21:51:27 +0200 Subject: [PATCH 72/93] :memo: Add sentence about lowercasing command names --- src/command-registry.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 2de076d1a..72098eda0 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -22,6 +22,7 @@ SequenceCount = 0 # will typically be the name of your package, and `action` describes the # behavior of your command. If either part consists of multiple words, these # must be separated by hyphens. E.g. `awesome-package:turn-it-up-to-eleven`. +# All words should be lowercased. # # As the event bubbles upward through the DOM, all registered event listeners # with matching selectors are invoked in order of specificity. In the event of a From 604e41966de70bd28ae8a0ab06621b01aa3cf58f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 29 May 2015 22:13:22 +0200 Subject: [PATCH 73/93] :arrow_up: bracket-matcher --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81b2771ec..37fb79894 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "autosave": "0.20.0", "background-tips": "0.25.0", "bookmarks": "0.35.0", - "bracket-matcher": "0.74.0", + "bracket-matcher": "0.75.0", "command-palette": "0.36.0", "deprecation-cop": "0.52.0", "dev-live-reload": "0.46.0", From 49c8a10c08d0f8b08a440287e59ae73643ad6b26 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Fri, 29 May 2015 13:21:45 -0700 Subject: [PATCH 74/93] Fix text lint error --- static/text.less | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/static/text.less b/static/text.less index 249cf820d..a189fbf9a 100644 --- a/static/text.less +++ b/static/text.less @@ -1,21 +1,23 @@ @import "ui-variables"; .text-bits (@type) { - @text-color: "text-color-@{type}"; - @bg-color: "background-color-@{type}"; + @text-color-name: "text-color-@{type}"; + @bg-color-name: "background-color-@{type}"; + + @text-color: @@text-color-name; + @bg-color: @@bg-color-name; code { - color: @@text-color; - background: fadeout(@@bg-color, 80%); + color: @text-color; + background: fadeout(@bg-color, 80%); } a, a code { - color: @@text-color; text-decoration: underline; - color: darken(@@text-color, 10%); + color: darken(@text-color, 10%); &:hover { - color: darken(@@text-color, 15%); + color: darken(@text-color, 15%); } } } From 5884525648fd7a5357273dc9dd29cfb385439204 Mon Sep 17 00:00:00 2001 From: Lee Dohm Date: Sun, 31 May 2015 05:27:38 -0700 Subject: [PATCH 75/93] :memo: Add returns Disposable to ContextMenuManager.add --- src/context-menu-manager.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 0fdcf4ab6..73bcbf440 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -100,6 +100,9 @@ class ContextMenuManager # whether to display this item on a given context menu deployment. Called # with the following argument: # * `event` The click event that deployed the context menu. + # + # Returns a {Disposable} on which `.dispose()` can be called to remove the + # added menu items. add: (itemsBySelector) -> if Grim.includeDeprecatedAPIs # Detect deprecated file path as first argument From c81409cf90e09f3e37e0a62b2b6001001bb4f6ef Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 31 May 2015 11:15:48 -0400 Subject: [PATCH 76/93] :arrow_up: language-json@0.15.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 37fb79894..25dda7539 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "language-hyperlink": "0.13.0", "language-java": "0.15.0", "language-javascript": "0.78.0", - "language-json": "0.14.0", + "language-json": "0.15.0", "language-less": "0.27.0", "language-make": "0.14.0", "language-mustache": "0.11.0", From eaaa6b15e5bd786a66281e2e00cd33962f4ee0cd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 1 Jun 2015 09:35:41 -0700 Subject: [PATCH 77/93] :non-potable_water: Prevent stdout/stderr data listener leaks Old listeners were not cleaned up before new ones were being added causing the following console message: warning: possible EventEmitter memory leak detected. Refs #7033 --- spec/task-spec.coffee | 13 +++++++++++++ src/task.coffee | 11 +++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/spec/task-spec.coffee b/spec/task-spec.coffee index 81a8713ad..bddb59d86 100644 --- a/spec/task-spec.coffee +++ b/spec/task-spec.coffee @@ -57,3 +57,16 @@ describe "Task", -> expect(deprecations.length).toBe 1 expect(deprecations[0].getStacks()[0][1].fileName).toBe handlerPath jasmine.restoreDeprecationsSnapshot() + + it "adds data listeners to standard out and error to report output", -> + task = new Task(require.resolve('./fixtures/task-spec-handler')) + {stdout, stderr} = task.childProcess + + task.start() + task.start() + expect(stdout.listeners('data').length).toBe 1 + expect(stderr.listeners('data').length).toBe 1 + + task.terminate() + expect(stdout.listeners('data').length).toBe 0 + expect(stderr.listeners('data').length).toBe 0 diff --git a/src/task.coffee b/src/task.coffee index d752ea11d..939b71635 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -100,11 +100,12 @@ class Task @childProcess.removeAllListeners() @childProcess.on 'message', ({event, args}) => @emit(event, args...) if @childProcess? + # Catch the errors that happened before task-bootstrap. - @childProcess.stdout.on 'data', (data) -> - console.log data.toString() - @childProcess.stderr.on 'data', (data) -> - console.error data.toString() + @childProcess.stdout.removeAllListeners() + @childProcess.stdout.on 'data', (data) -> console.log data.toString() + @childProcess.stderr.removeAllListeners() + @childProcess.stderr.on 'data', (data) -> console.error data.toString() # Public: Starts the task. # @@ -152,6 +153,8 @@ class Task return unless @childProcess? @childProcess.removeAllListeners() + @childProcess.stdout.removeAllListeners() + @childProcess.stderr.removeAllListeners() @childProcess.kill() @childProcess = null From b68902dd8303e99e85aa814be1be36c638ebe938 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 29 May 2015 21:53:52 +0200 Subject: [PATCH 78/93] :art: --- spec/config-spec.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 2c5ba2fc2..d64ac2d27 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -805,7 +805,6 @@ describe "Config", -> atom.config.loadUserConfig() expect(atom.config.get("foo.bar")).toBe "baz" - describe ".observeUserConfig()", -> updatedHandler = null From e723b26eb0dc3a4b7213f4d41b0a709579eab457 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 29 May 2015 21:54:29 +0200 Subject: [PATCH 79/93] Add maximumLength schema enforcer for strings --- spec/config-spec.coffee | 10 ++++++++++ src/config.coffee | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index d64ac2d27..b75a121a1 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -1380,6 +1380,16 @@ describe "Config", -> expect(atom.config.set('foo.bar.aString', nope: 'nope')).toBe false expect(atom.config.get('foo.bar.aString')).toBe 'ok' + describe 'when the schema has a "maximumLength" key', -> + it "trims the string to be no longer than the specified maximum", -> + schema = + type: 'string' + default: 'ok' + maximumLength: 3 + atom.config.setSchema('foo.bar.aString', schema) + atom.config.set('foo.bar.aString', 'abcdefg') + expect(atom.config.get('foo.bar.aString')).toBe 'abc' + describe 'when the value has an "object" type', -> beforeEach -> schema = diff --git a/src/config.coffee b/src/config.coffee index b97ce99ec..9ab8ce607 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -1060,6 +1060,12 @@ Config.addSchemaEnforcers throw new Error("Validation failed at #{keyPath}, #{JSON.stringify(value)} must be a string") value + validateMaximumLength: (keyPath, value, schema) -> + if typeof schema.maximumLength is 'number' and value.length > schema.maximumLength + value.slice(0, schema.maximumLength) + else + value + 'null': # null sort of isnt supported. It will just unset in this case coerce: (keyPath, value, schema) -> From e258dd2f8e4d1057013c63cb75d71dd52030b5b2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 29 May 2015 21:55:20 +0200 Subject: [PATCH 80/93] Use maximumLength schema enforcer to constrain invisible characters Fixes #7014 --- src/config-schema.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index b814e21de..fd35d2b07 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -179,15 +179,19 @@ module.exports = eol: type: ['boolean', 'string'] default: '\u00ac' + maximumLength: 1 space: type: ['boolean', 'string'] default: '\u00b7' + maximumLength: 1 tab: type: ['boolean', 'string'] default: '\u00bb' + maximumLength: 1 cr: type: ['boolean', 'string'] default: '\u00a4' + maximumLength: 1 zoomFontWhenCtrlScrolling: type: 'boolean' default: process.platform isnt 'darwin' From 703a36b62073614392a890dc8a519404e89a5592 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 1 Jun 2015 11:19:45 -0700 Subject: [PATCH 81/93] :arrow_up: snippets to fix spec --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 25dda7539..e5afd1beb 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "package-generator": "0.39.0", "release-notes": "0.52.0", "settings-view": "0.206.0", - "snippets": "0.92.0", + "snippets": "0.93.0", "spell-check": "0.58.0", "status-bar": "0.74.0", "styleguide": "0.44.0", From 7e34164c4bed65394f7c9818cb0b84eec8375adb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 29 May 2015 14:55:48 -0700 Subject: [PATCH 82/93] Keep old folder names in api preview mode --- src/package.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/package.coffee b/src/package.coffee index 65c7af822..d55de19db 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -277,7 +277,7 @@ class Package [stylesheetPath, atom.themes.loadStylesheet(stylesheetPath, true)] getStylesheetsPath: -> - if includeDeprecatedAPIs and fs.isDirectorySync(path.join(@path, 'stylesheets')) + if fs.isDirectorySync(path.join(@path, 'stylesheets')) deprecate("Store package style sheets in the `styles/` directory instead of `stylesheets/` in the `#{@name}` package", packageName: @name) path.join(@path, 'stylesheets') else @@ -353,7 +353,7 @@ class Package deferred = Q.defer() - if includeDeprecatedAPIs and fs.isDirectorySync(path.join(@path, 'scoped-properties')) + if fs.isDirectorySync(path.join(@path, 'scoped-properties')) settingsDirPath = path.join(@path, 'scoped-properties') deprecate("Store package settings files in the `settings/` directory instead of `scoped-properties/`", packageName: @name) else From 3c647dc7af65f6bde49a1993ddf004d3c749408c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 29 May 2015 14:56:40 -0700 Subject: [PATCH 83/93] Keep activate events in api preview mode --- src/package.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.coffee b/src/package.coffee index d55de19db..130918af7 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -489,7 +489,7 @@ class Package else if _.isArray(commands) @activationCommands[selector].push(commands...) - if includeDeprecatedAPIs and @metadata.activationEvents? + if @metadata.activationEvents? deprecate(""" Use `activationCommands` instead of `activationEvents` in your package.json Commands should be grouped by selector as follows: From 558d10434ab0b76ea07f420594f51f1a11fd5460 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 29 May 2015 14:58:39 -0700 Subject: [PATCH 84/93] Keep old context menu format in api preview mode --- src/package.coffee | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/package.coffee b/src/package.coffee index 130918af7..a3fbf9e39 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -203,16 +203,15 @@ class Package try itemsBySelector = map['context-menu'] - if includeDeprecatedAPIs - # Detect deprecated format for items object - for key, value of itemsBySelector - unless _.isArray(value) - deprecate(""" - The context menu CSON format has changed. Please see - https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format - for more info. - """, {packageName: @name}) - itemsBySelector = atom.contextMenu.convertLegacyItemsBySelector(itemsBySelector) + # Detect deprecated format for items object + for key, value of itemsBySelector + unless _.isArray(value) + deprecate(""" + The context menu CSON format has changed. Please see + https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format + for more info. + """, {packageName: @name}) + itemsBySelector = atom.contextMenu.convertLegacyItemsBySelector(itemsBySelector) @activationDisposables.add(atom.contextMenu.add(itemsBySelector)) catch error From fed19872b36296e1c1f4756ea5e0b6e036ca8e7b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 29 May 2015 14:59:49 -0700 Subject: [PATCH 85/93] Keep old configDefaults support in api preview mode --- src/package.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.coffee b/src/package.coffee index a3fbf9e39..b404f99e8 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -170,7 +170,7 @@ class Package if @mainModule? if @mainModule.config? and typeof @mainModule.config is 'object' atom.config.setSchema @name, {type: 'object', properties: @mainModule.config} - else if includeDeprecatedAPIs and @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object' + else if @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object' deprecate("""Use a config schema instead. See the configuration section of https://atom.io/docs/latest/hacking-atom-package-word-count and https://atom.io/docs/api/latest/Config for more details""", {packageName: @name}) From 703eaac7ca159b041352f47bb615bdd74d20adf3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 1 Jun 2015 13:32:03 -0700 Subject: [PATCH 86/93] Keep around atom.config.unobserve no-op --- src/config.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 9ab8ce607..de907b8dd 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -1158,6 +1158,9 @@ withoutEmptyObjects = (object) -> resultObject = object resultObject +Config::unobserve = (keyPath) -> + Grim.deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.' + if Grim.includeDeprecatedAPIs EmitterMixin = require('emissary').Emitter EmitterMixin.includeInto(Config) @@ -1207,9 +1210,6 @@ if Grim.includeDeprecatedAPIs Grim.deprecate 'Config::toggle is no longer supported. Please remove from your code.' @set(keyPath, not @get(keyPath)) - Config::unobserve = (keyPath) -> - Grim.deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.' - Config::addScopedSettings = (source, selector, value, options) -> Grim.deprecate("Use ::set instead") settingsBySelector = {} From 66620f85105fed8ec095a802bd5e1cc9014348da Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 1 Jun 2015 13:32:38 -0700 Subject: [PATCH 87/93] Add TODO comment --- src/config.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.coffee b/src/config.coffee index de907b8dd..f781e02e9 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -1158,6 +1158,7 @@ withoutEmptyObjects = (object) -> resultObject = object resultObject +# TODO remove in 1.0 API Config::unobserve = (keyPath) -> Grim.deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.' From 4812dcc355b595b098a9bfa27551b54c754641c7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 1 Jun 2015 20:14:39 +0200 Subject: [PATCH 88/93] Include invisibles in TokenizedLine::copy --- src/tokenized-line.coffee | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 6761eecad..b8f7226c8 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -220,17 +220,20 @@ class TokenizedLine copy: -> copy = new TokenizedLine copy.tokenIterator = @tokenIterator - copy.indentLevel = @indentLevel copy.openScopes = @openScopes copy.text = @text copy.tags = @tags copy.specialTokens = @specialTokens + copy.startBufferColumn = @startBufferColumn + copy.bufferDelta = @bufferDelta + copy.ruleStack = @ruleStack + copy.lineEnding = @lineEnding + copy.invisibles = @invisibles + copy.endOfLineInvisibles = @endOfLineInvisibles + copy.indentLevel = @indentLevel + copy.tabLength = @tabLength copy.firstNonWhitespaceIndex = @firstNonWhitespaceIndex copy.firstTrailingWhitespaceIndex = @firstTrailingWhitespaceIndex - copy.lineEnding = @lineEnding - copy.endOfLineInvisibles = @endOfLineInvisibles - copy.ruleStack = @ruleStack - copy.startBufferColumn = @startBufferColumn copy.fold = @fold copy From 86dd4773fbdf6b0997fbc087ccc912e00a703e08 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 1 Jun 2015 18:26:23 -0400 Subject: [PATCH 89/93] :arrow_up: language-sql@0.16.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e5afd1beb..a8a5c6eb0 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "language-sass": "0.38.0", "language-shellscript": "0.15.0", "language-source": "0.9.0", - "language-sql": "0.15.0", + "language-sql": "0.16.0", "language-text": "0.6.0", "language-todo": "0.23.0", "language-toml": "0.16.0", From 34591d3542e049b49c800f9fc80247a28a793080 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 1 Jun 2015 17:54:43 -0700 Subject: [PATCH 90/93] Catch read errors during deserialization Prevents Atom from failing to open because of a read error with a serialized editor. --- spec/workspace-spec.coffee | 13 +++++++++++++ src/text-editor.coffee | 10 +++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index 009f95a1c..f198fbe94 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -16,6 +16,19 @@ describe "Workspace", -> atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')]) atom.workspace = workspace = new Workspace + describe "serialization", -> + it "does not deserialize text editors for files that can't be read", -> + pathToOpen = path.join(temp.mkdirSync(), 'file.txt') + + waitsForPromise -> + workspace.open(pathToOpen) + + runs -> + expect(workspace.getPaneItems().length).toBe 1 + fs.mkdirSync(pathToOpen) + deserializedWorkspace = atom.workspace.testSerialization() + expect(deserializedWorkspace.getPaneItems().length).toBe 0 + describe "::open(uri, options)", -> openEvents = null diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 6d979fdf7..1eedc884d 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -128,7 +128,15 @@ class TextEditor extends Model displayBuffer: @displayBuffer.serialize() deserializeParams: (params) -> - params.displayBuffer = DisplayBuffer.deserialize(params.displayBuffer) + try + displayBuffer = DisplayBuffer.deserialize(params.displayBuffer) + catch error + if error.syscall is 'read' + return # Error reading the file, don't deserialize an editor for it + else + throw error + + params.displayBuffer = displayBuffer params.registerEditor = true params From 82eb01d13ee12f2b1c75e6adc74ac6d82ba5a28f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 1 Jun 2015 18:01:34 -0700 Subject: [PATCH 91/93] Move deserialize spec to text-editor-spec --- spec/text-editor-spec.coffee | 14 ++++++++++++++ spec/workspace-spec.coffee | 13 ------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 28dbaaff8..8ad1ce390 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1,3 +1,6 @@ +fs = require 'fs-plus' +path = require 'path' +temp = require 'temp' clipboard = require '../src/safe-clipboard' TextEditor = require '../src/text-editor' @@ -19,6 +22,17 @@ describe "TextEditor", -> atom.packages.activatePackage('language-javascript') describe "when the editor is deserialized", -> + it "returns undefined when the path cannot be read", -> + pathToOpen = path.join(temp.mkdirSync(), 'file.txt') + editor1 = null + + waitsForPromise -> + atom.project.open(pathToOpen).then (o) -> editor1 = o + + runs -> + fs.mkdirSync(pathToOpen) + expect(editor1.testSerialization()).toBeUndefined() + it "restores selections and folds based on markers in the buffer", -> editor.setSelectedBufferRange([[1, 2], [3, 4]]) editor.addSelectionForBufferRange([[5, 6], [7, 5]], reversed: true) diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index f198fbe94..009f95a1c 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -16,19 +16,6 @@ describe "Workspace", -> atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')]) atom.workspace = workspace = new Workspace - describe "serialization", -> - it "does not deserialize text editors for files that can't be read", -> - pathToOpen = path.join(temp.mkdirSync(), 'file.txt') - - waitsForPromise -> - workspace.open(pathToOpen) - - runs -> - expect(workspace.getPaneItems().length).toBe 1 - fs.mkdirSync(pathToOpen) - deserializedWorkspace = atom.workspace.testSerialization() - expect(deserializedWorkspace.getPaneItems().length).toBe 0 - describe "::open(uri, options)", -> openEvents = null From 81ff6678bd0446f36743935432188acd0bae1d78 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 2 Jun 2015 12:57:27 -0700 Subject: [PATCH 92/93] Prepare 0.206 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8a5c6eb0..74a07659d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "0.205.0", + "version": "0.206.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From a1b4f89dddf23cdce2f1025784f6b5c54d0781bd Mon Sep 17 00:00:00 2001 From: My-khael Pierce Date: Tue, 2 Jun 2015 16:36:14 -0400 Subject: [PATCH 93/93] remove unnecessary line break --- static/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/static/index.html b/static/index.html index 84e8d57d4..5fcb30ad2 100644 --- a/static/index.html +++ b/static/index.html @@ -2,7 +2,6 @@ -